diff --git a/documentation/earth-satellites.rst b/documentation/earth-satellites.rst index f6fd98e95..8e5f1df1c 100644 --- a/documentation/earth-satellites.rst +++ b/documentation/earth-satellites.rst @@ -8,7 +8,7 @@ by downloading the satellite’s standard SGP4 orbital elements. Orbital elements are published by organizations like `CelesTrak`_. Beware of these limitations: -.. _Celestrak: https://celestrak.org/ +.. _CelesTrak: https://celestrak.org/ 1. Don’t expect perfect agreement between any two pieces of software that are trying to predict satellite positions. @@ -56,28 +56,26 @@ Beware of these limitations: The TLE format and its rivals ============================= -Where most folks download satellite element sets at Celestrak: +Most folks download satellite element sets from CelesTrak: https://celestrak.org/NORAD/elements/index.php -Celestrak supports several data formats. -The original Two-Line Element ‘TLE’ format — -the URL will specify ``FORMAT=tle`` — +CelesTrak supports several data formats. +The original Two-Line Element ‘TLE’ format describes a satellite orbit using two lines of dense ASCII text. Whitespace is significant because every character needs to be aligned in exactly the right column. -Here, for example, are elements for the ISS:: +Here, for example, are elements for the International Space Station (ISS):: ISS (ZARYA) 1 25544U 98067A 24127.82853009 .00015698 00000+0 27310-3 0 9995 2 25544 51.6393 160.4574 0003580 140.6673 205.7250 15.50957674452123 -Here are the same elements as JSON — -in this case, -with ``FORMAT=json-pretty`` specified in the Celestrak URL -because the ‘pretty’ newlines and indentation make it easy to read; -use ``FORMAT=json`` if you don’t need the indentation -and want data that’s more compact:: +But CelesTrak also supports modern formats. +Here is the same element set in JSON format — +specifically, CelesTrak’s ``FORMAT=json-pretty`` format, +which adds indentation to make it easy to read; +use ``FORMAT=json`` if you don’t need the extra spaces and newlines:: [{ "OBJECT_NAME": "ISS (ZARYA)", @@ -99,7 +97,10 @@ and want data that’s more compact:: "MEAN_MOTION_DDOT": 0 }] -And here are the same elements with ``FORMAT=CSV``:: +And here are the same elements with ``FORMAT=CSV`` +(this is only two lines of text, +but your browser will probably wrap them +to fit your screen):: OBJECT_NAME,OBJECT_ID,EPOCH,MEAN_MOTION,ECCENTRICITY,INCLINATION,RA_OF_ASC_NODE,ARG_OF_PERICENTER,MEAN_ANOMALY,EPHEMERIS_TYPE,CLASSIFICATION_TYPE,NORAD_CAT_ID,ELEMENT_SET_NO,REV_AT_EPOCH,BSTAR,MEAN_MOTION_DOT,MEAN_MOTION_DDOT ISS (ZARYA),1998-067A,2024-05-06T19:53:04.999776,15.50957674,.000358,51.6393,160.4574,140.6673,205.7250,0,U,25544,999,45212,.2731E-3,.15698E-3,0 @@ -113,184 +114,202 @@ Note these differences: with four digits past the decimal point — both JSON and CSV will be able to carry greater precision in the future. The old TLE format, by contrast, - can’t add more decimal places + will never be able to add more decimal places because each element has a fixed-width number of characters. * The TLE format has run out of simple integer catalog numbers, - because the catalog number + because it limits the catalog number (in the above example, ``25544``) - is limited to 5 digits. + to 5 digits. The JSON and CSV formats, by contrast, don’t place a limit on the size of the catalog number. * The JSON and CSV formats are self-documenting. You can tell, even as a first-time reader, which element is the inclination. - But the TLE lines provide no hint about which element is which. + The old TLE lines provide no hint about which element is which. * The TLE and CSV formats are both very efficient, and describe an orbit using about 150 characters. - Yes, the CSV file has that big 225-character header at the top, - but it only appears once, + Yes, the CSV file has that big 225-character header at the top; + but the header line only appears once, no matter how many satellites are listed after it. By contrast, the bulky JSON format requires more than 400 characters per satellite - because the element names need to be repeated over again - for every satellite. + because the element names need to be repeated again every time. -There are more obscure formats in use at Celestrak, -including ‘key-value notation (KVN)’ and an unfortunate XML format, +There are more obscure formats in use at CelesTrak, +including a ‘key-value notation (KVN)’ and an unfortunate XML format, but here we will focus on the mainstream formats listed above. -Downloading a TLE file ----------------------- +Downloading satellite elements +------------------------------ + +Whether you choose one of CelesTrak’s +`pre-packaged satellite lists +`_ +like ‘Space Stations’ or ‘CubeSats’, +or perform a +`query for a particular satellite +`_, +there are three issues to beware of: + +* Be sure to save the data to a file the first time your script runs, + so that subsequent runs won’t need to download the same data again. + This is crucial to reducing the load on the CelesTrak servers + and helping them continue to provide CelesTrak as a free service. + +* Satellite elements gradually go out of date. + Once a file is a few days old, + you will probably want to download the file again. + +* The CelesTrak data URLs all use the exact same filename. + So if you download the ‘Space Stations’ + whose URL looks like ``gp.php?GROUP=stations``, + and then the ‘CubeSats’ with ``gp.php?GROUP=cubesat``, + then Skyfield will save them to the same filename ``gp.php`` + and the CubeSats will wind up overwriting the Space Stations. + To avoid this, use the ``filename=`` optional argument + to create a separate local file for each remote URL. + +Here’s a useful pattern for downloading element sets with Skyfield: + +.. include:: ../examples/satellite_download.py + :literal: + +The next section will illustrate how to load satellites +once you have downloaded the file. + +If your project is serious enough +that you will need to be able to double-check and replicate old results later, +then don’t follow this example — +every time the file gets too old, +this code will overwrite the file with new data. +Instead, you will probably want to put the date in the filename, +and archive each file along with your project’s code. + +Loading satellite elements +-------------------------- + +Once you have downloaded a file of elements, +use one of these patterns to load them into Skyfield. +For the traditional TLE format: -You can find satellite element sets at the -`NORAD Two-Line Element Sets `_ -page of the Celestrak web site. - -Beware that the two-line element (TLE) format is very rigid. -The meaning of each character -is based on its exact offset from the beginning of the line. -You must download and use the element set’s text -without making any change to its whitespace. - -Skyfield loader objects offer a :meth:`~skyfield.iokit.Loader.tle_file()` -method that can download and cache a file full of satellite elements -from a site like Celestrak. -A popular observing target for satellite observers -is the International Space Station, -which is listed in Celestrak’s ``stations.txt`` file: - -.. testsetup:: +.. testcode:: - stations_txt_bytes = b"""\ - ISS (ZARYA) \n\ - 1 25544U 98067A 14020.93268519 .00009878 00000-0 18200-3 0 5082 - 2 25544 51.6498 109.4756 0003572 55.9686 274.8005 15.49815350868473 - """ * 60 - open('stations.txt', 'wb').write(stations_txt_bytes) + from skyfield.api import load + from skyfield.iokit import parse_tle_file -.. testcode:: + ts = load.timescale() - from skyfield.api import load, wgs84 + with load.open('stations.tle') as f: + satellites = list(parse_tle_file(f, ts)) - stations_url = 'http://celestrak.org/NORAD/elements/stations.txt' - satellites = load.tle_file(stations_url) print('Loaded', len(satellites), 'satellites') .. testoutput:: - Loaded 60 satellites + Loaded 27 satellites -Indexing satellites by name or number -------------------------------------- - -If you want to operate on every satellite -in the list that you have loaded from a file, -you can use Python’s ``for`` loop. -But if you instead want to select individual satellites by name or number, -try building a lookup dictionary -using Python’s dictionary comprehension syntax: +For the verbose but easy-to-read JSON format: .. testcode:: - by_name = {sat.name: sat for sat in satellites} - satellite = by_name['ISS (ZARYA)'] - print(satellite) + import json + from skyfield.api import EarthSatellite, load + + with load.open('stations.json') as f: + data = json.load(f) + + ts = load.timescale() + sats = [EarthSatellite.from_omm(ts, fields) for fields in data] + print('Loaded', len(sats), 'satellites') .. testoutput:: - ISS (ZARYA) catalog #25544 epoch 2014-01-20 22:23:04 UTC + Loaded 27 satellites + +For the more compact CSV format: .. testcode:: - by_number = {sat.model.satnum: sat for sat in satellites} - satellite = by_number[25544] - print(satellite) + import csv + from skyfield.api import EarthSatellite, load -.. testoutput:: + with load.open('stations.csv', mode='r') as f: + data = list(csv.DictReader(f)) - ISS (ZARYA) catalog #25544 epoch 2014-01-20 22:23:04 UTC + ts = load.timescale() + sats = [EarthSatellite.from_omm(ts, fields) for fields in data] + print('Loaded', len(sats), 'satellites') -Performing a TLE query ----------------------- +.. testoutput:: -In addition to offering traditional text files -like ``stations.txt`` and ``active.txt``, -Celestrak supports queries that return TLE elements. + Loaded 27 satellites -But be careful! +In each case, +you are asked to provide a ``ts`` timescale. +Why? +Because Skyfield needs the timescale’s knowledge of leap seconds +to turn each satellite’s epoch date +into an ``.epoch`` :class:`~skyfield.timelib.Time` object. -Because every query to Celestrak requests the same filename ``tle.php`` -Skyfield will by default only download the first result. -Your second, third, and all subsequent attempts to query Celestrak -will simply return the contents -of the ``tle.php`` file that’s already on disk — -giving you the results of your first query over and over again. +Loading satellite data from a string +------------------------------------ -Here are two easy remedies: +If your program has already loaded TLE, JSON, or CSV data into memory +and doesn’t need to read it over again from a file, +you can make the string behave like a file +by wrapping it in a Python I/O object:: -1. Specify the argument ``reload=True``, - which asks Skyfield to always download new results - even if there is already a file on disk. - Every query will overwrite the file with new data. + # For TLE and JSON: -2. Or, specify a ``filename=`` argument - so that each query’s result - is saved to a file specific to that query. - Each query result will be saved to disk with its own filename. + from io import BytesIO + f = BytesIO(byte_string) -Here’s an example of the second approach — -code that requests one specific satellite, -saving the result to a file specific to the query: + # For CSV: -.. testcode:: + from io import StringIO + f = StringIO(text_string) - n = 25544 - url = 'https://celestrak.org/satcat/tle.php?CATNR={}'.format(n) - filename = 'tle-CATNR-{}.txt'.format(n) - satellites = load.tle_file(url, filename=filename) - print(satellites) +You can then use the resulting file object ``f`` +with the example code in the previous section. -.. testoutput:: +Indexing satellites by name or number +------------------------------------- + +If you want to operate on every satellite +in the list that you have loaded from a file, +you can use Python’s ``for`` loop. +But if you instead want to select individual satellites by name or number, +try building a lookup dictionary +using Python’s dictionary comprehension syntax: - [] +.. testcode:: -The above code will download a new result -each time it’s asked for a satellite that it hasn’t yet fetched. -But note that when asked again for the same satellite, -it will simply reload the existing file from disk -unless ``reload=True`` is specified. + by_name = {sat.name: sat for sat in satellites} + satellite = by_name['ISS (ZARYA)'] + print(satellite) -Loading a TLE file from a string --------------------------------- +.. testoutput:: -If your program has already loaded the text of a TLE file, -and you don’t need Skyfield to handle the download for you, -then you can turn it into a list of Skyfield satellites -with the :func:`~skyfield.iokit.parse_tle_file()` function. -The function is a Python generator, -so we use Python’s ``list()`` constructor -to load all the satellites: + ISS (ZARYA) catalog #25544 epoch 2024-05-09 08:48:20 UTC .. testcode:: - from io import BytesIO - from skyfield.iokit import parse_tle_file + by_number = {sat.model.satnum: sat for sat in satellites} + satellite = by_number[25544] + print(satellite) - f = BytesIO(stations_txt_bytes) - satellites = list(parse_tle_file(f)) +.. testoutput:: -If it’s simpler in your case, -you can instead pass :func:`~skyfield.iokit.parse_tle_file()` -a simple list of lines. + ISS (ZARYA) catalog #25544 epoch 2024-05-09 08:48:20 UTC Loading a single TLE set from strings ------------------------------------- If your program already has the two lines of TLE data for a satellite -and doesn’t need Skyfield to download and parse a Celestrak file, +and doesn’t need Skyfield to download and parse a CelesTrak file, you can instantiate an :class:`~skyfield.sgp4lib.EarthSatellite` directly. .. testcode:: @@ -401,6 +420,8 @@ over the span of a single day: .. testcode:: + from skyfield.api import wgs84 + bluffton = wgs84.latlon(+40.8939, -83.8917) t0 = ts.utc(2014, 1, 23) t1 = ts.utc(2014, 1, 24) @@ -702,7 +723,6 @@ and ``False`` otherwise. .. testcode:: eph = load('de421.bsp') - satellite = by_name['ISS (ZARYA)'] two_hours = ts.utc(2014, 1, 20, 0, range(0, 120, 20)) sunlit = satellite.at(two_hours).is_sunlit(eph) @@ -749,7 +769,6 @@ through the :meth:`~skyfield.positionlib.ICRF.is_behind_earth()` method. eph = load('de421.bsp') earth, venus = eph['earth'], eph['venus'] - satellite = by_name['ISS (ZARYA)'] two_hours = ts.utc(2014, 1, 20, 0, range(0, 120, 20)) p = (earth + satellite).at(two_hours).observe(venus).apparent() diff --git a/documentation/stations.csv b/documentation/stations.csv new file mode 100644 index 000000000..3e8d0c608 --- /dev/null +++ b/documentation/stations.csv @@ -0,0 +1,28 @@ +OBJECT_NAME,OBJECT_ID,EPOCH,MEAN_MOTION,ECCENTRICITY,INCLINATION,RA_OF_ASC_NODE,ARG_OF_PERICENTER,MEAN_ANOMALY,EPHEMERIS_TYPE,CLASSIFICATION_TYPE,NORAD_CAT_ID,ELEMENT_SET_NO,REV_AT_EPOCH,BSTAR,MEAN_MOTION_DOT,MEAN_MOTION_DDOT +ISS (ZARYA),1998-067A,2024-05-09T08:48:19.938816,15.51043222,.0003466,51.6381,147.8590,150.4670,338.5502,0,U,25544,999,45252,.27777E-3,.16024E-3,0 +CSS (TIANHE),2021-035A,2024-05-09T00:08:21.259968,15.63450567,.0003879,41.4681,322.4705,310.0852,49.9647,0,U,48274,999,17296,.47178E-3,.44782E-3,0 +ISS (NAUKA),2021-066A,2024-05-08T19:52:52.426848,15.51025615,.0003349,51.6355,150.5366,149.2285,210.8902,0,U,49044,999,15817,.28217E-3,.16275E-3,0 +FREGAT DEB,2011-037PF,2024-05-09T01:52:27.087456,12.14183725,.0970755,51.6682,100.0873,38.9529,327.7334,0,U,49271,999,13044,.53806E-1,.22918E-3,0 +CSS (WENTIAN),2022-085A,2024-05-08T19:32:24.132480,15.63430646,.0003878,41.4681,323.6416,309.1053,50.9442,0,U,53239,999,10185,.44581E-3,.4225E-3,0 +CSS (MENGTIAN),2022-143A,2024-05-08T19:32:24.132480,15.63430646,.0003878,41.4681,323.6416,309.1053,50.9442,0,U,54216,999,8633,.44581E-3,.4225E-3,0 +ISS DEB [SPX-28 IPA FSE],1998-067VP,2024-05-08T15:57:19.131264,16.01941673,.0004565,51.6185,113.9060,57.1524,302.9926,0,U,57212,999,4985,.725E-3,.424541E-2,.90482E-4 +ISS DEB,1998-067WC,2024-05-08T12:13:05.124576,15.79259615,.0006542,51.6266,136.3716,42.3437,317.8068,0,U,58229,999,2938,.87658E-3,.163586E-2,0 +PROGRESS-MS 25,2023-184A,2024-05-08T19:52:52.426848,15.51025615,.0003349,51.6355,150.5366,149.2285,210.8902,0,U,58460,999,2447,.28217E-3,.16275E-3,0 +BEAK,1998-067WD,2024-05-09T03:41:34.279008,15.76580994,.0005919,51.6309,136.9549,51.3320,308.8208,0,U,58612,999,2238,.95703E-3,.158289E-2,0 +TIANZHOU-7,2024-013A,2024-05-08T19:32:24.132480,15.63430646,.0003878,41.4681,323.6416,309.1053,50.9442,0,U,58811,999,17242,.44581E-3,.4225E-3,0 +CYGNUS NG-20,2024-021A,2024-05-08T19:52:52.426848,15.51025615,.0003349,51.6355,150.5366,149.2285,210.8902,0,U,58898,999,1456,.28217E-3,.16275E-3,0 +PROGRESS-MS 26,2024-029A,2024-05-08T19:52:52.426848,15.51025615,.0003349,51.6355,150.5366,149.2285,210.8902,0,U,58961,999,1262,.28217E-3,.16275E-3,0 +CREW DRAGON 8,2024-042A,2024-05-08T19:52:52.426848,15.51025615,.0003349,51.6355,150.5366,149.2285,210.8902,0,U,59097,999,983,.28217E-3,.16275E-3,0 +SOYUZ-MS 25,2024-055A,2024-05-08T19:52:52.426848,15.51025615,.0003349,51.6355,150.5366,149.2285,210.8902,0,U,59294,999,637,.28217E-3,.16275E-3,0 +MICROORBITER-1,1998-067WF,2024-05-09T05:26:00.617856,15.56086321,.000293,51.6338,147.9454,101.0748,259.0573,0,U,59483,999,439,.11224E-2,.80169E-3,0 +CURTIS,1998-067WG,2024-05-08T23:26:48.164640,15.55130715,.000163,51.6338,149.2796,101.4702,258.6472,0,U,59507,999,436,.96605E-3,.66412E-3,0 +KASHIWA,1998-067WH,2024-05-08T10:52:35.969376,15.56340642,.0002884,51.6337,151.7802,96.6079,263.5241,0,U,59508,999,428,.12306E-2,.88878E-3,0 +1998-067WJ,1998-067WJ,2024-05-08T23:50:31.532064,15.54210810,.0004934,51.6352,149.3927,75.1638,284.9899,0,U,59559,999,323,.95997E-3,.63795E-3,0 +1998-067WK,1998-067WK,2024-05-08T23:33:34.532352,15.55918508,.0007105,51.6349,149.3135,85.4197,274.7606,0,U,59560,999,315,.14945E-2,.106686E-2,0 +1998-067WL,1998-067WL,2024-05-08T08:42:20.237472,15.52592728,.000278,51.6357,152.6792,98.5651,261.5654,0,U,59561,999,310,.51054E-3,.31623E-3,0 +BURSTCUBE,1998-067WM,2024-05-08T23:52:55.364736,15.54147491,.0002632,51.6351,149.4024,117.5195,242.6063,0,U,59562,999,321,.95233E-3,.63112E-3,0 +1998-067WN,1998-067WN,2024-05-08T23:58:11.728704,15.53686017,.0003114,51.6355,149.4273,129.0540,231.0728,0,U,59563,999,320,.76265E-3,.49516E-3,0 +SHENZHOU-18 (SZ-18),2024-078A,2024-05-08T13:24:27.582912,15.63411877,.0003902,41.4681,325.2027,308.5199,51.5291,0,U,59591,999,17289,.4696E-3,.44503E-3,0 +1998-067WP,1998-067WP,2024-05-08T12:56:24.246528,15.54734172,.0005166,51.6350,151.6080,73.0758,287.0799,0,U,59596,999,317,.11065E-2,.75147E-3,0 +1998-067WQ,1998-067WQ,2024-05-08T14:28:37.834752,15.54780191,.0005199,51.6358,151.2866,73.4697,286.6865,0,U,59597,999,315,.11126E-2,.75697E-3,0 +SZ-17 MODULE,2023-164C,2024-05-08T19:41:15.555552,15.62250315,.0005736,41.4784,323.6878,330.8958,29.1561,0,U,59624,999,137,.39322E-3,.35514E-3,0 diff --git a/documentation/stations.json b/documentation/stations.json new file mode 100644 index 000000000..c5c0280ed --- /dev/null +++ b/documentation/stations.json @@ -0,0 +1 @@ +[{"OBJECT_NAME":"ISS (ZARYA)","OBJECT_ID":"1998-067A","EPOCH":"2024-05-09T08:48:19.938816","MEAN_MOTION":15.51043222,"ECCENTRICITY":0.0003466,"INCLINATION":51.6381,"RA_OF_ASC_NODE":147.859,"ARG_OF_PERICENTER":150.467,"MEAN_ANOMALY":338.5502,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":25544,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":45252,"BSTAR":0.00027777,"MEAN_MOTION_DOT":0.00016024,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"CSS (TIANHE)","OBJECT_ID":"2021-035A","EPOCH":"2024-05-09T00:08:21.259968","MEAN_MOTION":15.63450567,"ECCENTRICITY":0.0003879,"INCLINATION":41.4681,"RA_OF_ASC_NODE":322.4705,"ARG_OF_PERICENTER":310.0852,"MEAN_ANOMALY":49.9647,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":48274,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":17296,"BSTAR":0.00047178,"MEAN_MOTION_DOT":0.00044782,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"ISS (NAUKA)","OBJECT_ID":"2021-066A","EPOCH":"2024-05-08T19:52:52.426848","MEAN_MOTION":15.51025615,"ECCENTRICITY":0.0003349,"INCLINATION":51.6355,"RA_OF_ASC_NODE":150.5366,"ARG_OF_PERICENTER":149.2285,"MEAN_ANOMALY":210.8902,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":49044,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":15817,"BSTAR":0.00028217,"MEAN_MOTION_DOT":0.00016275,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"FREGAT DEB","OBJECT_ID":"2011-037PF","EPOCH":"2024-05-09T01:52:27.087456","MEAN_MOTION":12.14183725,"ECCENTRICITY":0.0970755,"INCLINATION":51.6682,"RA_OF_ASC_NODE":100.0873,"ARG_OF_PERICENTER":38.9529,"MEAN_ANOMALY":327.7334,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":49271,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":13044,"BSTAR":0.053806,"MEAN_MOTION_DOT":0.00022918,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"CSS (WENTIAN)","OBJECT_ID":"2022-085A","EPOCH":"2024-05-08T19:32:24.132480","MEAN_MOTION":15.63430646,"ECCENTRICITY":0.0003878,"INCLINATION":41.4681,"RA_OF_ASC_NODE":323.6416,"ARG_OF_PERICENTER":309.1053,"MEAN_ANOMALY":50.9442,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":53239,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":10185,"BSTAR":0.00044581,"MEAN_MOTION_DOT":0.0004225,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"CSS (MENGTIAN)","OBJECT_ID":"2022-143A","EPOCH":"2024-05-08T19:32:24.132480","MEAN_MOTION":15.63430646,"ECCENTRICITY":0.0003878,"INCLINATION":41.4681,"RA_OF_ASC_NODE":323.6416,"ARG_OF_PERICENTER":309.1053,"MEAN_ANOMALY":50.9442,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":54216,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":8633,"BSTAR":0.00044581,"MEAN_MOTION_DOT":0.0004225,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"ISS DEB [SPX-28 IPA FSE]","OBJECT_ID":"1998-067VP","EPOCH":"2024-05-08T15:57:19.131264","MEAN_MOTION":16.01941673,"ECCENTRICITY":0.0004565,"INCLINATION":51.6185,"RA_OF_ASC_NODE":113.906,"ARG_OF_PERICENTER":57.1524,"MEAN_ANOMALY":302.9926,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":57212,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":4985,"BSTAR":0.000725,"MEAN_MOTION_DOT":0.00424541,"MEAN_MOTION_DDOT":9.0482e-5},{"OBJECT_NAME":"ISS DEB","OBJECT_ID":"1998-067WC","EPOCH":"2024-05-08T12:13:05.124576","MEAN_MOTION":15.79259615,"ECCENTRICITY":0.0006542,"INCLINATION":51.6266,"RA_OF_ASC_NODE":136.3716,"ARG_OF_PERICENTER":42.3437,"MEAN_ANOMALY":317.8068,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":58229,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":2938,"BSTAR":0.00087658,"MEAN_MOTION_DOT":0.00163586,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"PROGRESS-MS 25","OBJECT_ID":"2023-184A","EPOCH":"2024-05-08T19:52:52.426848","MEAN_MOTION":15.51025615,"ECCENTRICITY":0.0003349,"INCLINATION":51.6355,"RA_OF_ASC_NODE":150.5366,"ARG_OF_PERICENTER":149.2285,"MEAN_ANOMALY":210.8902,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":58460,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":2447,"BSTAR":0.00028217,"MEAN_MOTION_DOT":0.00016275,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"BEAK","OBJECT_ID":"1998-067WD","EPOCH":"2024-05-09T03:41:34.279008","MEAN_MOTION":15.76580994,"ECCENTRICITY":0.0005919,"INCLINATION":51.6309,"RA_OF_ASC_NODE":136.9549,"ARG_OF_PERICENTER":51.332,"MEAN_ANOMALY":308.8208,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":58612,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":2238,"BSTAR":0.00095703,"MEAN_MOTION_DOT":0.00158289,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"TIANZHOU-7","OBJECT_ID":"2024-013A","EPOCH":"2024-05-08T19:32:24.132480","MEAN_MOTION":15.63430646,"ECCENTRICITY":0.0003878,"INCLINATION":41.4681,"RA_OF_ASC_NODE":323.6416,"ARG_OF_PERICENTER":309.1053,"MEAN_ANOMALY":50.9442,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":58811,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":17242,"BSTAR":0.00044581,"MEAN_MOTION_DOT":0.0004225,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"CYGNUS NG-20","OBJECT_ID":"2024-021A","EPOCH":"2024-05-08T19:52:52.426848","MEAN_MOTION":15.51025615,"ECCENTRICITY":0.0003349,"INCLINATION":51.6355,"RA_OF_ASC_NODE":150.5366,"ARG_OF_PERICENTER":149.2285,"MEAN_ANOMALY":210.8902,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":58898,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":1456,"BSTAR":0.00028217,"MEAN_MOTION_DOT":0.00016275,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"PROGRESS-MS 26","OBJECT_ID":"2024-029A","EPOCH":"2024-05-08T19:52:52.426848","MEAN_MOTION":15.51025615,"ECCENTRICITY":0.0003349,"INCLINATION":51.6355,"RA_OF_ASC_NODE":150.5366,"ARG_OF_PERICENTER":149.2285,"MEAN_ANOMALY":210.8902,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":58961,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":1262,"BSTAR":0.00028217,"MEAN_MOTION_DOT":0.00016275,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"CREW DRAGON 8","OBJECT_ID":"2024-042A","EPOCH":"2024-05-08T19:52:52.426848","MEAN_MOTION":15.51025615,"ECCENTRICITY":0.0003349,"INCLINATION":51.6355,"RA_OF_ASC_NODE":150.5366,"ARG_OF_PERICENTER":149.2285,"MEAN_ANOMALY":210.8902,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59097,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":983,"BSTAR":0.00028217,"MEAN_MOTION_DOT":0.00016275,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"SOYUZ-MS 25","OBJECT_ID":"2024-055A","EPOCH":"2024-05-08T19:52:52.426848","MEAN_MOTION":15.51025615,"ECCENTRICITY":0.0003349,"INCLINATION":51.6355,"RA_OF_ASC_NODE":150.5366,"ARG_OF_PERICENTER":149.2285,"MEAN_ANOMALY":210.8902,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59294,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":637,"BSTAR":0.00028217,"MEAN_MOTION_DOT":0.00016275,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"MICROORBITER-1","OBJECT_ID":"1998-067WF","EPOCH":"2024-05-09T05:26:00.617856","MEAN_MOTION":15.56086321,"ECCENTRICITY":0.000293,"INCLINATION":51.6338,"RA_OF_ASC_NODE":147.9454,"ARG_OF_PERICENTER":101.0748,"MEAN_ANOMALY":259.0573,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59483,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":439,"BSTAR":0.0011224,"MEAN_MOTION_DOT":0.00080169,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"CURTIS","OBJECT_ID":"1998-067WG","EPOCH":"2024-05-08T23:26:48.164640","MEAN_MOTION":15.55130715,"ECCENTRICITY":0.000163,"INCLINATION":51.6338,"RA_OF_ASC_NODE":149.2796,"ARG_OF_PERICENTER":101.4702,"MEAN_ANOMALY":258.6472,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59507,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":436,"BSTAR":0.00096605,"MEAN_MOTION_DOT":0.00066412,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"KASHIWA","OBJECT_ID":"1998-067WH","EPOCH":"2024-05-08T10:52:35.969376","MEAN_MOTION":15.56340642,"ECCENTRICITY":0.0002884,"INCLINATION":51.6337,"RA_OF_ASC_NODE":151.7802,"ARG_OF_PERICENTER":96.6079,"MEAN_ANOMALY":263.5241,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59508,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":428,"BSTAR":0.0012306,"MEAN_MOTION_DOT":0.00088878,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"1998-067WJ","OBJECT_ID":"1998-067WJ","EPOCH":"2024-05-08T23:50:31.532064","MEAN_MOTION":15.5421081,"ECCENTRICITY":0.0004934,"INCLINATION":51.6352,"RA_OF_ASC_NODE":149.3927,"ARG_OF_PERICENTER":75.1638,"MEAN_ANOMALY":284.9899,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59559,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":323,"BSTAR":0.00095997,"MEAN_MOTION_DOT":0.00063795,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"1998-067WK","OBJECT_ID":"1998-067WK","EPOCH":"2024-05-08T23:33:34.532352","MEAN_MOTION":15.55918508,"ECCENTRICITY":0.0007105,"INCLINATION":51.6349,"RA_OF_ASC_NODE":149.3135,"ARG_OF_PERICENTER":85.4197,"MEAN_ANOMALY":274.7606,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59560,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":315,"BSTAR":0.0014945,"MEAN_MOTION_DOT":0.00106686,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"1998-067WL","OBJECT_ID":"1998-067WL","EPOCH":"2024-05-08T08:42:20.237472","MEAN_MOTION":15.52592728,"ECCENTRICITY":0.000278,"INCLINATION":51.6357,"RA_OF_ASC_NODE":152.6792,"ARG_OF_PERICENTER":98.5651,"MEAN_ANOMALY":261.5654,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59561,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":310,"BSTAR":0.00051054,"MEAN_MOTION_DOT":0.00031623,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"BURSTCUBE","OBJECT_ID":"1998-067WM","EPOCH":"2024-05-08T23:52:55.364736","MEAN_MOTION":15.54147491,"ECCENTRICITY":0.0002632,"INCLINATION":51.6351,"RA_OF_ASC_NODE":149.4024,"ARG_OF_PERICENTER":117.5195,"MEAN_ANOMALY":242.6063,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59562,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":321,"BSTAR":0.00095233,"MEAN_MOTION_DOT":0.00063112,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"1998-067WN","OBJECT_ID":"1998-067WN","EPOCH":"2024-05-08T23:58:11.728704","MEAN_MOTION":15.53686017,"ECCENTRICITY":0.0003114,"INCLINATION":51.6355,"RA_OF_ASC_NODE":149.4273,"ARG_OF_PERICENTER":129.054,"MEAN_ANOMALY":231.0728,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59563,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":320,"BSTAR":0.00076265,"MEAN_MOTION_DOT":0.00049516,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"SHENZHOU-18 (SZ-18)","OBJECT_ID":"2024-078A","EPOCH":"2024-05-08T13:24:27.582912","MEAN_MOTION":15.63411877,"ECCENTRICITY":0.0003902,"INCLINATION":41.4681,"RA_OF_ASC_NODE":325.2027,"ARG_OF_PERICENTER":308.5199,"MEAN_ANOMALY":51.5291,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59591,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":17289,"BSTAR":0.0004696,"MEAN_MOTION_DOT":0.00044503,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"1998-067WP","OBJECT_ID":"1998-067WP","EPOCH":"2024-05-08T12:56:24.246528","MEAN_MOTION":15.54734172,"ECCENTRICITY":0.0005166,"INCLINATION":51.635,"RA_OF_ASC_NODE":151.608,"ARG_OF_PERICENTER":73.0758,"MEAN_ANOMALY":287.0799,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59596,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":317,"BSTAR":0.0011065,"MEAN_MOTION_DOT":0.00075147,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"1998-067WQ","OBJECT_ID":"1998-067WQ","EPOCH":"2024-05-08T14:28:37.834752","MEAN_MOTION":15.54780191,"ECCENTRICITY":0.0005199,"INCLINATION":51.6358,"RA_OF_ASC_NODE":151.2866,"ARG_OF_PERICENTER":73.4697,"MEAN_ANOMALY":286.6865,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59597,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":315,"BSTAR":0.0011126,"MEAN_MOTION_DOT":0.00075697,"MEAN_MOTION_DDOT":0},{"OBJECT_NAME":"SZ-17 MODULE","OBJECT_ID":"2023-164C","EPOCH":"2024-05-08T19:41:15.555552","MEAN_MOTION":15.62250315,"ECCENTRICITY":0.0005736,"INCLINATION":41.4784,"RA_OF_ASC_NODE":323.6878,"ARG_OF_PERICENTER":330.8958,"MEAN_ANOMALY":29.1561,"EPHEMERIS_TYPE":0,"CLASSIFICATION_TYPE":"U","NORAD_CAT_ID":59624,"ELEMENT_SET_NO":999,"REV_AT_EPOCH":137,"BSTAR":0.00039322,"MEAN_MOTION_DOT":0.00035514,"MEAN_MOTION_DDOT":0}] diff --git a/documentation/stations.tle b/documentation/stations.tle new file mode 100644 index 000000000..5e877f73b --- /dev/null +++ b/documentation/stations.tle @@ -0,0 +1,81 @@ +ISS (ZARYA) +1 25544U 98067A 24130.36689744 .00016024 00000+0 27777-3 0 9992 +2 25544 51.6381 147.8590 0003466 150.4670 338.5502 15.51043222452521 +CSS (TIANHE) +1 48274U 21035A 24130.00580162 .00044782 00000+0 47178-3 0 9992 +2 48274 41.4681 322.4705 0003879 310.0852 49.9647 15.63450567172966 +ISS (NAUKA) +1 49044U 21066A 24129.82838457 .00016275 00000+0 28217-3 0 9992 +2 49044 51.6355 150.5366 0003349 149.2285 210.8902 15.51025615158179 +FREGAT DEB +1 49271U 11037PF 24130.07809129 .00022918 00000+0 53806-1 0 9995 +2 49271 51.6682 100.0873 0970755 38.9529 327.7334 12.14183725130446 +CSS (WENTIAN) +1 53239U 22085A 24129.81416820 .00042250 00000+0 44581-3 0 9994 +2 53239 41.4681 323.6416 0003878 309.1053 50.9442 15.63430646101857 +CSS (MENGTIAN) +1 54216U 22143A 24129.81416820 .00042250 00000+0 44581-3 0 9995 +2 54216 41.4681 323.6416 0003878 309.1053 50.9442 15.63430646 86338 +ISS DEB [SPX-28 IPA FSE] +1 57212U 98067VP 24129.66480476 .00424541 90482-4 72500-3 0 9990 +2 57212 51.6185 113.9060 0004565 57.1524 302.9926 16.01941673 49854 +ISS DEB +1 58229U 98067WC 24129.50908709 .00163586 00000+0 87658-3 0 9997 +2 58229 51.6266 136.3716 0006542 42.3437 317.8068 15.79259615 29386 +PROGRESS-MS 25 +1 58460U 23184A 24129.82838457 .00016275 00000+0 28217-3 0 9997 +2 58460 51.6355 150.5366 0003349 149.2285 210.8902 15.51025615 24476 +BEAK +1 58612U 98067WD 24130.15386897 .00158289 00000+0 95703-3 0 9998 +2 58612 51.6309 136.9549 0005919 51.3320 308.8208 15.76580994 22381 +TIANZHOU-7 +1 58811U 24013A 24129.81416820 .00042250 00000+0 44581-3 0 9998 +2 58811 41.4681 323.6416 0003878 309.1053 50.9442 15.63430646172429 +CYGNUS NG-20 +1 58898U 24021A 24129.82838457 .00016275 00000+0 28217-3 0 9993 +2 58898 51.6355 150.5366 0003349 149.2285 210.8902 15.51025615 14560 +PROGRESS-MS 26 +1 58961U 24029A 24129.82838457 .00016275 00000+0 28217-3 0 9992 +2 58961 51.6355 150.5366 0003349 149.2285 210.8902 15.51025615 12626 +CREW DRAGON 8 +1 59097U 24042A 24129.82838457 .00016275 00000+0 28217-3 0 9998 +2 59097 51.6355 150.5366 0003349 149.2285 210.8902 15.51025615 9836 +SOYUZ-MS 25 +1 59294U 24055A 24129.82838457 .00016275 00000+0 28217-3 0 9991 +2 59294 51.6355 150.5366 0003349 149.2285 210.8902 15.51025615 6371 +MICROORBITER-1 +1 59483U 98067WF 24130.22639604 .00080169 00000+0 11224-2 0 9996 +2 59483 51.6338 147.9454 0002930 101.0748 259.0573 15.56086321 4390 +CURTIS +1 59507U 98067WG 24129.97694635 .00066412 00000+0 96605-3 0 9990 +2 59507 51.6338 149.2796 0001630 101.4702 258.6472 15.55130715 4367 +KASHIWA +1 59508U 98067WH 24129.45319409 .00088878 00000+0 12306-2 0 9992 +2 59508 51.6337 151.7802 0002884 96.6079 263.5241 15.56340642 4280 +1998-067WJ +1 59559U 98067WJ 24129.99342051 .00063795 00000+0 95997-3 0 9995 +2 59559 51.6352 149.3927 0004934 75.1638 284.9899 15.54210810 3236 +1998-067WK +1 59560U 98067WK 24129.98164968 .00106686 00000+0 14945-2 0 9995 +2 59560 51.6349 149.3135 0007105 85.4197 274.7606 15.55918508 3156 +1998-067WL +1 59561U 98067WL 24129.36273423 .00031623 00000+0 51054-3 0 9996 +2 59561 51.6357 152.6792 0002780 98.5651 261.5654 15.52592728 3107 +BURSTCUBE +1 59562U 98067WM 24129.99508524 .00063112 00000+0 95233-3 0 9994 +2 59562 51.6351 149.4024 0002632 117.5195 242.6063 15.54147491 3216 +1998-067WN +1 59563U 98067WN 24129.99874686 .00049516 00000+0 76265-3 0 9996 +2 59563 51.6355 149.4273 0003114 129.0540 231.0728 15.53686017 3205 +SHENZHOU-18 (SZ-18) +1 59591U 24078A 24129.55865258 .00044503 00000+0 46960-3 0 9995 +2 59591 41.4681 325.2027 0003902 308.5199 51.5291 15.63411877172898 +1998-067WP +1 59596U 98067WP 24129.53916952 .00075147 00000+0 11065-2 0 9990 +2 59596 51.6350 151.6080 0005166 73.0758 287.0799 15.54734172 3177 +1998-067WQ +1 59597U 98067WQ 24129.60321568 .00075697 00000+0 11126-2 0 9990 +2 59597 51.6358 151.2866 0005199 73.4697 286.6865 15.54780191 3155 +SZ-17 MODULE +1 59624U 23164C 24129.82031893 .00035514 00000+0 39322-3 0 9993 +2 59624 41.4784 323.6878 0005736 330.8958 29.1561 15.62250315 1375 diff --git a/examples/satellite_download.py b/examples/satellite_download.py new file mode 100644 index 000000000..87ba8dc2a --- /dev/null +++ b/examples/satellite_download.py @@ -0,0 +1,10 @@ +from skyfield.api import load + +max_days = 7.0 # download again once 7 days old +name = 'stations.csv' # custom filename, not 'gp.php' + +base = 'https://celestrak.org/NORAD/elements/gp.php' +url = base + '?GROUP=stations&FORMAT=csv' + +if not load.exists(name) or load.days_old(name) >= max_days: + load.download(url, filename=name) diff --git a/skyfield/iokit.py b/skyfield/iokit.py index f5ba0da57..6dd1495fd 100644 --- a/skyfield/iokit.py +++ b/skyfield/iokit.py @@ -158,7 +158,7 @@ def days_old(self, filename): seconds = time() - mtime return seconds / 86400.0 - def _exists(self, filename): + def exists(self, filename): return os.path.exists(self.path_to(filename)) def __call__(self, filename, reload=False, backup=False, builtin=False): @@ -346,7 +346,7 @@ def timescale(self, delta_t=None, builtin=True): Service. For details, see :ref:`downloading-timescale-files`. """ - e = self._exists + e = self.exists if builtin: # See "build_arrays.py" for a notes on how these are stored. arrays = load_bundled_npy('iers.npz') diff --git a/skyfield/sgp4lib.py b/skyfield/sgp4lib.py index 400cd4564..e118c74e2 100644 --- a/skyfield/sgp4lib.py +++ b/skyfield/sgp4lib.py @@ -4,6 +4,7 @@ from numpy import ( array, concatenate, identity, multiply, ones_like, repeat, ) +from sgp4 import omm from sgp4.api import SGP4_ERRORS, Satrec from .constants import AU_KM, DAY_S, T0, tau @@ -136,6 +137,23 @@ def from_satrec(cls, satrec, ts): self._setup(satrec) return self + @classmethod + def from_omm(cls, ts, element_dict): + """Build an EarthSatellite from OMM text fields. + + Provide a ``ts`` timescale object, and a Python dict of OMM + field names and values. The timescale is used to build the + satellite's ``.epoch`` time. + + """ + self = cls.__new__(cls) + self.name = element_dict.get('OBJECT_NAME', None) + self.model = satrec = Satrec() + omm.initialize(satrec, element_dict) + self.epoch = ts._utc_jd(satrec.jdsatepoch, satrec.jdsatepochF) + self._setup(satrec) + return self + def __str__(self): return self.target_name diff --git a/skyfield/tests/test_timelib.py b/skyfield/tests/test_timelib.py index 941862e14..00a2b1dd5 100644 --- a/skyfield/tests/test_timelib.py +++ b/skyfield/tests/test_timelib.py @@ -276,6 +276,20 @@ def test_building_time_from_python_date(ts): t = ts.utc(d) assert t.utc == (2020, 7, 22, 0, 0, 0.0) +def test_building_time_from_utc_julian_date(ts): + t = ts._utc_jd(2457754.5, - one_second) + assert t.utc == (2016, 12, 31, 23, 59, 59.0) # no JD corresponds to s=60.0 + + t = ts._utc_jd(2457754.5, 0.0) + assert t.utc == (2017, 1, 1, 0, 0, 0.0) + + t = ts._utc_jd(2457754.5, one_second) + assert t.utc == (2017, 1, 1, 0, 0, 1.0) + +def test_utc_julian_date_accuracy(ts): + t = ts._utc_jd(2460439.5, 0.36689744000250357) + assert t.utc_strftime('%H:%M:%S.%f') == '08:48:19.938816' + def test_timescale_linspace(ts): t0 = ts.tt(2021, 11, 3, 6) t1 = ts.tt(2021, 11, 5, 18) diff --git a/skyfield/timelib.py b/skyfield/timelib.py index 3cc3c65ea..332f6cf3e 100644 --- a/skyfield/timelib.py +++ b/skyfield/timelib.py @@ -263,6 +263,21 @@ def _strftime(self, format, jd, fraction, seconds_bump=None): return [strftime(format, item) for item in zip(*tup)] return strftime(format, tup) + def _utc_jd(self, whole, fraction): + # Switch from days to seconds. + seconds = whole * DAY_S + seconds2, fraction_s = divmod(fraction * DAY_S, 1.0) + seconds += seconds2 + + # Add an integer number of leap seconds. + seconds += interp(seconds, self._leap_utc, self._leap_offsets) + + # Switch back to days. + whole, fraction = divmod(seconds, DAY_S) + fraction += fraction_s + fraction /= DAY_S + return self.tai_jd(whole, fraction) + def tai(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0, jd=None): """Build a `Time` from an International Atomic Time `calendar date`.