Nutze Skyfield fuer praezisere Daemmerungsberechnung

This commit is contained in:
2026-07-13 19:35:39 +02:00
parent dc5292735f
commit 97357a88e0
1092 changed files with 370248 additions and 61 deletions
+1
View File
@@ -0,0 +1 @@
"""A subpackage of third-party data that gets bundled with Skyfield."""
Binary file not shown.
Binary file not shown.
Binary file not shown.
+46
View File
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
"""Routines to download Earth orientation data."""
import numpy as np
import os
from skyfield.api import load
from skyfield.timelib import julian_date
def morrison_and_stephenson_2004_table():
"""Table of smoothed Delta T values from Morrison and Stephenson, 2004."""
import pandas as pd
f = load.open('http://eclipse.gsfc.nasa.gov/SEcat5/deltat.html')
tables = pd.read_html(f.read())
df = tables[0]
return pd.DataFrame({'year': df[0], 'delta_t': df[1]})
def parse_S15_table(f):
"""Parse polynomial coefficients from Table S15.
The table is available at the website of Her Majesty's Nautical
Almanac Office, from the paper “Measurement of the Earth's Rotation:
720 BC to AD 2015” by L.V. Morrison, F.R. Stephenson, C.Y. Hohenkerk
and M. Zawilski 2021.
"""
# http://astro.ukho.gov.uk/nao/lvm/Table-S15.2020.txt
content = f.read()
banner = b'- ' * 36 + b'-\n'
sections = content.split(banner)
names = sections[1].splitlines()[-1].decode('utf-8').split()
table = np.loadtxt(sections[2].splitlines())
return names, table.T
def main():
thisdir = os.path.dirname(__file__)
df = morrison_and_stephenson_2004_table()
year = df.year.values
jd = julian_date(year, 1, 1)
delta_t = df.delta_t.values
array = np.array((jd, delta_t))
np.save(os.path.join(thisdir, 'morrison_stephenson_deltat.npy'),
array)
if __name__ == '__main__':
main()
@@ -0,0 +1,87 @@
# All values are in km**3/s**2
# Source: ftp://ssd.jpl.nasa.gov/pub/xfr/gm_Horizons.pck
GM_dict = {
0: 132890518792.23172, # script for calculating this value in design/ssb_gm.py
1: 2.2031780000000021E+04,
2: 3.2485859200000006E+05,
3: 4.0350323550225981E+05,
4: 4.2828375214000022E+04,
5: 1.2671276480000021E+08,
6: 3.7940585200000003E+07,
7: 5.7945486000000080E+06,
8: 6.8365271005800236E+06,
9: 9.7700000000000068E+02,
10: 1.3271244004193938E+11,
199: 2.2031780000000021E+04,
299: 3.2485859200000006E+05,
399: 3.9860043543609598E+05,
499: 4.282837362069909E+04,
599: 1.266865349115908E+08,
699: 3.793120723493890E+07,
799: 5.793951322279009E+06,
899: 6.835099502439672E+06,
999: 8.693390780381926E+02,
301: 4.9028000661637961E+03,
401: 7.087546066894452E-04,
402: 9.615569648120313E-05,
501: 5.959924010272514E+03,
502: 3.202739815114734E+03,
503: 9.887819980080976E+03,
504: 7.179304867611079E+03,
505: 1.487604677404272E-01,
506: 1.380080696078966E-01,
601: 2.503458199931431E+00,
602: 7.211185066509890E+00,
603: 4.120856508658532E+01,
604: 7.311574218947423E+01,
605: 1.539419035933117E+02,
606: 8.978137030983542E+03,
607: 3.712085754472412E-01,
608: 1.205095752388872E+02,
609: 5.532371285376407E-01,
610: 1.265765099012197E-01,
611: 3.512333288208074E-02,
612: 3.424829447502984E-04,
615: 3.718871247516475E-04,
616: 1.075208001007610E-02,
617: 9.290325122028795E-03,
701: 8.346344431770477E+01,
702: 8.509338094489388E+01,
703: 2.269437003741248E+02,
704: 2.053234302535623E+02,
705: 4.319516899232100E+00,
801: 1.427598140725034E+03,
901: 1.062509269522026E+02,
902: 2.150552267969335E-03,
903: 3.663917107480563E-03,
904: 4.540734312735987E-04,
905: 2.000000000000000E-20,
2000001: 6.2809393000000000E+01,
2000002: 1.3923011000000001E+01,
2000003: 1.6224149999999999E+00,
2000004: 1.7288008999999999E+01,
2000010: 5.5423920000000004E+00,
2000015: 2.0981550000000002E+00,
2000016: 1.5300480000000001E+00,
2000031: 2.8448720000000001E+00,
2000048: 1.1351590000000000E+00,
2000052: 1.1108039999999999E+00,
2000065: 1.4264810000000001E+00,
2000087: 9.8635300000000004E-01,
2000088: 1.1557990000000000E+00,
2000433: 4.463E-4,
2000451: 1.0295259999999999E+00,
2000511: 2.3312860000000000E+00,
2000704: 2.3573170000000001E+00,
}
+76
View File
@@ -0,0 +1,76 @@
# This URL worked until September 2020:
#
# URL = 'http://cdsarc.u-strasbg.fr/ftp/cats/I/239/hip_main.dat.gz'
#
# Then someone at VizieR apparently ran `gunzip` on the file, breaking
# the existing URL. The fastest fix is for us to switch to:
#
# URL = 'https://cdsarc.u-strasbg.fr/ftp/cats/I/239/hip_main.dat'
#
# Then in September 2022 the site's certificate broke, and an engineer
# at unistra.fr confirmed that 'the domain "astro.unistra.fr" as well as
# the domain "u-strasbg.fr" are obsoleted by "cds.unistra.fr".' Thus:
URL = 'https://cdsarc.cds.unistra.fr/ftp/cats/I/239/hip_main.dat'
# But what if someone runs `gzip` on the file again? Then the new URL
# will break like the old one did. It appears that VizieR makes no
# guarantee that raw catalog URLs are stable, and that we need to switch
# to one of their catalog generation URLs, which produce text in a new
# format that Skyfield will have to learn. Discussion at:
# https://github.com/skyfielders/python-skyfield/issues/454
url = URL # old name, in case anyone used it
PANDAS_MESSAGE = """Skyfield needs Pandas to load the Hipparcos catalog
To load the Hipparcos star catalog, Skyfield needs the Pandas data
analysis toolkit. Try installing it using your usual Python package
installer, like "pip install pandas" or "conda install pandas".
"""
_COLUMN_NAMES = (
'Catalog', 'HIP', 'Proxy', 'RAhms', 'DEdms', 'Vmag',
'VarFlag', 'r_Vmag', 'RAdeg', 'DEdeg', 'AstroRef', 'Plx', 'pmRA',
'pmDE', 'e_RAdeg', 'e_DEdeg', 'e_Plx', 'e_pmRA', 'e_pmDE', 'DE:RA',
'Plx:RA', 'Plx:DE', 'pmRA:RA', 'pmRA:DE', 'pmRA:Plx', 'pmDE:RA',
'pmDE:DE', 'pmDE:Plx', 'pmDE:pmRA', 'F1', 'F2', '---', 'BTmag',
'e_BTmag', 'VTmag', 'e_VTmag', 'm_BTmag', 'B-V', 'e_B-V', 'r_B-V',
'V-I', 'e_V-I', 'r_V-I', 'CombMag', 'Hpmag', 'e_Hpmag', 'Hpscat',
'o_Hpmag', 'm_Hpmag', 'Hpmax', 'HPmin', 'Period', 'HvarType',
'moreVar', 'morePhoto', 'CCDM', 'n_CCDM', 'Nsys', 'Ncomp',
'MultFlag', 'Source', 'Qual', 'm_HIP', 'theta', 'rho', 'e_rho',
'dHp', 'e_dHp', 'Survey', 'Chart', 'Notes', 'HD', 'BD', 'CoD',
'CPD', '(V-I)red', 'SpType', 'r_SpType',
)
def load_dataframe(fobj):
"""Given an open file for ``hip_main.dat``, return a parsed dataframe.
If the file is gzipped, it will be automatically uncompressed.
"""
try:
from pandas import read_csv
except ImportError:
raise ImportError(PANDAS_MESSAGE)
fobj.seek(0)
magic = fobj.read(2)
compression = 'gzip' if (magic == b'\x1f\x8b') else None
fobj.seek(0)
df = read_csv(
fobj, sep='|', names=_COLUMN_NAMES, compression=compression,
usecols=['HIP', 'Vmag', 'RAdeg', 'DEdeg', 'Plx', 'pmRA', 'pmDE'],
na_values=[' ', ' ', ' ', ' '],
)
df.columns = (
'hip', 'magnitude', 'ra_degrees', 'dec_degrees',
'parallax_mas', 'ra_mas_per_year', 'dec_mas_per_year',
)
df = df.assign(
ra_hours = df['ra_degrees'] / 15.0,
epoch_year = 1991.25,
)
return df.set_index('hip')
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
"""Physical data for the planets from the JPL HORIZONS system.
To rebuild this data, consult the following IPython Notebook:
https://github.com/brandon-rhodes/astronomy-notebooks/blob/master/Utils-HORIZONS-data.ipynb
"""
from skyfield.units import Distance
radii_km = [
('Sun', 695500.0),
('Mercury', 2440.0),
('Venus', 6051.8),
('Earth', 6371.01),
('Mars', 3389.9),
('Jupiter', 69911.0),
('Saturn', 58232.0),
('Uranus', 25362.0),
('Neptune', 24624.0),
('134340 Pluto', 1195.0),
]
def festoon_ephemeris(ephemeris):
for name, radius_km in radii_km:
name = name.lower().split()[-1]
getattr(ephemeris, name).radius = Distance(km=radius_km)
Binary file not shown.
+61
View File
@@ -0,0 +1,61 @@
"""Parse data files from the International Earth Rotation Service.
See:
https://datacenter.iers.org/eop.php
ftp://cddis.gsfc.nasa.gov/pub/products/iers/readme.finals2000A
"""
import numpy as np
from ..constants import DAY_S
inf = float('inf')
# This regular expression must remain a plain string; attempting to
# compile it triggers a bug in older NumPy versions like 1.14.3:
# https://github.com/skyfielders/python-skyfield/issues/372
_R = (b'(?m)^......(.........) . '
b'(.\\d.......)......... '
b'(.\\d.......)......... '
b'.(.\\d........)')
def parse_x_y_dut1_from_finals_all(f):
return np.fromregex(f, _R, [
('utc_mjd', float),
('x_arcseconds', float),
('y_arcseconds', float),
('dut1', float),
])
def install_polar_motion_table(ts, finals_data):
t = ts.utc(1858, 11, 17.0 + finals_data['utc_mjd'])
ts.polar_motion_table = (
t.tt,
np.array(finals_data['x_arcseconds']),
np.array(finals_data['y_arcseconds']),
)
def build_timescale_arrays(utc_mjd, dut1):
big_jumps = np.diff(dut1) > 0.9
leap_second_mask = np.concatenate([[False], big_jumps])
tt_minus_utc = np.cumsum(leap_second_mask) + 32.184 + 12.0
daily_tt = utc_mjd + tt_minus_utc / DAY_S + 2400000.5
daily_delta_t = (tt_minus_utc - dut1).round(7)
leap_dates = utc_mjd[leap_second_mask]
# Since "finals2000A.all" starts on 1973-01-02 and leaves out the
# first two leap seconds, add them back. (But check first, in case
# the user loads a more complete data file than "finals2000A.all".)
first_leap = leap_dates[0] if len(leap_dates) else 0
more_leaps = [mjd for mjd in (41499.0, 41683.0) if first_leap > mjd]
leap_dates = np.concatenate([more_leaps, leap_dates])
leap_dates += 2400000.5
leap_offsets = np.arange(11.0, len(leap_dates) + 11.0)
return daily_tt, daily_delta_t, leap_dates, leap_offsets
# Compatibility with older Skyfield versions:
def parse_dut1_from_finals_all(f):
data = parse_x_y_dut1_from_finals_all(f)
return data['utc_mjd'], data['dut1']
Binary file not shown.
+271
View File
@@ -0,0 +1,271 @@
# -*- coding: utf-8 -*-
"""Routines for interpreting data from the IAU Minor Planet Center."""
import io
import re
import pandas as pd
from ..data.spice import inertial_frames
from ..keplerlib import _KeplerOrbit
from ..timelib import julian_day
MPCORB_URL = 'https://www.minorplanetcenter.net/iau/MPCORB/MPCORB.DAT.gz'
_MPCORB_COLUMNS = [
('designation_packed', (0, 7)),
('magnitude_H', (8, 13)),
('magnitude_G', (14, 19)),
('epoch_packed', (20, 25)),
('mean_anomaly_degrees', (26, 35)),
('argument_of_perihelion_degrees', (37, 46)),
('longitude_of_ascending_node_degrees', (48, 57)),
('inclination_degrees', (59, 68)),
('eccentricity', (70, 79)),
('mean_daily_motion_degrees', (80, 91)),
('semimajor_axis_au', (92, 103)),
('uncertainty', (105, 106)),
('reference', (107, 116)),
('observations', (117, 122)),
('oppositions', (123, 126)),
('observation_period', (127, 136)),
('rms_residual_arcseconds', (137, 141)),
('coarse_perturbers', (142, 145)),
('precise_perturbers', (146, 149)),
('computer_name', (150, 160)),
('hex_flags', (161, 165)),
('designation', (166, 194)),
('last_observation_date', (194, 202)),
]
_MPCORB_NECESSARY_COLUMNS = {
'designation_packed', 'epoch_packed', 'mean_anomaly_degrees',
'argument_of_perihelion_degrees', 'longitude_of_ascending_node_degrees',
'inclination_degrees', 'eccentricity', 'mean_daily_motion_degrees',
'semimajor_axis_au',
}
_MPCORB_DTYPES = {
# These seem to be ignored by read_fwf()?
'epoch_packed': 'category',
'uncertainty': 'category',
'coarse_perturbers': 'category',
'precise_perturbers': 'category',
'computer_name': 'category',
}
_MPCORB_CONVERTERS = {
'designation_packed': str,
'hex_flags': str,
}
def load_mpcorb_dataframe(fobj):
"""Parse a Minor Planet Center orbits file into a Pandas dataframe.
See :doc:`kepler-orbits`. The MPCORB file format is documented at:
https://minorplanetcenter.net/iau/info/MPOrbitFormat.html
"""
# See https://github.com/pandas-dev/pandas/issues/18035
fobj = io.TextIOWrapper(fobj)
columns = _MPCORB_COLUMNS
# if not slow:
# keepers = _MPCORB_NECESSARY_COLUMNS
# columns = [tup for tup in columns if tup[0] in keepers]
names, colspecs = zip(*columns)
df = pd.read_fwf(
fobj, colspecs=colspecs, names=names,
dtypes=_MPCORB_DTYPES,
converters=_MPCORB_CONVERTERS,
)
return df
def mpcorb_orbit(row, ts, gm_km3_s2):
a = row.semimajor_axis_au
e = row.eccentricity
p = a * (1.0 - e*e)
def n(c):
return ord(c) - (48 if c.isdigit() else 55)
def d(s):
year = 100 * n(s[0]) + int(s[1:3])
month = n(s[3])
day = n(s[4])
return julian_day(year, month, day) - 0.5
epoch_jd = d(row.epoch_packed)
t_epoch = ts.tt_jd(epoch_jd)
minor_planet = _KeplerOrbit._from_mean_anomaly(
p,
e,
row.inclination_degrees,
row.longitude_of_ascending_node_degrees,
row.argument_of_perihelion_degrees,
row.mean_anomaly_degrees,
t_epoch,
gm_km3_s2,
10,
row.designation,
)
minor_planet._rotation = inertial_frames['ECLIPJ2000'].T
return minor_planet
COMET_URL = 'https://www.minorplanetcenter.net/iau/MPCORB/CometEls.txt'
_COMET_COLUMNS = [
('number', (0, 4)),
('orbit_type', (4, 5)),
('designation_packed', (5, 12)),
('perihelion_year', (14, 18)),
('perihelion_month', (19, 21)),
('perihelion_day', (22, 29)),
('perihelion_distance_au', (30, 39)),
('eccentricity', (41, 49)),
('argument_of_perihelion_degrees', (51, 59)),
('longitude_of_ascending_node_degrees', (61, 69)),
('inclination_degrees', (71, 79)),
('perturbed_epoch_year', (81, 85)),
('perturbed_epoch_month', (85, 87)),
('perturbed_epoch_day', (87, 89)),
('magnitude_g', (91, 95)),
('magnitude_k', (96, 100)),
('designation', (102, 158)),
('reference', (159, 168)),
]
_COMET_FAST_COLUMNS = (
'perihelion_year', 'perihelion_month', 'perihelion_day',
'perihelion_distance_au', 'eccentricity', 'argument_of_perihelion_degrees',
'longitude_of_ascending_node_degrees', 'inclination_degrees',
'magnitude_g', 'magnitude_k',
'designation', 'reference',
)
# Some CometEls.txt fields now have commas in them, like the final
# Halley's Comet field "98, 1083". Since Pandas 1.4 complains about
# extra fields, let's use Record Separator instead of comma internally.
_COMET_SEP = '\x1E'
_fast_comet_re = None
_fast_comet_sub = None
def load_comets_dataframe(fobj):
"""Parse a Minor Planet Center comets file into a Pandas dataframe.
This imports only the fields essential for computing comet orbits.
See :func:`~skyfield.data.mpc.load_comets_dataframe_slow()` for a
slower routine that includes every comet data field.
See :doc:`kepler-orbits`. The comet file format is documented at:
https://www.minorplanetcenter.net/iau/info/CometOrbitFormat.html
"""
global _fast_comet_re, _fast_comet_sub
text = fobj.read()
if _fast_comet_re is None:
# Build a regular expression that will turn the fixed-width
# comet file into a CSV that Pandas can import efficiently.
keepers = set(_COMET_FAST_COLUMNS)
pat = ['^']
previous_end = None
for name, (start, end) in _COMET_COLUMNS:
if previous_end is not None:
pat.append(' ' * (start - previous_end))
if name == 'designation':
pat.append('(.*?) +(.*)')
break
else:
keep = name in keepers
if keep:
pat.append('(')
pat.append('.' * (end - start))
if keep:
pat.append(')')
previous_end = end
pat = ''.join(pat)
keeper_indexes = range(len(keepers))
sub = _COMET_SEP.join('\\{}'.format(i + 1) for i in keeper_indexes)
_fast_comet_re = re.compile(pat.encode('ascii'), re.M)
_fast_comet_sub = sub.encode('ascii')
text = _fast_comet_re.sub(_fast_comet_sub, text)
df = pd.read_csv(io.BytesIO(text), sep=_COMET_SEP, header=None,
names=_COMET_FAST_COLUMNS)
return df
def load_comets_dataframe_slow(fobj):
"""Parse a Minor Planet Center comets file into a Pandas dataframe.
This routine reads in every single field from the comets data file.
See :func:`~skyfield.data.mpc.load_comets_dataframe()` for a faster
routine that omits some of the more expensive comet fields.
See :doc:`kepler-orbits`. The comet file format is documented at:
https://www.minorplanetcenter.net/iau/info/CometOrbitFormat.html
"""
fobj = io.StringIO(fobj.read().decode('ascii'))
names, colspecs = zip(*_COMET_COLUMNS)
df = pd.read_fwf(fobj, colspecs=colspecs, names=names)
return df
def comet_orbit(row, ts, gm_km3_s2):
e = row.eccentricity
if e == 1.0:
p = row.perihelion_distance_au * 2.0
else:
a = row.perihelion_distance_au / (1.0 - e)
p = a * (1.0 - e*e)
t_perihelion = ts.tt(row.perihelion_year, row.perihelion_month,
row.perihelion_day)
comet = _KeplerOrbit._from_periapsis(
p,
e,
row.inclination_degrees,
row.longitude_of_ascending_node_degrees,
row.argument_of_perihelion_degrees,
t_perihelion,
gm_km3_s2,
10,
row['designation'],
)
comet._rotation = inertial_frames['ECLIPJ2000'].T
return comet
def _comet_orbits(rows, ts, gm_km3_s2):
e = rows.eccentricity.values
parabolic = (e == 1.0)
p = (1 - e*e) / (1.0 - e + parabolic)
p[parabolic] += 2.0
p *= rows.perihelion_distance_au.values
t_perihelion = ts.tt(rows.perihelion_year.values, rows.perihelion_month.values,
rows.perihelion_day.values)
comet = _KeplerOrbit._from_periapsis(
p,
e,
rows.inclination_degrees.values,
rows.longitude_of_ascending_node_degrees.values,
rows.argument_of_perihelion_degrees.values,
t_perihelion,
gm_km3_s2,
10,
rows['designation'],
)
comet._rotation = inertial_frames['ECLIPJ2000'].T
return comet
def unpack(designation_packed):
def n(c):
return ord(c) - (48 if c.isdigit() else 55)
s = designation_packed
s1 = s[1]
if s1 == '/':
return s
return '{0[0]}/{1}{0[2]}{0[3]} {0[4]}{2}{3}'.format(
s, n(s1), int(s[5:7]), s[7].lstrip('0'))
Binary file not shown.
+83
View File
@@ -0,0 +1,83 @@
# Machine generated - see build_spice.py
from numpy import array
inertial_frames = [
('J2000', [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, -0.0, 1.0]]),
('B1950',
[[0.99992570795236291, 0.011178938126427691, 0.0048590038414544293],
[-0.011178938137770135, 0.9999375133499887, -2.715792625851078e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('FK4',
[[0.99992567949568767, 0.011181483239171792, 0.0048590037723143858],
[-0.01118148322046629, 0.99993748489331347, -2.7170293744002029e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-118',
[[0.99992567914061581, 0.011181514992482714, 0.0048590037714515821],
[-0.011181514973402329, 0.99993748453824161, -2.7170448043105616e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-96',
[[0.99992568569166396, 0.011180929131774816, 0.004859003787369841],
[-0.011180929119611181, 0.99993749108928975, -2.7167601165747207e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-102',
[[0.99992570058677066, 0.011179596947047826, 0.004859003823560055],
[-0.011179596950612145, 0.99993750598439646, -2.716112767048625e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-108',
[[0.99992568207060584, 0.011181252967069354, 0.0048590037785712081],
[-0.011181252951082478, 0.99993748746823163, -2.7169174781036253e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-111',
[[0.99992567608045124, 0.011181788652696216, 0.0048590037640154644],
[-0.011181788630384961, 0.99993748147807704, -2.7171777842249146e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-114',
[[0.99992567798323728, 0.011181618493732738, 0.004859003768639205],
[-0.011181618473430402, 0.99993748338086308, -2.7170950987511777e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-122',
[[0.99992567913790542, 0.011181515234874401, 0.0048590037714449962],
[-0.011181515215791154, 0.99993748453553122, -2.7170449220961369e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-125',
[[0.99992567676350608, 0.011181727569991416, 0.0048590037656752851],
[-0.011181727548401311, 0.99993748216113176, -2.7171481022599927e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('DE-130',
[[0.99992567951195044, 0.011181481784821675, 0.0048590037723539028],
[-0.011181481766133343, 0.99993748490957624, -2.7170286676867509e-05],
[-0.0048590038153592712, -2.7162594714247048e-05, 0.9999881946023742]]),
('GALACTIC',
[[-0.054875539395742523, -0.87343710472759606, -0.48383499177002515],
[0.49410945362774389, -0.44482959429757496, 0.74698224869989183],
[-0.86766613568337381, -0.19807638961301985, 0.45598379452141991]]),
('DE-200', [[1.0, 0.0, -0.0], [-0.0, 1.0, -0.0], [-0.0, -0.0, 1.0]]),
('DE-202', [[1.0, 0.0, -0.0], [-0.0, 1.0, -0.0], [-0.0, -0.0, 1.0]]),
('MARSIAU',
[[0.67325774746002498, 0.73940787491414595, -3.6947768825436786e-17],
[-0.58963083782625325, 0.53688031082163401, 0.60340285625473833],
[0.44616082366044196, -0.40624564781301037, 0.79743651350036859]]),
('ECLIPJ2000',
[[1.0, 0.0, 0.0],
[0.0, 0.91748206206918181, 0.39777715593191371],
[0.0, -0.39777715593191371, 0.91748206206918181]]),
('ECLIPB1950',
[[0.99992570795236291, 0.011178938126427691, 0.0048590038414544293],
[-0.012189277138214926, 0.91736881787898283, 0.39785157220522011],
[-9.9405009203520217e-06, -0.3978812427417045, 0.91743692784599817]]),
('DE-140',
[[0.99992567653846676, 0.011181770119802481, 0.0048589521583800562],
[-0.011181770179728694, 0.99993748168487007, -2.7154519585747306e-05],
[-0.0048589520204735384, -2.7179184981447069e-05, 0.99998819485359658]]),
('DE-142',
[[0.99992567654026054, 0.011181769732063588, 0.0048589526815459912],
[-0.011181769790785997, 0.99993748168921248, -2.7154769316986656e-05],
[-0.0048589525464097748, -2.7178939228786992e-05, 0.99998819485104773]]),
('DE-143',
[[0.999925676543585, 0.011181774307743055, 0.0048589414674685858],
[-0.011181774330053015, 0.99993748163825025, -2.7162211525057475e-05],
[-0.0048589414161271738, -2.7171394236557294e-05, 0.99998819490533486]]),
]
inertial_frames = dict((key, array(value)) for key, value in inertial_frames)
+59
View File
@@ -0,0 +1,59 @@
"""Parse Stellarium data files."""
from collections import namedtuple
StarName = namedtuple('StarName', 'hip name')
def parse_constellations(lines):
"""Return a list of constellation outlines.
Each constellation outline is a list of edges, each of which is
drawn between a pair of specific stars::
[
(name, [(star1, star2), (star3, star4), ...]),
(name, [(star1, star2), (star3, star4), ...]),
...
]
Each name is a 3-letter constellation abbreviation; each star is an
integer Hipparcos catalog number. See :ref:`neowise-chart` for an
example of how to combine this data with the Hipparcos star catalog
to draw constellation lines on a chart.
"""
constellations = []
for line in lines:
line = line.lstrip()
if line.startswith(b'#'):
continue
fields = line.split()
if not fields:
continue
name = fields[0]
edges = [(int(fields[i]), int(fields[i+1]))
for i in range(2, len(fields), 2)]
constellations.append((name.decode('utf-8'), edges))
return constellations
def parse_star_names(lines):
"""Return the names in a Stellarium ``star_names.fab`` file.
Returns a list of named tuples, each of which offers a ``.hip``
attribute with a Hipparcos catalog number and a ``.name`` attribute
with the star name. Do not depend on the tuple having only length
two; additional fields may be added in the future.
"""
names = []
for line in lines:
line = line.strip()
if line == b'' or line.startswith(b'#'):
continue
fields = line.split()
hip, name = fields[0].split(b'|')
names.append(StarName(
int(hip),
name.strip(b'_(")').decode('utf-8'),
))
return names
+82
View File
@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""Parsing routines for JPL text PCK files.
For an (incomplete) summary of the file format, look for the heading
“NAIF Text Kernel Format” in the “PCK Required Reading”:
https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/pck.html
"""
import re
def load(lines, variables):
"""Load PCK text kernel names and values into a ``variables`` dict."""
for name, equals, values in parse(lines):
if equals == b'=':
if len(values) == 1:
variables[name] = values[0] # unbox a scalar value
else:
variables[name] = values
elif equals == b'+=':
old = variables.get(name)
if old is None:
old = variables[name] = []
elif not isinstance(old, list):
old = [old] # box an earlier scalar before extending
old.extend(values)
def parse(lines):
"""Yield ``(name, equals, values)`` bytestrings from a PCK text kernel.
This merely reads raw assignment statements; it doesn’t combine
multiple ``+=`` assignments to create single values. The byte
string ``equals`` will be either ``b'='`` or ``b'+='``. Scalars are
returned as a ``values`` list one item long.
"""
tokens = iter(_parse_tokens(lines))
for token in tokens:
name = token.decode('ascii')
equals = next(tokens)
if equals not in (b'=', b'+='):
raise ValueError('an equals sign is expected after %r' % name)
token = next(tokens)
if token == b'(':
values = []
for token in tokens:
if token == b')':
break
values.append(_evaluate(token))
else:
values = [_evaluate(token)]
yield name, equals, values
def _evaluate(token):
"""Return a string, integer, or float parsed from a PCK text kernel."""
if token[0:1].startswith(b"'"):
return token[1:-1].decode('ascii')
if token.isdigit():
return int(token)
if token.startswith(b'@'):
raise NotImplementedError('TODO: need parser for dates,'
' like @01-MAY-1991/16:25')
token = token.replace(b'D', b'E') # for numbers like -1.4D-12
return float(token)
_token_re = re.compile(b"[A-Za-z]\\w+|=|\\+=|\\(|\\)|'[^']*'|[^), ]+")
def _parse_tokens(lines):
"""Yield all the tokens inside the data segments of a PCK text file."""
lines = iter(lines)
for line in lines:
if b'\\begindata' not in line:
continue # save cost of strip() on most lines
line = line.strip()
if line != b'\\begindata':
continue
for line in lines:
line = line.strip()
if line == b'\\begintext':
break
for token in _token_re.findall(line):
yield token
+51
View File
@@ -0,0 +1,51 @@
URL = 'https://cdsarc.u-strasbg.fr/ftp/cats/I/239/tyc_main.dat'
PANDAS_MESSAGE = """Skyfield needs Pandas to load the Tycho2 catalog
To load the Tycho2 star catalog, Skyfield needs the Pandas data
analysis toolkit. Try installing it using your usual Python package
installer, like "pip install pandas" or "conda install pandas".
"""
_COLUMN_NAMES = (
'Catalog', 'TYC', 'Proxy', 'RAhms', 'DEdms', 'Vmag', '---',
'r_Vmag', 'RAdeg', 'DEdeg', 'AstroRef', 'Plx', 'pmRA',
'pmDE', 'e_RAdeg', 'e_DEdeg', 'e_Plx', 'e_pmRA', 'e_pmDE', 'DE:RA',
'Plx:RA', 'Plx:DE', 'pmRA:RA', 'pmRA:DE', 'pmRA:Plx', 'pmDE:RA',
'pmDE:DE', 'pmDE:Plx', 'pmDE:pmRA', 'Nastro', 'F2', 'HIP', 'BTmag',
'e_BTmag', 'VTmag', 'e_VTmag', 'r_BTmag', 'B-V', 'e_B-V', '---(2)',
'Q', 'Fs', 'Source', 'Nphoto', 'VTscat', 'VTmax', 'VTmin', 'Var',
'VarFlag', 'MultFlag', 'morePhoto', 'm_HIP', 'PPM', 'HD', 'BD', 'CoD',
'CPD', 'Remark',
)
def load_dataframe(fobj):
"""Given an open file for ``tyc_main.dat``, return a parsed dataframe.
If the file is gzipped, it will be automatically uncompressed.
"""
try:
from pandas import read_csv
except ImportError:
raise ImportError(PANDAS_MESSAGE)
fobj.seek(0)
magic = fobj.read(2)
compression = 'gzip' if (magic == b'\x1f\x8b') else None
fobj.seek(0)
df = read_csv(
fobj, sep='|', names=_COLUMN_NAMES, compression=compression,
usecols=['TYC', 'Vmag', 'RAdeg', 'DEdeg', 'Plx', 'pmRA', 'pmDE'],
na_values=[' ', ' ', ' '],
)
df.columns = (
'tyc', 'magnitude', 'ra_degrees', 'dec_degrees',
'parallax_mas', 'ra_mas_per_year', 'dec_mas_per_year',
)
df = df.assign(
ra_hours = df['ra_degrees'] / 15.0,
epoch_year = 1991.25,
)
return df.set_index('tyc')