Satellite orbit records
TABASCAL obtains satellite orbital elements from the IAU CPS SatChecker service. No account or credentials are required.
Two archives, one handover
SatChecker keeps satellite orbits in two formats, and which one you get depends on when your observation was.
Archive |
Endpoint |
Covers |
|---|---|---|
TLE |
|
up to 2026-07-11, frozen |
OMM |
|
from 2026-07-12 onwards, growing |
The two do not overlap: the last TLE and the first OMM are about twelve hours apart, and the TLE archive will never gain another record. SatChecker 1.7.0 made the split because Celestrak is dropping Alpha-5 notation in order to preserve the original TLE format — which means catalogue numbers above 99999 cease to be representable as TLEs at all.
A TLE (Two-Line Element set) encodes an orbit in two fixed-width 69-column lines. An OMM (Orbit Mean-Elements Message) carries the same orbital elements as named numeric fields. Both describe the same SGP4 model, and TABASCAL derives the same element set from either, so nothing downstream of resolution cares which one a satellite resolved to.
You do not choose between them. TABASCAL asks the archive your observation epoch falls in, and falls back to the other if that one has nothing usable.
Resolution order
Each configured NORAD ID is resolved independently, in this order:
extra_orbit_dir: explicit user or replay files, of either kind. The valid record closest to the observation is selected and checked againstextra_orbit_max_age_days.Managed per-NORAD cache: the validated cached record closest to the observation is selected. If its age is within
cache_reuse_max_age_days, no network request is made.SatChecker: TABASCAL requests the nearest record at the exact observation epoch. Cache misses run with at most five requests in flight, and valid responses are added to the per-NORAD cache.
TABASCAL does not use SatChecker’s full-catalogue endpoints. Those select the newest record at or before the requested epoch, whereas TABASCAL requires the record whose epoch is closest on either side.
Which endpoint, and the fallback
The observation epoch picks the endpoint to ask first — get-nearest-omm for an
observation on or after 2026-07-12, get-nearest-tle before it. In the common
case that is the whole story: one request per satellite, answered from the right
archive.
If that request yields nothing acceptable, the other endpoint is asked for the
IDs the first archive did not answer with an acceptable record. Note that this
is not the same as the IDs left unresolved: a satellite whose cached record is
stale but still inside the hard ceiling is already resolved from the cache
before any request goes out, and something closer is the entire point of asking.
It reaches the fallback too. The fallback exists because neither endpoint
reports that it has nothing near the epoch you asked for. Ask get-nearest-omm for a
2021 epoch and it returns its earliest 2026 record — years off, with nothing in
the response to say so. Ask get-nearest-tle for a 2027 epoch and it returns
the last TLE ever published. TABASCAL’s age ceiling rejects both, and that
rejection is exactly the signal that the record you want lives in the other
archive. For an observation within a few days of the handover this is the normal
path, not an exceptional one.
That is also why the handover date is a hint rather than a cutoff. SatChecker now sources OMM from Space-Track as well as Celestrak, and Space-Track’s OMM history runs years deep, so OMM may yet appear for earlier epochs. A hardcoded cutoff would keep silently preferring a stale TLE; with the fallback, a backfill costs one extra request instead of a worse answer. The date is not a configuration key — it is a property of the service, not of your run.
A failure is different from an unusable answer. If the service cannot be reached, returns HTTP 429, or rejects every request alike, TABASCAL does not try the other endpoint: the service is down, and asking a down service a different question is still asking a down service.
Being a considerate client
TABASCAL aims to be a considerate client of a free public service. Requests are issued at most five at a time, are submitted one at a time as earlier ones land rather than queued all at once, and every response that can be reused is written to the local cache so a later run does not ask again.
If a request fails because the service cannot be reached, or the service returns HTTP 429 to say this client should back off, TABASCAL stops the batch there: no further requests are sent, so an outage or a rate limit costs at most five requests no matter how many satellites are configured — and, as above, no second round against the other endpoint.
A malformed reply, or a 4xx rejection of one request, is different: the service is up and answering, so it is treated as that one satellite’s problem and the rest of the batch continues — an unknown catalogue number legitimately gets an empty response. Should the service reject ten consecutive requests with the same status and no success in between, TABASCAL concludes it is facing a wall rather than ten absent satellites, and stops there too.
Whatever the cause, every configured ID is reported, and the resulting error
distinguishes a satellite with no record from one TABASCAL could not ask about.
When the service supplies a Retry-After hint it is carried into that error, so
you know when the run is worth repeating — TABASCAL reports the wait rather than
sleeping through it, so an unattended preflight never blocks for an interval it
did not choose.
Every configured satellite must resolve to an acceptable record. The check runs during preflight, before the visibilities are read. A missing or over-age record stops the run rather than silently shrinking the RFI model.
Cache policy
The managed cache normally lives in the platform user-cache directory, such as
~/.cache/orbit-cache on Linux or ~/Library/Caches/orbit-cache on macOS. Set
ORBIT_CACHE_DIR to relocate it. This variable controls managed storage; it is
not an additional source like --extra-orbit-dir.
Each orbit-<NORAD ID>.json file is an atomically written, versioned envelope
containing the validated immutable records already learned for that object.
Records are keyed by their contents and epoch rather than by the observation
that originally requested them, so one record can serve multiple nearby runs.
One file holds both kinds. Around the handover a satellite will typically have both its last TLEs and its first OMM records in the same file; which one a given observation uses is decided by epoch distance, not by format.
Two age settings have intentionally different jobs:
Setting |
Purpose |
Default |
|---|---|---|
|
Avoid a request when the nearest cached record is already this close to the observation |
1 day |
|
Hard safety ceiling for every SatChecker or managed-cache record |
3 days |
If a cached record is older than the reuse threshold but still inside the hard ceiling, TABASCAL asks SatChecker for something closer — including the fallback archive, if the first one has nothing to offer for that satellite. A response replaces the cached record only when it is strictly closer to the observation, so a refresh can never downgrade what you already hold. If the request fails or returns nothing usable, the acceptable cached record is used as an offline fallback. Records outside the hard ceiling are never accepted automatically.
cache_reuse_max_age_days: null means always reuse the nearest acceptable
cached record. remote_max_age_days: null is a separate expert opt-out that
removes the safety ceiling. When both are numeric, the reuse threshold must not
exceed the ceiling.
Cache reads validate the schema, NORAD identity and every field consumed downstream — see Validation for what that means per kind. A missing, partial, corrupt, or incompatible file is treated as a cache miss; a file that exists but cannot be used is also reported, so a cache that never takes hold does not silently cost a request every run. Cache-write failures are warnings: TABASCAL continues with validated records in memory, but a later run will need to fetch them again.
Cache files written by a TABASCAL that predates OMM support (schema version 1) are reported as unusable and replaced by the next fetch. Nothing needs converting; you may see one warning per satellite on the first run after upgrading.
Validation
The two formats do not offer the same guarantees, and it is worth being explicit about what is lost rather than letting the difference pass silently.
Both kinds are checked for:
a present, numeric, finite, whole-number
NORAD_CAT_ID;finiteness on all seven orbital elements;
inclination in [0, 180]; RAAN, argument of pericenter and mean anomaly in [0, 360); eccentricity in [0, 1); mean motion strictly positive.
A TLE additionally gets two checks that have no OMM equivalent:
The modulo-10 checksum on each 69-column line. This is what makes single-character corruption detectable — a flipped digit inside a fixed-width numeric field otherwise parses cleanly, stays in range, and silently shifts the modelled trajectory.
The embedded identity cross-check. Both lines carry the satellite identifier, and TABASCAL requires them to agree with each other and with the row. A record filed under the wrong satellite is caught.
There is a third, subtler difference. A TLE’s epoch is always re-derived from
line 1; a provider’s own epoch field is never trusted for acceptance. An OMM has
no lines to re-derive from, so its EPOCH field must be taken at face value —
and, as described above, a clamped get-nearest-omm response is precisely the
case where a wrong epoch would otherwise be invisible. To partly close that gap,
an OMM epoch must parse as ISO 8601 and fall inside an absolute plausibility
window (not before 1957, not more than a year in the future).
An OMM record has no checksum. There is no way to add one. The range checks and the epoch window are what stands in for it, and they are weaker. This is a property of the format, not of TABASCAL’s handling of it.
Logs report each selected provider, epoch, signed offset, absolute age, and which endpoint answered.
Record age and suitability
The three-day default is a provisional emergency backstop, not a claim that a three-day-old element set is scientifically adequate. Position error depends strongly on orbit, maneuvers, baseline, wavelength, and the intended phase accuracy. The observation-specific replacement is tracked in issue #101.
Exact replay
Every run saves the exact records it used to
<sim_dir>/results/used_orbits_<name>.json. To reproduce those trajectory
priors, copy or retain that file and pass its directory to a later run:
tabascal run -c path/to/config.yaml -ms path/to/data.ms --extra-orbit-dir /path/to/saved-run
The default extra_orbit_max_age_days: null deliberately exempts explicit
replay files from the remote age ceiling.
The file records each entry in whatever form it needs to be read back as itself: a TLE’s two lines, or an OMM’s epoch and elements. Derived quantities are not stored — they are recomputed on every read, so a stored copy could only ever drift out of agreement with the elements it came from.
Supplying records manually
Use --extra-orbit-dir when SatChecker lacks an object or an acceptable
historical record. Every *.json file in the directory is considered. Files
must be JSON tables carrying either kind’s required columns.
For a TLE:
NORAD_CAT_IDTLE_LINE1TLE_LINE2
For an OMM:
NORAD_CAT_IDEPOCH(ISO 8601)INCLINATION,RA_OF_ASC_NODE,ECCENTRICITY,ARG_OF_PERICENTER,MEAN_ANOMALY,MEAN_MOTION,BSTAR
OBJECT_NAME and OBJECT_ID are optional. A JSON array of objects is the
clearest format to use:
[
{
"NORAD_CAT_ID": 25544,
"OBJECT_NAME": "ISS (ZARYA)",
"TLE_LINE1": "1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927",
"TLE_LINE2": "2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
},
{
"NORAD_CAT_ID": 25544,
"OBJECT_NAME": "ISS (ZARYA)",
"EPOCH": "2026-08-13T03:34:14.082240",
"INCLINATION": 51.6324,
"RA_OF_ASC_NODE": 18.1827,
"ECCENTRICITY": 0.0007533,
"ARG_OF_PERICENTER": 41.6914,
"MEAN_ANOMALY": 318.4648,
"MEAN_MOTION": 15.49426097,
"BSTAR": 7.5606e-05
}
]
You do not need to say which kind a record is. TABASCAL infers it: a record with
TLE lines is a TLE, a record with the element columns is an OMM. (A record may
declare RECORD_KIND explicitly, and files TABASCAL writes itself do, but no
external export carries such a field and none needs to.) A record carrying both
— as Space-Track’s exports do — is read as a TLE, because its lines are the
stronger thing to validate against.
A directory that does not exist is reported as a warning rather than silently searched, so a typo in the path cannot quietly leave you modelling SatChecker’s satellites instead of your own.
A file may contain multiple records for the same object, records for multiple objects, and a mixture of kinds. TABASCAL validates each record as described under Validation and selects the valid record closest to the observation independently for each requested ID — by epoch distance, regardless of format. Invalid rows do not poison unrelated satellites; the unresolved ID falls through to the managed cache and SatChecker.
The column-oriented JSON written by pandas.DataFrame.to_json() is also
accepted. In particular, TABASCAL’s used_orbits_<name>.json replay files can
be placed directly in this directory. Other files and subdirectories are
ignored; only *.json files immediately inside extra_orbit_dir are read.
Obtaining compatible JSON from Space-Track
Space-Track’s gp and gp_history JSON responses include NORAD_CAT_ID,
OBJECT_NAME, TLE_LINE1, TLE_LINE2 and the OMM element fields, so their
response bodies can be saved directly in extra_orbit_dir without conversion.
Use gp for the current element set and gp_history for historical element
sets. Space-Track requires a free account and authenticated requests; see its
API documentation and
GP field definition.
This is the practical route to a pre-handover epoch that SatChecker’s OMM archive cannot serve, and to a post-handover object with no TLE representation.
For example, the following logs in, downloads ISS element sets whose epochs fall between 20 and 22 February 2023, and logs out. The object, epoch range and output file are set once at the top so the rest can be reused unchanged. The password is prompted for at run time and cleared afterwards rather than being hard-coded or written into the output file:
# --- parameters -------------------------------------------------------------
NORAD_ID='25544' # numeric NORAD catalogue ID (25544 = ISS)
EPOCH_START='2023-02-20' # UTC
EPOCH_STOP='2023-02-22' # UTC
OUTPUT='iss-history.json'
SPACETRACK_USER='your-email@example.com'
# ----------------------------------------------------------------------------
BASE='https://www.space-track.org/basicspacedata/query'
QUERY_URL="${BASE}/class/gp_history/norad_cat_id/${NORAD_ID}/EPOCH/${EPOCH_START}--${EPOCH_STOP}/orderby/EPOCH%20asc/format/json"
printf 'Space-Track password: '
read -r -s SPACETRACK_PASSWORD
printf '\n'
cookie_jar=$(mktemp)
curl --fail --silent --show-error \
--cookie-jar "$cookie_jar" \
--data-urlencode "identity=${SPACETRACK_USER}" \
--data-urlencode "password=${SPACETRACK_PASSWORD}" \
https://www.space-track.org/ajaxauth/login
curl --fail --silent --show-error \
--cookie "$cookie_jar" \
"$QUERY_URL" \
--output "$OUTPUT"
curl --fail --silent --show-error \
--cookie "$cookie_jar" \
https://www.space-track.org/ajaxauth/logout
rm -f "$cookie_jar"
unset SPACETRACK_PASSWORD
Put iss-history.json in the directory passed to --extra-orbit-dir.
Notes for adapting this into a script of your own:
EPOCH_STARTandEPOCH_STOPare UTC and both ends are inclusive. A bare date means midnight, so the window above ends at2023-02-22 00:00:00; append a time as2023-02-22%2012:00:00for finer control, keeping the%20encoding since the value sits in the URL path.NORAD_IDalso accepts a comma-separated list —'25544,48274'— to fetch several objects into one file. TABASCAL reads multi-object files, andOUTPUTis set independently so the filename need not track the IDs.To obtain the current element set instead of a historical range, leave everything else alone and use the
gpclass, which takes no epoch range:QUERY_URL="${BASE}/class/gp/norad_cat_id/${NORAD_ID}/format/json"
If you save this as a script rather than pasting it into a shell, add
set -euat the top so a failed login does not fall through to the query. It is deliberately absent above, becauseset -ein an interactive shell closes the terminal on the first error.
Space-Track’s published usage policy says not to use gp_history for current
ephemerides and to download a historical object or range once and retain it
locally. For many objects or large date ranges, use the historical bulk files
Space-Track provides rather than repeatedly querying gp_history.
This is a manual interoperability path only; TABASCAL does not store Space-Track credentials or query Space-Track itself.
Distributed runs
Only process 0 resolves satellites and performs network requests. Its complete resolution — including the chosen records, provenance, epochs, and coverage decision — is broadcast to every process, and workers derive the orbital elements locally. This prevents duplicate requests and ensures that all processes either use the same satellite set or fail coherently.
For a TLE, “locally” means every rank re-parses the same two lines with the same parser, so the results are bit-identical by construction. An OMM has no lines to re-parse, so its element values themselves cross the broadcast, as JSON numbers rather than as text — which round-trips exactly in Python 3. Ranks that disagreed here would hold subtly different trajectory priors with nothing raising anywhere, so this is asserted by test as exact equality rather than approximate.