PF research library · 8 min read

Tracking model and coordinate conventions

r34 documentation · satellite-tracking.md

On this page
  1. Data and time policy
  2. Optional static-GR chart adapter
  3. Numerical validation
  4. Primary sources

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