Orbit determination
What you will accomplish
An orbit fitted to tracking observations, with a covariance you understand well enough to quote.
The two estimators here report position uncertainties that differ by an order of magnitude on identical data. Neither is wrong. This guide is mostly about why.
Prerequisites
- A station catalog.
- An initial orbit estimate.
Steps
Build a tracking-station file
stations list --json writes a catalog, which is not the same document an OD
run consumes. The OD tools need each station wrapped with the measurement kinds
it produces and their noise:
[
{
"station": {
"id": "dsn:goldstone",
"name": "NASA DSN Goldstone (Mojave, USA)",
"latitude_deg": 35.4267,
"longitude_deg": -116.89,
"altitude_km": 1.0,
"min_elevation_deg": 6.0
},
"range": { "sigma": 0.01 },
"range_rate": { "sigma": 0.001 }
}
]Passing the catalog directly fails with
missing field 'station' at line 11 column 3, which names the field but not the
document type it expected.
Simulate observations
orbitforge od simulate-tracking \
--orbit orbit.json \
--stations tracking-stations.json \
--duration-hours 24 --cadence-seconds 30 \
--csv obs.csvSimulated 614 observations from 3 stations over 24.0 h (seed 7).Fit
orbitforge od bls \
--obs obs.csv --stations tracking-stations.json \
--initial orbit.json --epoch 2026-01-01T00:00:00Z \
--json bls.jsonBLS converged in 2 iterations: RMS 1.027, 614 used / 0 edited.
Position sigma [0.0016, 0.0011, 0.0014] km; velocity sigma [1.06e-6, 1.69e-6, 1.67e-6] km/s.Read the RMS first
An RMS near 1.0 means the residuals match the sigmas you declared for the measurements. It is the fastest indication that the fit is consistent with its own noise model.
Much above 1 means the model does not explain the data, or the sigmas are optimistic. Much below 1 means the sigmas are pessimistic and the solution is fitting noise you claimed was larger than it is.
Arc length decides whether it converges at all
The same true orbit, the same estimator, and the initial guess set to the exact truth in every case:
| Setup | Observations | Result |
|---|---|---|
| 1 station, 6 hours | 38 | Fails |
| 1 station, 24 hours | 174 | Converges, 3 iterations |
| 3 stations, 24 hours | 614 | Converges, 2 iterations |
The 6-hour case reports:
invalid orbit-determination input: only 0 usable measurements; at least 6 are requiredThat message describes a symptom, not the cause. The observations parsed fine and 38 of them were used on the first iteration.
The solution then diverged, and on the next pass the residual editor rejected everything, leaving zero. A short single-station arc gives a nearly singular normal matrix, so tiny residuals produce enormous corrections.
This happened starting from the true orbit, so it is not a bad initial guess. The geometry simply does not determine six states.
If OD fails this way, lengthen the arc before improving the guess. Six hours is under four revolutions seen from one point on a rotating Earth; 24 hours gives the station a spread of viewing geometries.
Station diversity buys precision, not convergence
| Setup | Position sigma R, I, C |
|---|---|
| 1 station, 24 hours | [9.4, 4.3, 4.7] m |
| 3 stations, 24 hours | [1.6, 1.1, 1.4] m |
Both converge. Three stations improve the radial component by about a factor of six, because widely separated sites see the satellite along different lines of sight and break correlations a single site cannot.
Arc length fixes convergence. Station geometry fixes precision. They are separate problems and the fix for one does not fix the other.
The EKF’s number answers a different question
Running an EKF over the identical 614 observations:
orbitforge od ekf \
--obs obs.csv --stations tracking-stations.json \
--initial orbit.json --epoch 2026-01-01T00:00:00ZEKF processed 307 epochs: 614 accepted / 0 rejected.
Final position sigma [0.0198, 0.1085, 0.0379] km at 2026-01-02T00:00:00Z.Compared with the batch solution’s [1.6, 1.1, 1.4] m, the EKF looks roughly 12 to 100 times worse. Concluding that batch estimation is better would be wrong.
The two numbers are at different epochs and carry different assumptions.
Batch least squares reports covariance at the solution epoch, having used every observation in the arc, before and after. The EKF reports covariance at the last epoch, using only observations up to that moment, and inflated by process noise between updates.
The process noise is the whole difference. Sweeping --snc, the acceleration PSD
in km^2/s^3:
--snc | Final position sigma R, I, C |
|---|---|
| 0 | [2.0, 1.1, 1.0] m |
| 1e-12 (default) | [19.8, 108.5, 37.9] m |
| 1e-9 | [16.8, 7.3, 5.5] km |
With process noise switched off the EKF reports [2.0, 1.1, 1.0] m against the batch solution’s [1.6, 1.1, 1.4] m. They agree.
The apparent order-of-magnitude gap was a modeling choice, not an estimator quality difference. The default 1e-12 is an assumption about unmodeled acceleration, and like the covariance in conjunction screening, it is supplied rather than measured.
Choosing between them
| Use batch least squares when | Use the EKF when |
|---|---|
| The arc is complete and in hand | Observations arrive continuously |
| You want the best fit over the whole arc | You want the current best estimate |
| Reprocessing is acceptable | You cannot reprocess history |
| Dynamics are well modeled | Unmodeled accelerations are present |
The in-track component being worst in the EKF result, 108.5 m against 19.8 m radial, is the usual pattern. Along-track position is the least observable direction from range and range-rate, and it is also where process noise accumulates fastest.
Checklist
- Is the RMS near 1.0?
- Is the arc long enough to converge, before you blame the initial guess?
- Are you comparing covariances at the same epoch?
- Is the EKF’s process noise stated alongside its covariance?
- Are the measurement sigmas realistic for the actual sensors?
- Does the station file have the wrapped tracking-station schema?
Next steps
od blsreference andod ekfreference.- Screening for conjunctions, which consumes the covariance this produces.
main (pre-release)