Replace Space-Track with IAU CPS SatChecker for orbit records - #44
Open
chrisfinlay wants to merge 1 commit into
Open
Replace Space-Track with IAU CPS SatChecker for orbit records#44chrisfinlay wants to merge 1 commit into
chrisfinlay wants to merge 1 commit into
Conversation
Vendors tabascal/satchecker/ from epfl-radio-astro/tabascal#92 and rebuilds tabsim's satellite retrieval on top of it. No account or credentials are required and the spacetrack dependency is removed. Both orbital-data formats SatChecker serves are handled: TLEs for epochs before its 2026-07-12 handover, and OMM records after it. An OMM record has no lines to parse, so its elements go straight into an sgp4.Satrec via sgp4init; both kinds end up as the same propagator over the same model. Retrieval gains per-satellite source precedence (extra_orbit_dir, managed cache, SatChecker), a validated per-NORAD cache, an age policy, and a reproducible used_orbits.json written into each simulation's input_data. Satellite names resolve through SatChecker's substring search rather than Space-Track's LIKE query, preserving the old semantics including the upper-casing tabsim always applied. Removes spacetrack_path, tle_dir, the tabsim-setup-spacetrack entry point, the shipped Space-Track TLE cache, and the Space-Track credentials from CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces tabsim's Space-Track TLE retrieval with the credential-free
IAU CPS SatChecker service, by vendoring
tabascal/satchecker/from epfl-radio-astro/tabascal#92and rebuilding tabsim's satellite selection on top of it.
That PR explicitly left this out of scope — "Generating a simulation with
sim-visfrom the separately installed
tabsimstill uses Space-Track; that is out of scopehere." This is that follow-up.
Both orbital-data formats SatChecker serves are supported: TLEs for epochs before its
2026-07-12 handover, and OMM (Orbit Mean-Elements Message) records after it. No account
or credentials are required, and the
spacetrackdependency is removed.The vendored package
tabsim/satchecker/is a direct copy oftabascal/satchecker/at commitbd51a11, not a reimplementation, so the two stay diffable and a fix on either sidetransplants without a merge. The deviations are listed in full in its
__init__.py:USER_AGENTcontact URL;read_legacy_tle_recordspassesprecise_float=Truetopandas.read_json.(2) is a real bug, present upstream too, and worth sending back. pandas' default JSON
float parser is not correctly rounded: it reads
0.0066635back as0.006663499999999999, a different double. For an OMM record that is theeccentricity, so a run replayed from its own
used_orbits.jsonpropagated to ameasurably different trajectory than the run that wrote it. Found by asserting exact
equality on a real end-to-end replay rather than
approx; see Verification.tabsim-specific additions live outside the vendored package, so it stays a clean copy.
Today that is one module,
tabsim/satchecker_names.py.What tabsim needed that TABASCAL did not
TABASCAL resolves a fixed list of NORAD IDs at one epoch. tabsim additionally
searches: it takes a candidate set (IDs and/or names), propagates each one, and
keeps the satellites that actually pass near the target. Two consequences shaped this PR.
Names
tabsim's
sat_nameshas no TABASCAL counterpart. SatChecker offers two name endpointsand only one of them is right here:
navstarnorad-ids-from-namesearch-satellitesNAVSTAR)Space-Track's
op.like(name)wraps the pattern in wildcards, so it was a substringsearch, and tabsim always passed
name.upper().search-satellitesplus the sameupper-casing reproduces that exactly. The exact-name index would silently have reduced
sat_names: [navstar]— which every shipped example config uses — to nothing at all,and the simulation would have run to completion with no satellites and no error. This
was caught end-to-end rather than by unit test, and the reasoning is written into the
module so it does not recur.
Decayed objects are dropped: they cannot be observed, have no record near any
present-day epoch, and would otherwise turn every re-entered namesake into a coverage
failure.
Coverage: numbered vs named satellites
TABASCAL's rule is that every configured satellite must resolve or the run stops.
Applied unchanged here it would be wrong for names, so the two are separated:
norad_ids,norad_ids_path) keep the strict rule. The usernamed these individually; one dropped for want of a record is indistinguishable from
one that simply never passed the target, which is exactly the silent failure the rule
exists to prevent.
OrbitErrornames each failure, how close the best availablerecord was, and the remedies.
no satellite for a record to be missing for — reported, not fatal. A named satellite
whose record cannot be obtained is reported and excluded.
Retrieval behaviour
Unchanged from #92, since it is the same code. Each NORAD ID resolves independently:
extra_orbit_dir→ managed per-NORAD cache → SatChecker, with endpoint selectionagainst the handover date, failover to the other archive when the first yields nothing
acceptable, bounded concurrency, outage handling, and validated caching.
tabsim/orbit.pyis ported fromtabascal/orbit.pyless the multi-process broadcastand the Measurement Set preflight — tabsim's simulation is single-process and builds its
own time grid — plus
resolve_names.tabsim/orbit_config.pyis ported fromtabascal/orbit_config.pyless the MS epoch derivation and model-componentintrospection, which tabsim has no equivalent of.
Propagation
FixedOrbit-style propagation builds a SkyfieldEarthSatellitefrom two TLE lines, soan OMM record could not be propagated at all — post-handover simulations would fail on
the default satellite component. An OMM's elements now go straight into an
sgp4.Satrecvia
sgp4init, wrapped byEarthSatellite.from_satrec. The TLE path is untouched.ndot/nddotare passed as zero — SGP4 models drag throughbstaralone and neverreads them while propagating.
Output schema
rfi_tle_sat_orbitis a fixed-width string array of the two TLE lines. An OMM recordhas none — its elements are the record — so those rows are written empty rather than
filled with something that looks like a TLE and is not one. The zarr/MS schema is
otherwise unchanged.
The complete record goes to
input_data/used_orbits.jsoninstead, inextra_orbit_dirformat. Pointing a later run'sextra_orbit_dirat that directoryreproduces the run's satellite trajectories exactly, independently of the shared cache,
of the age ceiling, and of what SatChecker serves by then. It is written by hand rather
than with
DataFrame.to_json, which formats floats to a fixed number of decimal places— the default 10 rounds an OMM element outright, and even the maximum 15 writes
0.0066635as0.006663499999999999.json.dumpwrites a float throughrepr, theshortest representation that reads back identically.
Configuration
Added under
rfi_sources.tle_satellite:extra_orbit_dirnullextra_orbit_max_age_daysnullnull= unlimited, keeping exact replay workingremote_max_age_days3cache_reuse_max_age_days1Also:
ORBIT_CACHE_DIRrelocates the managed cache, andsim-vis --extra_orbit_dir(
-eod) setsextra_orbit_dirat runtime. A path given on the command line resolvesagainst the working directory; one in the config resolves against the config, like every
other path there.
Removed:
spacetrack_path,tle_dir, the-st/--spacetrackflags onsim-visandtle-region, thetabsim-setup-spacetrackentry point and its script, the shippedSpace-Track TLE cache under
tabsim/data/rfi/tles/, the Space-Track credentials step inCI, and the
spacetrackdependency.sgp4andplatformdirsare added;pandasispromoted from transitive to declared, since it is imported directly.
tabsim-import-tlesnow writes to./orbitsby default rather than into the installedpackage, and its output is documented as an
extra_orbit_dir— which is what it alwaysproduced, but there was no longer anything reading the old location.
norad_ids_pathis read byread_norad_ids_filerather thannp.loadtxt, which reportsthe offending file and line number on a typo. It still takes the first column, so a
file pairing IDs with a name or note reads as before.
Verification
tests/test_orbit.pycoveringsource precedence, the age policy, endpoint failover, outage handling, coverage
errors, replay, configuration validation, name lookup, and both propagation paths.
Everything in it runs offline.
same TLE it is compared against, so a degrees-for-radians or rev/day-for-rad/min slip
would show up as kilometres. Measured max separation over 12 h on a 6800 km orbit:
0.11 m, and a field-by-field
Satreccomparison shows every orbital elementtransfers exactly — the residual is entirely the OMM
EPOCHbeing an ISO 8601string, so the epoch survives at microsecond resolution (~15 µs here, ~0.1 m of
along-track motion at 7.7 km/s).
sat_names: [navstar]): 80NAVSTAR satellites resolved via
nearest-TLE, 3 visible, RFI added, zarr + MS written,rfi_tle_sat_orbitcarrying the expected TLE lines.resolved via
nearest-OMM, propagated, and written with empty line columns and fullOMM records in
used_orbits.json.--extra_orbit_dirpointed at the first run'sinput_dataproducesbyte-identical
rfi_tle_sat_xyzandvis_obs(np.array_equal, notapprox).This is the assertion that surfaced the
precise_floatbug; before the fix it was offby 11 nm and nothing would ever have said so.
measuring what it claims to.
Known limitations
so
sat_names: [starlink](>21000 objects) is that many requests on a cold cache,where Space-Track answered in one bulk query. Inherent to the service's API, and
equally true of #92. A warning fires above 500 matches;
navstar(80) is the realisticcase, and every response is cached.
remote_max_age_days: 3is a backstop against obviously unsuitable records, not aclaim of three-day positional accuracy.
re-derived; range checks and an absolute epoch plausibility window are what remain.
Documented in the vendored
records.py.tests/test_sim-vis.py::test_simulation_runs_with_configreaches the network. Itpreviously required Space-Track credentials, so this is a reduction in what CI needs,
not a new requirement.