Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-sgp4 for openSUSE:Factory checked in at 2026-03-25 21:20:09 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sgp4 (Old) and /work/SRC/openSUSE:Factory/.python-sgp4.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sgp4" Wed Mar 25 21:20:09 2026 rev:10 rq:1342402 version:2.25 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sgp4/python-sgp4.changes 2025-05-09 18:53:13.145723388 +0200 +++ /work/SRC/openSUSE:Factory/.python-sgp4.new.8177/python-sgp4.changes 2026-03-27 06:49:05.780956150 +0100 @@ -1,0 +2,15 @@ +Wed Mar 25 08:40:55 UTC 2026 - Dirk Müller <[email protected]> + +- update to 2.25: + * Added a ``gravconst`` parameter to the ``omm.initialize()`` + routine. + * The documentation now specifies the acceptable range for orbital + element angles like inclination and mean anomaly, and a new function + ``check_satrec(sat)`` will tell the caller if any of the angles are + out of bounds. + * The documentation now gives an example of loading elements from + JSON. + * Tweaked the fallback Python code to accept TLE lines without a + final checksum character in the 69th column, to match the C++ code. + +------------------------------------------------------------------- Old: ---- sgp4-2.23.tar.gz New: ---- sgp4-2.25.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sgp4.spec ++++++ --- /var/tmp/diff_new_pack.0jJX9B/_old 2026-03-27 06:49:06.412982177 +0100 +++ /var/tmp/diff_new_pack.0jJX9B/_new 2026-03-27 06:49:06.412982177 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-sgp4 # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-sgp4 -Version: 2.23 +Version: 2.25 Release: 0 Summary: Track earth satellite TLE orbits using up-to-date 2010 version of SGP4 License: MIT ++++++ sgp4-2.23.tar.gz -> sgp4-2.25.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/PKG-INFO new/sgp4-2.25/PKG-INFO --- old/sgp4-2.23/PKG-INFO 2023-11-11 14:24:45.915692600 +0100 +++ new/sgp4-2.25/PKG-INFO 2025-08-04 19:52:45.481662000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: sgp4 -Version: 2.23 +Version: 2.25 Summary: Track Earth satellites given TLE data, using up-to-date 2020 SGP4 routines. Home-page: https://github.com/brandon-rhodes/python-sgp4 Author: Brandon Rhodes @@ -10,19 +10,28 @@ Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Scientific/Engineering :: Astronomy +Provides: sgp4 +Description-Content-Type: text/x-rst License-File: LICENSE - +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: provides +Dynamic: summary This package compiles the official C++ code from `Revisiting Spacetrack Report #3`_ (AIAA 2006-6753) — specifically, the 2023 May 09 release @@ -36,9 +45,21 @@ .. _Fundamentals of Astrodynamics and Applications: https://celestrak.org/software/vallado-sw.php +What we today call ‘SGP4’ in fact merges together what in the 1970s were +originally two separate satellite propagation routines, but which are +now selected automatically: + +* SGP4 — the ‘Simplified General Perturbations' model is used for + satellites close enough to Earth that their orbit takes less than + 225 minutes (3 hours 45 minutes) to complete. + +* SDP4 — the ‘Simplified Deep Space Perturbations' model is used for + satellites farther from Earth, which take 225 minutes or longer to + compete an orbit. + If your machine can’t install or compile the C++ code, then this package -falls back to using a slower pure-Python implementation of SGP4. Tests -make sure that its positions **agree to within 0.1 mm** with the +falls back to using a slower pure-Python implementation of the library. +Tests make sure that its positions **agree to within 0.1 mm** with the standard version of the algorithm — an error far less than the 1–3 km/day by which satellites themselves deviate from the ideal orbits described in TLE files. @@ -101,7 +122,7 @@ be consumed by the whole part that specifies the day. The remaining digits will provide a precision for the fraction of around 20.1 µs. This should be no problem for the accuracy of your result — satellite - positions usually off by a few kilometers anyway, far less than a + positions are usually off by a few kilometers anyway, far less than a satellite moves in 20.1 µs — but if you run a solver that dives down into the microseconds while searching for a rising or setting time, the solver might be bothered by the 20.1 µs plateau between each jump @@ -180,23 +201,39 @@ Reading OMM data takes two steps, because OMM supports several different text formats. First, parse the input text to recover the field names and values that it stores; second, build a Python satellite object from -those field values. For example, to load OMM from XML: +those field values. The fastest and most efficient format is usually +CSV, since the column names are only given once, on the first line: ->>> with open('sample_omm.xml') as f: -... fields = next(omm.parse_xml(f)) ->>> sat = Satrec() ->>> omm.initialize(sat, fields) +>>> with open('sample_omm.csv') as f: +... for fields in omm.parse_csv(f): +... sat = Satrec() +... omm.initialize(sat, fields) + +The JSON format is more verbose because it repeats the field names over +again in every single record: + +>>> import json +>>> with open('sample_omm.json') as f: +... record_list = json.load(f) +>>> for fields in record_list: +... sat = Satrec() +... omm.initialize(sat, fields) -Or, to load OMM from CSV: +The most verbose format is XML: ->>> with open('sample_omm.csv') as f: -... fields = next(omm.parse_csv(f)) ->>> sat = Satrec() ->>> omm.initialize(sat, fields) +>>> with open('sample_omm.xml') as f: +... for fields in omm.parse_xml(f): +... sat = Satrec() +... omm.initialize(sat, fields) Either way, the satellite object should wind up properly initialized and ready to start producing positions. +The ``omm.initialize()`` routine uses the standard WGS72 gravity model +by default. To make your own choice of model, supply a third argument +that is one of the three constants ``WGS72OLD``, ``WGS72``, or ``WGS84`` +(which are shown below in the section on ‘Gravity’). + If you are interested in saving satellite parameters using the new OMM format, then read the section on “Export” below. @@ -417,17 +454,26 @@ ... 0.0, # ndot: ballistic coefficient (radians/minute^2) ... 0.0, # nddot: mean motion 2nd derivative (radians/minute^3) ... 0.0007417, # ecco: eccentricity -... 0.3083420829620822, # argpo: argument of perigee (radians) -... 0.9013560935706996, # inclo: inclination (radians) -... 1.4946964807494398, # mo: mean anomaly (radians) +... 0.3083420829620822, # argpo: argument of perigee (radians 0..2pi) +... 0.9013560935706996, # inclo: inclination (radians 0..pi) +... 1.4946964807494398, # mo: mean anomaly (radians 0..2pi) ... 0.06763602333248933, # no_kozai: mean motion (radians/minute) -... 3.686137125541276, # nodeo: R.A. of ascending node (radians) +... 3.686137125541276, # nodeo: R.A. of ascending node (radians 0..2pi) ... ) -These numbers don’t look the same as the numbers in the TLE, because the -underlying ``sgp4init()`` routine uses different units: radians rather -than degrees. But this is the same orbit and will produce the same -positions. +You might notice that these numbers don’t look the same as the numbers +in the TLE above. For example, the inclination was 51.6439 in the TLE, +but is 0.901356 here. That’s because ``sgp4init()`` uses different +units than the TLE format: angles are in radians rather than degrees. +But this is the same orbit and will produce the same positions. + +If you want to double-check that your elements are valid, you can run +the function ``check_satrec()``, which will raise a ``ValueError`` with +an informative error message if any of the angles are out of bounds. If +the satellite is fine, it simply returns, without raising an exception: + +>>> from sgp4.conveniences import check_satrec +>>> check_satrec(satellite2) Note that ``ndot`` and ``nddot`` are ignored by the SGP4 propagator, so you can leave them ``0.0`` without any effect on the resulting satellite @@ -483,11 +529,11 @@ | ``nddot`` — Second time derivative of the mean motion (loaded from the TLE, but otherwise ignored). | ``bstar`` — Ballistic drag coefficient B* (1/earth radii). -| ``inclo`` — Inclination (radians). -| ``nodeo`` — Right ascension of ascending node (radians). +| ``inclo`` — Inclination (radians 0 ≤ i < pi). +| ``nodeo`` — Right ascension of ascending node (radians 0 ≤ Ω < 2pi). | ``ecco`` — Eccentricity. -| ``argpo`` — Argument of perigee (radians). -| ``mo`` — Mean anomaly (radians). +| ``argpo`` — Argument of perigee (radians 0 ≤ ω < 2pi). +| ``mo`` — Mean anomaly (radians 0 ≤ M < 2pi). | ``no_kozai`` — Mean motion (radians/minute). | ``no`` — Alias for ``no_kozai``, for compatibility with old code. @@ -549,13 +595,13 @@ *Mean Elements From Most Recent Propagation* -Partway through each propagation, the SGP4 routine saves a set of -“singly averaged mean elements” that describe the orbit’s shape at the -moment for which a position is being computed. They are averaged with -respect to the mean anomaly and include the effects of secular gravity, -atmospheric drag, and — in Deep Space mode — of those pertubations from -the Sun and Moon that SGP4 averages over an entire revolution of each of -those bodies. They omit both the shorter-term and longer-term periodic +Each time the SGP4 routine generates a satellite position, it also saves +a set of “singly averaged mean elements” that describe shape of the +satellite's orbit at that moment. They are averaged with respect to the +mean anomaly and include the effects of secular gravity, atmospheric +drag, and — in Deep Space mode — of those pertubations from the Sun and +Moon that SGP4 averages over an entire revolution of each of those +bodies. They omit both the shorter-term and longer-term periodic pertubations from the Sun and Moon that SGP4 applies right before computing each position. @@ -643,6 +689,22 @@ Changelog --------- +Not yet released — 2.25 + +* Added a ``gravconst`` parameter to the ``omm.initialize()`` routine. + +2024-02-15 — 2.24 + +* The documentation now specifies the acceptable range for orbital + element angles like inclination and mean anomaly, and a new function + ``check_satrec(sat)`` will tell the caller if any of the angles are + out of bounds. + +* The documentation now gives an example of loading elements from JSON. + +* Tweaked the fallback Python code to accept TLE lines without a final + checksum character in the 69th column, to match the C++ code. + 2023-10-01 — 2.23 * Tweaked tests to resolve breakage introduced by Python 3.12. @@ -732,4 +794,3 @@ | 2013-11-29 — 1.2 — Made ``epochyr`` 4 digits; add ``datetime`` for ``.epoch`` | 2012-11-22 — 1.1 — Python 3 compatibility; more documentation | 2012-08-27 — 1.0 — Initial release - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/setup.py new/sgp4-2.25/setup.py --- old/sgp4-2.23/setup.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/setup.py 2025-08-04 19:52:36.000000000 +0200 @@ -1,19 +1,31 @@ +# This setup.py supports three modes. +# +# 1. We normally perform a best-effort install: we will try to compile +# the C++ module, but if it won't compile, then we fall back to +# installing the pure-Python SGP4 implementation instead. +# +# 2. `PYTHON_SGP4_COMPILE=always` insists on compiling the accelerated +# module written in C++. Install fails if it can't be compiled. +# +# 3. `PYTHON_SGP4_COMPILE=never` insists on installing the pure-Python +# version of SGP4. Users probably never want this, but it's useful +# to support local and CI tests that want to make sure the +# pure-Python version is the one getting tested. + import os import sys from distutils.core import setup, Extension -import sgp4 -description, long_description = sgp4.__doc__.split('\n', 1) +PYTHON_SGP4_COMPILE = os.environ.get('PYTHON_SGP4_COMPILE', '') +ext_modules = [] + +if sys.version_info[0] == 3 and PYTHON_SGP4_COMPILE != 'never': -# Force compilation on Travis CI + Python 3 to make sure it keeps working. -optional = True -if sys.version_info[0] != 2 and os.environ.get('TRAVIS') == 'true': - optional = False + if PYTHON_SGP4_COMPILE == 'always': + optional = False + else: + optional = True -# It is hard to write C extensions that support both Python 2 and 3, so -# we opt here to support the acceleration only for Python 3. -ext_modules = [] -if sys.version_info[0] == 3: ext_modules.append(Extension( 'sgp4.vallado_cpp', optional=optional, @@ -30,40 +42,50 @@ extra_compile_args=['-ffloat-store'], )) -# Read the package's "__version__" without importing it. +# Read the package's docstring and "__version__" without importing it. path = 'sgp4/__init__.py' with open(path, 'rb') as f: text = f.read().decode('utf-8') text = text.replace('-*- coding: utf-8 -*-', '') # for Python 2.7 + namespace = {} eval(compile(text, path, 'exec'), namespace) -setup(name = 'sgp4', - version = namespace['__version__'], - description = description, - long_description = long_description, - license = 'MIT', - author = 'Brandon Rhodes', - author_email = '[email protected]', - url = 'https://github.com/brandon-rhodes/python-sgp4', - classifiers = [ +version = namespace['__version__'] +description, long_description = namespace['__doc__'].split('\n', 1) + +# Avoid "`long_description` has syntax errors in markup" from extra +# whitespace that somehow creeps in. +description = description.strip() +long_description = long_description.strip() + '\n' + +setup( + name = 'sgp4', + version = version, + description = description, + long_description = long_description, + long_description_content_type = 'text/x-rst', + license = 'MIT', + author = 'Brandon Rhodes', + author_email = '[email protected]', + url = 'https://github.com/brandon-rhodes/python-sgp4', + classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: Astronomy', - ], - packages = ['sgp4'], - package_data = {'sgp4': ['SGP4-VER.TLE', 'sample*', 'tcppver.out']}, - ext_modules = ext_modules, + ], + packages = ['sgp4'], + package_data = {'sgp4': ['SGP4-VER.TLE', 'sample*', 'tcppver.out']}, + provides = ['sgp4'], + ext_modules = ext_modules, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/__init__.py new/sgp4-2.25/sgp4/__init__.py --- old/sgp4-2.23/sgp4/__init__.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/sgp4/__init__.py 2025-08-04 19:52:36.000000000 +0200 @@ -13,9 +13,21 @@ .. _Fundamentals of Astrodynamics and Applications: https://celestrak.org/software/vallado-sw.php +What we today call ‘SGP4’ in fact merges together what in the 1970s were +originally two separate satellite propagation routines, but which are +now selected automatically: + +* SGP4 — the ‘Simplified General Perturbations' model is used for + satellites close enough to Earth that their orbit takes less than + 225 minutes (3 hours 45 minutes) to complete. + +* SDP4 — the ‘Simplified Deep Space Perturbations' model is used for + satellites farther from Earth, which take 225 minutes or longer to + compete an orbit. + If your machine can’t install or compile the C++ code, then this package -falls back to using a slower pure-Python implementation of SGP4. Tests -make sure that its positions **agree to within 0.1 mm** with the +falls back to using a slower pure-Python implementation of the library. +Tests make sure that its positions **agree to within 0.1 mm** with the standard version of the algorithm — an error far less than the 1–3 km/day by which satellites themselves deviate from the ideal orbits described in TLE files. @@ -78,7 +90,7 @@ be consumed by the whole part that specifies the day. The remaining digits will provide a precision for the fraction of around 20.1 µs. This should be no problem for the accuracy of your result — satellite - positions usually off by a few kilometers anyway, far less than a + positions are usually off by a few kilometers anyway, far less than a satellite moves in 20.1 µs — but if you run a solver that dives down into the microseconds while searching for a rising or setting time, the solver might be bothered by the 20.1 µs plateau between each jump @@ -157,23 +169,39 @@ Reading OMM data takes two steps, because OMM supports several different text formats. First, parse the input text to recover the field names and values that it stores; second, build a Python satellite object from -those field values. For example, to load OMM from XML: +those field values. The fastest and most efficient format is usually +CSV, since the column names are only given once, on the first line: ->>> with open('sample_omm.xml') as f: -... fields = next(omm.parse_xml(f)) ->>> sat = Satrec() ->>> omm.initialize(sat, fields) +>>> with open('sample_omm.csv') as f: +... for fields in omm.parse_csv(f): +... sat = Satrec() +... omm.initialize(sat, fields) + +The JSON format is more verbose because it repeats the field names over +again in every single record: + +>>> import json +>>> with open('sample_omm.json') as f: +... record_list = json.load(f) +>>> for fields in record_list: +... sat = Satrec() +... omm.initialize(sat, fields) -Or, to load OMM from CSV: +The most verbose format is XML: ->>> with open('sample_omm.csv') as f: -... fields = next(omm.parse_csv(f)) ->>> sat = Satrec() ->>> omm.initialize(sat, fields) +>>> with open('sample_omm.xml') as f: +... for fields in omm.parse_xml(f): +... sat = Satrec() +... omm.initialize(sat, fields) Either way, the satellite object should wind up properly initialized and ready to start producing positions. +The ``omm.initialize()`` routine uses the standard WGS72 gravity model +by default. To make your own choice of model, supply a third argument +that is one of the three constants ``WGS72OLD``, ``WGS72``, or ``WGS84`` +(which are shown below in the section on ‘Gravity’). + If you are interested in saving satellite parameters using the new OMM format, then read the section on “Export” below. @@ -394,17 +422,26 @@ ... 0.0, # ndot: ballistic coefficient (radians/minute^2) ... 0.0, # nddot: mean motion 2nd derivative (radians/minute^3) ... 0.0007417, # ecco: eccentricity -... 0.3083420829620822, # argpo: argument of perigee (radians) -... 0.9013560935706996, # inclo: inclination (radians) -... 1.4946964807494398, # mo: mean anomaly (radians) +... 0.3083420829620822, # argpo: argument of perigee (radians 0..2pi) +... 0.9013560935706996, # inclo: inclination (radians 0..pi) +... 1.4946964807494398, # mo: mean anomaly (radians 0..2pi) ... 0.06763602333248933, # no_kozai: mean motion (radians/minute) -... 3.686137125541276, # nodeo: R.A. of ascending node (radians) +... 3.686137125541276, # nodeo: R.A. of ascending node (radians 0..2pi) ... ) -These numbers don’t look the same as the numbers in the TLE, because the -underlying ``sgp4init()`` routine uses different units: radians rather -than degrees. But this is the same orbit and will produce the same -positions. +You might notice that these numbers don’t look the same as the numbers +in the TLE above. For example, the inclination was 51.6439 in the TLE, +but is 0.901356 here. That’s because ``sgp4init()`` uses different +units than the TLE format: angles are in radians rather than degrees. +But this is the same orbit and will produce the same positions. + +If you want to double-check that your elements are valid, you can run +the function ``check_satrec()``, which will raise a ``ValueError`` with +an informative error message if any of the angles are out of bounds. If +the satellite is fine, it simply returns, without raising an exception: + +>>> from sgp4.conveniences import check_satrec +>>> check_satrec(satellite2) Note that ``ndot`` and ``nddot`` are ignored by the SGP4 propagator, so you can leave them ``0.0`` without any effect on the resulting satellite @@ -460,11 +497,11 @@ | ``nddot`` — Second time derivative of the mean motion (loaded from the TLE, but otherwise ignored). | ``bstar`` — Ballistic drag coefficient B* (1/earth radii). -| ``inclo`` — Inclination (radians). -| ``nodeo`` — Right ascension of ascending node (radians). +| ``inclo`` — Inclination (radians 0 ≤ i < pi). +| ``nodeo`` — Right ascension of ascending node (radians 0 ≤ Ω < 2pi). | ``ecco`` — Eccentricity. -| ``argpo`` — Argument of perigee (radians). -| ``mo`` — Mean anomaly (radians). +| ``argpo`` — Argument of perigee (radians 0 ≤ ω < 2pi). +| ``mo`` — Mean anomaly (radians 0 ≤ M < 2pi). | ``no_kozai`` — Mean motion (radians/minute). | ``no`` — Alias for ``no_kozai``, for compatibility with old code. @@ -526,13 +563,13 @@ *Mean Elements From Most Recent Propagation* -Partway through each propagation, the SGP4 routine saves a set of -“singly averaged mean elements” that describe the orbit’s shape at the -moment for which a position is being computed. They are averaged with -respect to the mean anomaly and include the effects of secular gravity, -atmospheric drag, and — in Deep Space mode — of those pertubations from -the Sun and Moon that SGP4 averages over an entire revolution of each of -those bodies. They omit both the shorter-term and longer-term periodic +Each time the SGP4 routine generates a satellite position, it also saves +a set of “singly averaged mean elements” that describe shape of the +satellite's orbit at that moment. They are averaged with respect to the +mean anomaly and include the effects of secular gravity, atmospheric +drag, and — in Deep Space mode — of those pertubations from the Sun and +Moon that SGP4 averages over an entire revolution of each of those +bodies. They omit both the shorter-term and longer-term periodic pertubations from the Sun and Moon that SGP4 applies right before computing each position. @@ -620,6 +657,22 @@ Changelog --------- +Not yet released — 2.25 + +* Added a ``gravconst`` parameter to the ``omm.initialize()`` routine. + +2024-02-15 — 2.24 + +* The documentation now specifies the acceptable range for orbital + element angles like inclination and mean anomaly, and a new function + ``check_satrec(sat)`` will tell the caller if any of the angles are + out of bounds. + +* The documentation now gives an example of loading elements from JSON. + +* Tweaked the fallback Python code to accept TLE lines without a final + checksum character in the 69th column, to match the C++ code. + 2023-10-01 — 2.23 * Tweaked tests to resolve breakage introduced by Python 3.12. @@ -711,4 +764,4 @@ | 2012-08-27 — 1.0 — Initial release """ -__version__ = '2.23' +__version__ = '2.25' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/conveniences.py new/sgp4-2.25/sgp4/conveniences.py --- old/sgp4-2.23/sgp4/conveniences.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/sgp4/conveniences.py 2025-08-04 19:52:36.000000000 +0200 @@ -7,6 +7,7 @@ """ import datetime as dt import sgp4 +from math import pi from .functions import days2mdhms, jday class _UTC(dt.tzinfo): @@ -73,25 +74,51 @@ micro = int(fraction * 1e6) return dt.datetime(year, month, day, hour, minute, second, micro, UTC) -_ATTRIBUTES = None +_ATTRIBUTES = [] +_ATTR_MAXES = {} +_MAX_VALUES = {'2pi': 2*pi, 'pi': pi} + +def _load_attributes(): + for line in sgp4.__doc__.splitlines(): + if line.endswith('*'): + title = line.strip('*') + _ATTRIBUTES.append(title) + elif line.startswith('| ``'): + pieces = line.split('``') + name = pieces[1] + _ATTRIBUTES.append(name) + i = 2 + while pieces[i] == ', ': + another_name = pieces[i+1] + _ATTRIBUTES.append(another_name) + i += 2 + if '<' in line: + _ATTR_MAXES[name] = '2pi' if ('2pi' in line) else 'pi' + +def check_satrec(sat): + """Check whether satellite orbital elements are within range.""" + + if not _ATTRIBUTES: + _load_attributes() + + e = [] + + for name, max_name in sorted(_ATTR_MAXES.items()): + value = getattr(sat, name) + if 0.0 <= value < _MAX_VALUES[max_name]: + continue + e.append(' {0} = {1:f} is outside the range 0 <= {0} < {2}\n' + .format(name, value, max_name)) + + if e: + raise ValueError('satellite parameters out of range:\n' + '\n'.join(e)) + def dump_satrec(sat, sat2=None): """Yield lines that list the attributes of one or two satellites.""" - global _ATTRIBUTES - if _ATTRIBUTES is None: - _ATTRIBUTES = [] - for line in sgp4.__doc__.splitlines(): - if line.endswith('*'): - title = line.strip('*') - _ATTRIBUTES.append(title) - elif line.startswith('| ``'): - pieces = line.split('``') - _ATTRIBUTES.append(pieces[1]) - i = 2 - while pieces[i] == ', ': - _ATTRIBUTES.append(pieces[i+1]) - i += 2 + if not _ATTRIBUTES: + _load_attributes() for item in _ATTRIBUTES: if item[0].isupper(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/io.py new/sgp4-2.25/sgp4/io.py --- old/sgp4-2.23/sgp4/io.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/sgp4/io.py 2025-08-04 19:52:36.000000000 +0200 @@ -166,7 +166,7 @@ line = longstr2.rstrip() - if (len(line) >= 69 and + if (len(line) >= 68 and line.startswith('2 ') and line[7] == ' ' and line[11] == '.' and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/model.py new/sgp4-2.25/sgp4/model.py --- old/sgp4-2.23/sgp4/model.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/sgp4/model.py 2025-08-04 19:52:36.000000000 +0200 @@ -42,6 +42,9 @@ array = None # replaced, if needed, with NumPy array() + def __init__(self): + self.revnum = 0 # for consistency, since sgp4init() leaves this unset + @property def no(self): return self.no_kozai diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/omm.py new/sgp4-2.25/sgp4/omm.py --- old/sgp4-2.23/sgp4/omm.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/sgp4/omm.py 2025-08-04 19:52:36.000000000 +0200 @@ -1,4 +1,4 @@ -"""Support for the new Orbit Mean-Elements Message format for TLE data.""" +"""Support for Orbit Mean-Elements Message (OMM) orbital elements format.""" import csv import xml.etree.ElementTree as ET @@ -26,7 +26,7 @@ _ndot_units = 1036800.0 / pi # See SGP4.cpp for details. _nddot_units = 2985984000.0 / 2.0 / pi # See SGP4.cpp for details. -def initialize(sat, fields): +def initialize(sat, fields, gravconst=WGS72): sat.classification = fields['CLASSIFICATION_TYPE'] sat.intldesg = fields['OBJECT_ID'][2:].replace('-', '') sat.ephtype = int(fields['EPHEMERIS_TYPE']) @@ -47,5 +47,5 @@ nodeo = float(fields['RA_OF_ASC_NODE']) * _to_radians satnum = int(fields['NORAD_CAT_ID']) - sat.sgp4init(WGS72, 'i', satnum, epoch, bstar, ndot, nddot, ecco, + sat.sgp4init(gravconst, 'i', satnum, epoch, bstar, ndot, nddot, ecco, argpo, inclo, mo, no_kozai, nodeo) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/sample_omm.json new/sgp4-2.25/sgp4/sample_omm.json --- old/sgp4-2.23/sgp4/sample_omm.json 1970-01-01 01:00:00.000000000 +0100 +++ new/sgp4-2.25/sgp4/sample_omm.json 2025-08-04 19:52:36.000000000 +0200 @@ -0,0 +1,19 @@ +[{ + "OBJECT_NAME": "VANGUARD 1", + "OBJECT_ID": "1958-002B", + "EPOCH": "2025-02-14T14:36:48.662784", + "MEAN_MOTION": 10.85873516, + "ECCENTRICITY": 0.1841322, + "INCLINATION": 34.2493, + "RA_OF_ASC_NODE": 19.2327, + "ARG_OF_PERICENTER": 100.1057, + "MEAN_ANOMALY": 281.1229, + "EPHEMERIS_TYPE": 0, + "CLASSIFICATION_TYPE": "U", + "NORAD_CAT_ID": 5, + "ELEMENT_SET_NO": 999, + "REV_AT_EPOCH": 39027, + "BSTAR": 0.00035436, + "MEAN_MOTION_DOT": 2.64e-6, + "MEAN_MOTION_DDOT": 0 +}] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4/tests.py new/sgp4-2.25/sgp4/tests.py --- old/sgp4-2.23/sgp4/tests.py 2023-11-11 14:24:31.000000000 +0100 +++ new/sgp4-2.25/sgp4/tests.py 2025-08-04 19:52:36.000000000 +0200 @@ -1,9 +1,9 @@ """Test suite for SGP4.""" try: - from unittest2 import TestCase, main -except: - from unittest import TestCase, main + from unittest2 import TestCase, SkipTest, main +except ImportError: + from unittest import TestCase, SkipTest, main import datetime as dt import re @@ -18,8 +18,6 @@ except ImportError: from StringIO import StringIO -import numpy as np - from sgp4.api import WGS72OLD, WGS72, WGS84, Satrec, jday from sgp4.earth_gravity import wgs72 from sgp4.ext import invjday, newtonnu, rv2coe @@ -72,6 +70,10 @@ if sys.version_info[:2] == (2, 7) or sys.version_info[:2] == (2, 6): TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp +def skip_under_python2(): + if sys.version_info[0] < 3: + raise SkipTest('feature not supported under Python 2') + # ------------------------------------------------------------------------ # Core Attributes # @@ -118,6 +120,10 @@ # Test array API def test_whether_array_logic_writes_nan_values_to_correct_row(): + skip_under_python2() + + import numpy as np + # https://github.com/brandon-rhodes/python-sgp4/issues/87 l1 = "1 44160U 19006AX 20162.79712247 +.00816806 +19088-3 +34711-2 0 9997" l2 = "2 44160 095.2472 272.0808 0216413 032.6694 328.7739 15.58006382062511" @@ -170,6 +176,16 @@ zone = conveniences.UTC assertEqual(datetime, dt.datetime(2000, 6, 27, 18, 50, 19, 733568, zone)) +def test_check_satrec(): + sat = Satrec.twoline2rv(LINE1, LINE2) + conveniences.check_satrec(sat) # should succeed with no exception + + sat = Satrec.twoline2rv(LINE1, LINE2.replace('34.2682', '-4.2682')) + msg = ('satellite parameters out of range:\n' + ' inclo = -0.074494 is outside the range 0 <= inclo < pi') + with assertRaisesRegex(ValueError, msg): + conveniences.check_satrec(sat) + def test_good_tle_checksum(): for line in LINE1, LINE2: checksum = int(line[-1]) @@ -233,6 +249,13 @@ assertEqual(actual_line2, line2) assertEqual(actual_line2_old, line2) +def test_export_tle_works_with_sgp4init(): + # Does sgp4init() build a sat that can successfully export? + sat = Satrec() + args = sgp4init_args(VANGUARD_ATTRS) + sat.sgp4init(WGS72, 'i', VANGUARD_ATTRS['satnum'], VANGUARD_EPOCH, *args) + export_tle(sat) + def test_export_tle_raises_error_for_out_of_range_angles(): # See https://github.com/brandon-rhodes/python-sgp4/issues/70 for angle in 'inclo', 'nodeo', 'argpo', 'mo': @@ -386,6 +409,12 @@ ) assertEqual(sat.epochyr, 99) +def test_tle_that_lacks_checksums(): + Satrec.twoline2rv( + '1 00058U 60013A 97142.85906518 .00000093 00000-0 +10762-4 0 274', + '2 00058 028.3286 356.4726 0164991 158.6392 202.1128 13.4602145880282', + ) + def test_setters(): sat = Satrec() @@ -818,6 +847,18 @@ value2 = getattr(sat2, attr) assertEqual(value1, value2, '%s %r != %r' % (attr, value1, value2)) +def test_omm_uses_gravconst_parameter(): + fields = next(omm.parse_csv(StringIO(MARIO_CSV))) + + sat1 = Satrec() + omm.initialize(sat1, fields, WGS72) + + sat2 = Satrec() + omm.initialize(sat2, fields, WGS84) + + assertEqual(sat1.mu, 398600.8) + assertEqual(sat2.mu, 398600.5) + # Live example of OMM: # https://celestrak.com/NORAD/elements/gp.php?INTDES=2020-025&FORMAT=JSON-PRETTY @@ -858,25 +899,25 @@ from sgp4.wulfgar import add_test_functions add_test_functions(loader, tests, __name__) - # Python 2.6 formats floating-point numbers a bit differently and - # breaks the doctest, so we only run the doctest on later versions. - if sys.version_info >= (2, 7): - - def setUp(suite): - suite.olddir = os.getcwd() - os.chdir(os.path.dirname(__file__)) - suite.oldaccel = api.accelerated - api.accelerated = True # so doctest passes under 2.7 - def tearDown(suite): - os.chdir(suite.olddir) - api.accelerated = suite.oldaccel - - options = dict(optionflags=ELLIPSIS, setUp=setUp, tearDown=tearDown) - tests.addTests(DocTestSuite('sgp4', **options)) - tests.addTests(DocTestSuite('sgp4.conveniences', **options)) - tests.addTests(DocTestSuite('sgp4.functions', **options)) + if sys.version_info[0] > 2: + load_doctests(tests) return tests +def load_doctests(tests): + def setUp(suite): + suite.olddir = os.getcwd() + os.chdir(os.path.dirname(__file__)) + suite.oldaccel = api.accelerated + api.accelerated = True # so doctests pass even if compilation failed + def tearDown(suite): + os.chdir(suite.olddir) + api.accelerated = suite.oldaccel + + options = dict(optionflags=ELLIPSIS, setUp=setUp, tearDown=tearDown) + tests.addTests(DocTestSuite('sgp4', **options)) + tests.addTests(DocTestSuite('sgp4.conveniences', **options)) + tests.addTests(DocTestSuite('sgp4.functions', **options)) + if __name__ == '__main__': main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4.egg-info/PKG-INFO new/sgp4-2.25/sgp4.egg-info/PKG-INFO --- old/sgp4-2.23/sgp4.egg-info/PKG-INFO 2023-11-11 14:24:45.000000000 +0100 +++ new/sgp4-2.25/sgp4.egg-info/PKG-INFO 2025-08-04 19:52:45.000000000 +0200 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: sgp4 -Version: 2.23 +Version: 2.25 Summary: Track Earth satellites given TLE data, using up-to-date 2020 SGP4 routines. Home-page: https://github.com/brandon-rhodes/python-sgp4 Author: Brandon Rhodes @@ -10,19 +10,28 @@ Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Scientific/Engineering :: Astronomy +Provides: sgp4 +Description-Content-Type: text/x-rst License-File: LICENSE - +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: provides +Dynamic: summary This package compiles the official C++ code from `Revisiting Spacetrack Report #3`_ (AIAA 2006-6753) — specifically, the 2023 May 09 release @@ -36,9 +45,21 @@ .. _Fundamentals of Astrodynamics and Applications: https://celestrak.org/software/vallado-sw.php +What we today call ‘SGP4’ in fact merges together what in the 1970s were +originally two separate satellite propagation routines, but which are +now selected automatically: + +* SGP4 — the ‘Simplified General Perturbations' model is used for + satellites close enough to Earth that their orbit takes less than + 225 minutes (3 hours 45 minutes) to complete. + +* SDP4 — the ‘Simplified Deep Space Perturbations' model is used for + satellites farther from Earth, which take 225 minutes or longer to + compete an orbit. + If your machine can’t install or compile the C++ code, then this package -falls back to using a slower pure-Python implementation of SGP4. Tests -make sure that its positions **agree to within 0.1 mm** with the +falls back to using a slower pure-Python implementation of the library. +Tests make sure that its positions **agree to within 0.1 mm** with the standard version of the algorithm — an error far less than the 1–3 km/day by which satellites themselves deviate from the ideal orbits described in TLE files. @@ -101,7 +122,7 @@ be consumed by the whole part that specifies the day. The remaining digits will provide a precision for the fraction of around 20.1 µs. This should be no problem for the accuracy of your result — satellite - positions usually off by a few kilometers anyway, far less than a + positions are usually off by a few kilometers anyway, far less than a satellite moves in 20.1 µs — but if you run a solver that dives down into the microseconds while searching for a rising or setting time, the solver might be bothered by the 20.1 µs plateau between each jump @@ -180,23 +201,39 @@ Reading OMM data takes two steps, because OMM supports several different text formats. First, parse the input text to recover the field names and values that it stores; second, build a Python satellite object from -those field values. For example, to load OMM from XML: +those field values. The fastest and most efficient format is usually +CSV, since the column names are only given once, on the first line: ->>> with open('sample_omm.xml') as f: -... fields = next(omm.parse_xml(f)) ->>> sat = Satrec() ->>> omm.initialize(sat, fields) +>>> with open('sample_omm.csv') as f: +... for fields in omm.parse_csv(f): +... sat = Satrec() +... omm.initialize(sat, fields) + +The JSON format is more verbose because it repeats the field names over +again in every single record: + +>>> import json +>>> with open('sample_omm.json') as f: +... record_list = json.load(f) +>>> for fields in record_list: +... sat = Satrec() +... omm.initialize(sat, fields) -Or, to load OMM from CSV: +The most verbose format is XML: ->>> with open('sample_omm.csv') as f: -... fields = next(omm.parse_csv(f)) ->>> sat = Satrec() ->>> omm.initialize(sat, fields) +>>> with open('sample_omm.xml') as f: +... for fields in omm.parse_xml(f): +... sat = Satrec() +... omm.initialize(sat, fields) Either way, the satellite object should wind up properly initialized and ready to start producing positions. +The ``omm.initialize()`` routine uses the standard WGS72 gravity model +by default. To make your own choice of model, supply a third argument +that is one of the three constants ``WGS72OLD``, ``WGS72``, or ``WGS84`` +(which are shown below in the section on ‘Gravity’). + If you are interested in saving satellite parameters using the new OMM format, then read the section on “Export” below. @@ -417,17 +454,26 @@ ... 0.0, # ndot: ballistic coefficient (radians/minute^2) ... 0.0, # nddot: mean motion 2nd derivative (radians/minute^3) ... 0.0007417, # ecco: eccentricity -... 0.3083420829620822, # argpo: argument of perigee (radians) -... 0.9013560935706996, # inclo: inclination (radians) -... 1.4946964807494398, # mo: mean anomaly (radians) +... 0.3083420829620822, # argpo: argument of perigee (radians 0..2pi) +... 0.9013560935706996, # inclo: inclination (radians 0..pi) +... 1.4946964807494398, # mo: mean anomaly (radians 0..2pi) ... 0.06763602333248933, # no_kozai: mean motion (radians/minute) -... 3.686137125541276, # nodeo: R.A. of ascending node (radians) +... 3.686137125541276, # nodeo: R.A. of ascending node (radians 0..2pi) ... ) -These numbers don’t look the same as the numbers in the TLE, because the -underlying ``sgp4init()`` routine uses different units: radians rather -than degrees. But this is the same orbit and will produce the same -positions. +You might notice that these numbers don’t look the same as the numbers +in the TLE above. For example, the inclination was 51.6439 in the TLE, +but is 0.901356 here. That’s because ``sgp4init()`` uses different +units than the TLE format: angles are in radians rather than degrees. +But this is the same orbit and will produce the same positions. + +If you want to double-check that your elements are valid, you can run +the function ``check_satrec()``, which will raise a ``ValueError`` with +an informative error message if any of the angles are out of bounds. If +the satellite is fine, it simply returns, without raising an exception: + +>>> from sgp4.conveniences import check_satrec +>>> check_satrec(satellite2) Note that ``ndot`` and ``nddot`` are ignored by the SGP4 propagator, so you can leave them ``0.0`` without any effect on the resulting satellite @@ -483,11 +529,11 @@ | ``nddot`` — Second time derivative of the mean motion (loaded from the TLE, but otherwise ignored). | ``bstar`` — Ballistic drag coefficient B* (1/earth radii). -| ``inclo`` — Inclination (radians). -| ``nodeo`` — Right ascension of ascending node (radians). +| ``inclo`` — Inclination (radians 0 ≤ i < pi). +| ``nodeo`` — Right ascension of ascending node (radians 0 ≤ Ω < 2pi). | ``ecco`` — Eccentricity. -| ``argpo`` — Argument of perigee (radians). -| ``mo`` — Mean anomaly (radians). +| ``argpo`` — Argument of perigee (radians 0 ≤ ω < 2pi). +| ``mo`` — Mean anomaly (radians 0 ≤ M < 2pi). | ``no_kozai`` — Mean motion (radians/minute). | ``no`` — Alias for ``no_kozai``, for compatibility with old code. @@ -549,13 +595,13 @@ *Mean Elements From Most Recent Propagation* -Partway through each propagation, the SGP4 routine saves a set of -“singly averaged mean elements” that describe the orbit’s shape at the -moment for which a position is being computed. They are averaged with -respect to the mean anomaly and include the effects of secular gravity, -atmospheric drag, and — in Deep Space mode — of those pertubations from -the Sun and Moon that SGP4 averages over an entire revolution of each of -those bodies. They omit both the shorter-term and longer-term periodic +Each time the SGP4 routine generates a satellite position, it also saves +a set of “singly averaged mean elements” that describe shape of the +satellite's orbit at that moment. They are averaged with respect to the +mean anomaly and include the effects of secular gravity, atmospheric +drag, and — in Deep Space mode — of those pertubations from the Sun and +Moon that SGP4 averages over an entire revolution of each of those +bodies. They omit both the shorter-term and longer-term periodic pertubations from the Sun and Moon that SGP4 applies right before computing each position. @@ -643,6 +689,22 @@ Changelog --------- +Not yet released — 2.25 + +* Added a ``gravconst`` parameter to the ``omm.initialize()`` routine. + +2024-02-15 — 2.24 + +* The documentation now specifies the acceptable range for orbital + element angles like inclination and mean anomaly, and a new function + ``check_satrec(sat)`` will tell the caller if any of the angles are + out of bounds. + +* The documentation now gives an example of loading elements from JSON. + +* Tweaked the fallback Python code to accept TLE lines without a final + checksum character in the 69th column, to match the C++ code. + 2023-10-01 — 2.23 * Tweaked tests to resolve breakage introduced by Python 3.12. @@ -732,4 +794,3 @@ | 2013-11-29 — 1.2 — Made ``epochyr`` 4 digits; add ``datetime`` for ``.epoch`` | 2012-11-22 — 1.1 — Python 3 compatibility; more documentation | 2012-08-27 — 1.0 — Initial release - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sgp4-2.23/sgp4.egg-info/SOURCES.txt new/sgp4-2.25/sgp4.egg-info/SOURCES.txt --- old/sgp4-2.23/sgp4.egg-info/SOURCES.txt 2023-11-11 14:24:45.000000000 +0100 +++ new/sgp4-2.25/sgp4.egg-info/SOURCES.txt 2025-08-04 19:52:45.000000000 +0200 @@ -19,6 +19,7 @@ sgp4/omm.py sgp4/propagation.py sgp4/sample_omm.csv +sgp4/sample_omm.json sgp4/sample_omm.xml sgp4/tcppver.out sgp4/tests.py
