A compact, fitted mean-element approximation of a satellite's orbit.
Orbis.ReducedOrbit distills a position track — from a precise SP3 product or
a list of ECEF samples — into a handful of mean elements that reproduce the
motion cheaply, for caching, transport, and quick visibility math. It is not
orbit determination and not a substitute for SGP4 or precise ephemeris: it
deliberately discards short-period structure and is honest about the error it
leaves behind (rms_m/max_m on the fit, and a source-backed drift/3).
Models
Two models are available, chosen with the :model option on fit/2:
:circular_secular(the default) — a circular orbit; best for near-circular orbits (Galileo).:eccentric_secular— adds eccentricity through a nonsingular(h, k)parameterization, recovering the radiala·esignal (~hundreds of km for GPS/BeiDou) while degrading smoothly to the circular model ase -> 0.
circular_secular
A circular orbit (eccentricity fixed at zero) whose orbital plane precesses at
a constant nodal rate. At an offset dt = t - t0 from the reference epoch the
angles advance linearly,
u(t) = arg_lat0 + n * dt # argument of latitude
raan(t) = raan0 + raan_rate * dt
e = 0and the inertial (GCRS) position is the in-plane circle rotated by the node and
inclination, r = Rz(raan) * Rx(i) * a * [cos u, sin u, 0].
The nodal rate raan_rate is fitted, but seeded from the J2 secular nodal
regression (Vallado, Fundamentals of Astrodynamics and Applications):
raan_rate_j2 = -1.5 * n * J2 * (Re / a)^2 * cos(i)Both the fitted value (raan_rate_rad_s) and the J2 seed (raan_rate_j2_rad_s)
are kept; raan_rate_mode is "fitted_j2_seeded". The model does not claim to
be a pure J2 propagation.
eccentric_secular
Eight free elements: the four circular plane elements plus h = e·sin ω,
k = e·cos ω, L0 (mean argument of latitude at epoch), and n. Derived
e = sqrt(h² + k²) and ω = atan2(h, k). At an offset dt the model advances
λ = L0 + n·dt, forms the mean anomaly M = λ − ω, solves Kepler's equation
E − e·sin E = M, and places the satellite at radius r = a(1 − e·cos E) and
argument of latitude u = ω + ν. The (h, k) form is nonsingular: at e = 0
it reproduces circular_secular exactly with arg_lat0 = L0. The struct then
carries h, k, e, and arg_perigee_rad (= ω).
Frames
Fitting and evaluation run internally in GCRS; positions are returned in
ECEF (ITRF) meters by default, or GCRS via frame: :gcrs. ECEF velocity
includes the Earth-rotation transport term. Sample/query epochs are interpreted
consistently for the Earth-rotation conversion; the ECEF product (the primary
output) is self-consistent across the fit, evaluation, and drift.
Expected accuracy
Representative drift (extrapolated model-vs-source position error): a fit to the first ~6 hours of an MGEX SP3 track, drifted over the rest of the day. Numbers are the max position error over the full day, measured against vendored MGEX products (GRG for GPS/Galileo, a trimmed GBM product for BeiDou):
| Orbit class | circular_secular | eccentric_secular |
|---|---|---|
| GPS, e ~ 0.024 (G21) | ~8 100 km | ~8 km |
| GPS, e ~ 0.020 (G02) | ~9 400 km | ~11 km |
| BeiDou IGSO, e ~ 5e-3 (C08) | ~2 200 km | ~12 km |
| BeiDou MEO, e ~ 9e-4 (C21) | ~140 km | ~5 km |
| Galileo, e ~ 1e-4 (E01) | ~8 km | ~8 km (≈ circular) |
Reading of the table:
- For eccentric orbits the circular model is unusable — the unmodelled
radial
a·esignal compounds badly under extrapolation.:eccentric_secularis the recommended model and brings the error down by one-to-three orders of magnitude. Note this holds even for the small eccentricities of BeiDou MEO/IGSO (e ~ 1e-3 to 5e-3): a few-hundred-metre to ~200 km radial signal still wrecks the circular extrapolation, so eccentric wins there too. - For near-circular orbits (Galileo, e ~ 1e-4) both models are comparable: the eccentric model is essentially identical to the circular one (~8 km either way), so it does not regress and is a safe default when the orbit class is unknown.
These figures are measured through drift/3 (the public NIF-backed pipeline,
GPST) against the vendored fixtures; the exact numbers shift slightly with the
fit window and drift cadence.
These are characterisations, not guarantees: always measure a given fit with
drift/3 against the source. LEO accuracy is characterised once the SGP4/TLE
source lands. This is a compact approximation for caching / visibility, never a
substitute for SP3 or SGP4.
Persistence
to_map/1 emits a stable, versioned map (frame, model, units, epoch scale,
elements, fit stats) that from_map/1 reads back, for caching/transport.
Summary
Functions
Evaluate the model error against the source ephemeris over a horizon.
Fit a mean-element model to a source orbit.
Reconstruct a model from a to_map/1 map. Validates the version and model id.
Position of the model at epoch, ECEF (ITRF) meters by default.
Position and velocity of the model at epoch.
Serialize a fitted model to a stable, versioned map (string keys) for caching
or transport. See from_map/1 for the inverse.
Types
@type t() :: %Orbis.ReducedOrbit{ a_m: float(), arg_lat_rad: float(), arg_perigee_rad: float() | nil, e: float(), epoch: NaiveDateTime.t(), fit: map(), frame: String.t(), h: float() | nil, i_rad: float(), k: float() | nil, mean_motion_rad_s: float(), model: String.t(), raan_rad: float(), raan_rate_j2_rad_s: float(), raan_rate_mode: String.t(), raan_rate_rad_s: float(), time_scale: String.t(), version: pos_integer() }
Functions
@spec drift( t(), Orbis.SP3.t() | [{epoch(), {number(), number(), number()}}], keyword() ) :: {:ok, map()} | {:error, term()}
Evaluate the model error against the source ephemeris over a horizon.
This compares the model to fresh truth samples (not to itself): for an
Orbis.SP3 source it samples the product over :window at :cadence_s for
:satellite_id; for a list of {epoch, {x_m, y_m, z_m}} samples it uses those
directly. Returns
{:ok, %{per_epoch: [%{epoch:, error_m:}], max_m:, rms_m:, threshold_horizon:,
requested:, used:}}where threshold_horizon is the first epoch the ECEF error exceeds
:threshold_m (or nil if it never does / no threshold given).
@spec fit( Orbis.SP3.t() | [{epoch(), {number(), number(), number()}}], keyword() ) :: {:ok, t()} | {:error, term()}
Fit a mean-element model to a source orbit.
Sources
- an
Orbis.SP3handle — requires:satellite_idand:window; samples the product at:cadence_sover the window; - a list of
{epoch, {x_m, y_m, z_m}}ECEF samples — the window is taken from the samples;:framemust be:ecef(the default).
Options
:model—:circular_secular(default) or:eccentric_secular. The circular model fixes eccentricity at zero (best for near-circular orbits); the eccentric model recovers thea·eradial signal (recommended for GPS and other eccentric orbits). See the moduledoc accuracy table.:satellite_id— e.g."G05"(SP3 source):window—{t0, t1}epochs bounding the fit (SP3 source):cadence_s— positive sampling step in seconds (SP3 source, default900):frame— for the sample-list source,:ecef(default):time_scale— for the sample-list source, the scale its epochs are in ("UTC"default, e.g."GPST"); SP3 sources use the product's own scale
Epochs are interpreted in the model's time scale (recorded on the result). The
reference epoch t0 is the earliest sample, so the result is independent of the
caller's sample order.
Returns {:ok, %Orbis.ReducedOrbit{}} or a tagged error:
{:too_few_samples, got, required}, :invalid_window, :invalid_cadence,
:satellite_id_required, :singular_plane_fit, :raan_ambiguous,
{:unsupported_source_frame, frame}, {:unsupported_model, model},
:transform_unavailable, :fit_did_not_converge.
Reconstruct a model from a to_map/1 map. Validates the version and model id.
Returns {:ok, %Orbis.ReducedOrbit{}} or {:error, {:unsupported_version, v}} /
{:error, {:unsupported_model, model}} / {:error, :malformed_map}.
Position of the model at epoch, ECEF (ITRF) meters by default.
Pass frame: :gcrs for the inertial position. Returns
{:ok, %{x_m:, y_m:, z_m:}} or {:error, reason}.
Position and velocity of the model at epoch.
ECEF velocity includes the Earth-rotation transport term. Returns
{:ok, %{position: %{x_m:, y_m:, z_m:}, velocity: %{vx_m_s:, vy_m_s:, vz_m_s:}}}.
Serialize a fitted model to a stable, versioned map (string keys) for caching
or transport. See from_map/1 for the inverse.