PF research library · 8 min read
Tracking model and coordinate conventions
r34 documentation · satellite-tracking.md · Formatted reading copy
The optional tracking provider reads CelesTrak general-perturbation (GP) orbital elements, propagates them with SGP4 using WGS72 constants, and transforms each complete Cartesian state from TEME into GCRS or ITRS using Astropy. Velocity is attached as a Cartesian differential, so a rotating-frame conversion includes the frame-motion contribution. Velocity is never transformed as a position.
These are ephemeris predictions from published orbital elements, not streaming telemetry or a measured satellite trajectory. Their differences from an ideal simulation do not establish the accuracy of either trajectory. No learned model or fitted synchronization offset is used to reduce the reported differences.
Data and time policy
The provider defaults to CelesTrak's JSON GP representation, which supports catalog identifiers beyond the legacy five-digit TLE limit. TLE input remains available. TLE checksums, identifiers, format and OMM conventions are validated. The exact response, source URL, retrieval UTC, and SHA-256 hash are cached.
The default element-age limit is seven days in either direction from the
requested propagation epoch. This is a configurable operational cutoff, not a
seven-day guarantee of orbital accuracy. A labeled allow_stale override permits
retrospective sensitivity tests. HTTP failures do not generate synthetic data or
silently substitute stale records. Offline mode requires a valid existing cache.
Records are refreshed after two hours by default; the live runner controls its
own iteration times and does not need a network request for every snapshot.
Every epoch must have an explicit UTC interpretation. In SR the worldline argument uses uniform TAI/TT-rate seconds; in static GR it uses TCG coordinate seconds. Astropy constructs time increments separately and both Julian-date components are passed to SGP4, avoiding the approximately 20-microsecond steps introduced by collapsing modern Julian dates into one 64-bit float. This improves numerical time resolution; it does not confer nanosecond physical accuracy on GP data.
Earth-orientation data are required for frame conversions. The provider records
the table, prediction statuses and permitted degradation. Dates outside the IERS
table are rejected unless allow_degraded_iers is explicitly selected. Live mode
allows Astropy to obtain current IERS data; offline tests disable downloads.
When comparison.iers_file names a local IERS-A table, all three CLI runners
pin that table during their calculations and disable automatic Earth-orientation
table replacement for that context. Tracking coverage checks still apply.
Leap-second data are checked separately from Earth orientation. Before any
propagation age calculation or UTC/TAI/TCG conversion, the provider explicitly
loads an IERS/ntp leap-second list and updates ERFA's conversion table. Online
runs use Astropy's automatic refresh under tracking.iers_auto_download.
tracking.offline: true disables both orbital-element and timing-data downloads.
This refresh updates data only; Python 3.8 and the pinned packages are retained.
If automatic lookup raises an error or returns a table that does not cover the
requested epoch, the provider tries timing_data/Leap_Second.dat, resolved
relative to the installed module rather than the working directory. That file
is an unmodified IERS Bulletin C72 list, valid before 2027-06-28, with a source URL
and raw SHA256 recorded in timing_data/PROVENANCE.json. The provider uses it only
when it covers the requested epoch. It reports the fallback once and records
fallback_used, fallback_reason, selection and refresh_attempt in the
tracking provenance. A covered in-memory table can be reused on subsequent live
updates without repeating the message.
Astropy 5.2 may hide individual URL failures while returning an expired table. The recorded outcome therefore distinguishes an expired return from an exception; it does not claim to identify a particular certificate, proxy or server failure. Superseded stale warnings are retained in JSON diagnostics instead of being presented as unresolved failures after a valid replacement. Other warnings remain visible. Astropy's first lazy UTC conversion is restricted to the verified local state, so it does not perform a second unnecessary network refresh or undo the selected table's verified expiry. Missing, corrupt or expired fallback data never silently extend coverage. Satellite GP and Earth-orientation refresh remain independent and obey their existing online/offline policies.
For a network-independent run, set tracking.leap_second_file to the local path
of a current official IERS Leap_Second.dat or ntp leap-seconds.list file.
In CLI configuration files, this path and tracking.cache_dir are relative to
the configuration file's directory, as is comparison.iers_file. Absolute
paths are retained. An omitted cache path becomes tracking_cache in the
configuration directory. These rules apply to the scenario runner, matched-state
comparison runner and legacy standalone comparison CLI. Direct Python provider
calls receive paths as supplied; use execution_config.read_config() when
loading the same portable JSON from an application. CLI output paths remain
relative to the working directory. The automatic bundled leap-list fallback
above continues to use the installed module's directory.
The supplied Windows batch launchers change to the package directory before
starting Python; direct Python invocations retain the caller's working directory.
Download the IERS file from
https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat if the cached or bundled list
is too old. An explicit local file is used as supplied and never silently
replaced by another list.
The requested UTC epoch must precede the list's expiration date; an uncovered
epoch stops with a refresh instruction. A covered historical epoch can still
be replayed when the list is expired today, with that fact recorded and warnings
retained. allow_degraded_iers does not waive leap-second coverage. A propagated
worldline cannot silently continue beyond the verified leap-second validity
limit. The provider records leap_seconds provenance separately from
earth_orientation: source, expiration, historical coverage, whether the list
is expired today, effective table hashes, ERFA updates, and the applied TAI−UTC
offset. Refresh failures never invent an unannounced leap second.
Optional static-GR chart adapter
GCRS Cartesian coordinates are approximately identified with Earth-monopole isotropic coordinates. The radial mapping to the Schwarzschild areal chart is
r = rho * (1 + GM / (2*c*c*rho))**2.
The spatial direction is retained and velocities use the full Jacobian of that
map. For Earth, the leading areal-minus-isotropic radial difference is GM/c^2,
approximately 4.4 millimetres. The algebraic radial transformation is exact for
the Schwarzschild metric; identifying full GCRS coordinates with that monopole
chart is an approximation. TCG is identified with the isolated-monopole
Schwarzschild coordinate time. The exact conventional rate relation
dTT/dTCG = 1 - L_G, with L_G = 6.969290134e-10, is applied using Astropy's
explicit TimeDelta(scale="tcg"); velocities receive the same chain-rule
factor before the spatial Jacobian. Multipoles, external tidal fields and a full
relativistic ephemeris-to-metric conversion are excluded. SGP4 trajectories are prescribed
worldlines in the photon model, not Schwarzschild geodesic orbits. Static-GR
photon propagation does not turn SGP4 into a GR orbit integrator.
Numerical validation
The optional tracking tests cover the published Vallado verification case at zero and 360 minutes, TLE/OMM consistency, sub-microsecond propagation steps, position/velocity transformations and inverse transformations, the rotational velocity of an Earth-fixed point, the radial-coordinate Jacobian, and explicit handling of unavailable, invalid and stale records. They also check that comparisons reject mixed frames or epochs.
The reference TLEs are historical numerical test data and never serve as an automatic live-mode fallback. Numerical agreement with the reference propagator validates the implementation of that propagator, not satellite prediction error.
The original 26 tracking tests are retained, using SGP4 2.24. Current full and
tracking-only logs are in validation_logs.
The Python 3.8 Linux Astropy table extension emitted a NumPy structure-size
runtime warning; numerical checks passed and the warning is retained in the log.
The deliberately out-of-range IERS test also emits ERFA's expected dubious-year
warning.
The September 16 execution-audit corrections add 11 regression cases for explicit local lists, online refresh policy, expiry rejection, covered historical replay, offline download prevention, timing provenance, and propagation beyond a list's validity, leap-second-day offsets, and pre-1972 UTC drift. All 37 tracking tests passed on Python 3.8.20 with Astropy 5.2.2 and Python 3.12.14 with Astropy 7.1.1. The automatic-fallback follow-up adds 11 further tracking regressions; all 48 pass on both interpreters. The current full-suite log has 392 passing tests. The synthetic list expirations used in policy tests are test fixtures only and are never an operational timing-data source.
The tracking_fixtures/capture_20260916 directory contains actual HTTP GP
responses retrieved on September 16, 2026 for NORAD 25544 (ISS) and 42829
(TECHNOSAT), their unmodified provenance, and a GCRS replay of those two cached
objects. The simultaneous attempt to obtain NORAD 44479 received HTTP proxy 403;
the three-object provider correctly failed. A subsequent, explicitly selected
public example obtained NORAD 20580 (HST), and the three-object ISS/TECHNOSAT/HST
provider completed at 2026-09-16T17:13:22.933650Z. All three element ages were
below 0.60 day and IERS data were in-range predictions. The earlier failure is
preserved; no automatic substitute satellite was selected. The
original upload's comment calling 42829 Galileo was incorrect; object names in
the new output come from the published response. The three-object data were
also exercised through the simulation engine with an explicit zero boost:
the SR collection was Earth-blocked and static-GR propagation reported
occultation, each giving zero accepted pairs. These are successful integration
checks with honest unavailable-link outcomes, not an optical-link demonstration.
The comparison module is also an executable command:
python real_time_satellite_comparison.py --config config_tracking_replay.json --steps 1
It compares configured ideal circular trajectories with same-epoch GCRS
predictions, without fitting the initial conditions. --duration requests
future predictions; it does not wait in real time. Use the top-level
config_tracking_replay.json for the supplied fixed-epoch example. Nested
configuration records in tracking_fixtures/ preserve the original capture
and may contain historical paths; they are evidence, not current CLI presets.
To recreate a particular historical timestamp, use the top-level preset with
--epoch here, or --epoch-utc in the scenario runner, together with elements
and timing tables that cover that timestamp.
Primary sources
- Astropy satellite-state construction and TEME frame conventions: https://docs.astropy.org/en/stable/coordinates/satellites.html
- SGP4 package documentation, WGS72 convention, split Julian dates, and stated limitations of TLE predictions: https://pypi.org/project/sgp4/2.24/
- Vallado et al., Revisiting Spacetrack Report #3, AIAA 2006-6753, reference implementation and verification data: https://celestrak.org/publications/AIAA/2006-6753/
- CelesTrak GP representations and omitted default OMM metadata: https://celestrak.org/NORAD/documentation/gp-data-formats.php
- IERS Conventions 2010, Chapter 10, TCG/TT relation and geocentric coordinates: https://iers-conventions.obspm.fr/content/chapter10/tn36_c10.pdf
- Astropy explicit time-interval scales: https://docs.astropy.org/en/stable/api/astropy.time.TimeDelta.html
- Astropy leap-second file selection, validity and ERFA updates: https://docs.astropy.org/en/stable/api/astropy.utils.iers.LeapSeconds.html