Nutze Skyfield fuer praezisere Daemmerungsberechnung
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
"""A subpackage of third-party data that gets bundled with Skyfield."""
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -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
@@ -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
@@ -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)
|
||||
BIN
Binary file not shown.
+61
@@ -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
@@ -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'))
|
||||
BIN
Binary file not shown.
+83
@@ -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
@@ -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
@@ -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
@@ -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')
|
||||
Reference in New Issue
Block a user