Sidereon.GNSS.ReducedOrbit (Sidereon v0.31.2)

Copy Markdown View Source

A compact, fitted mean-element approximation of a satellite's orbit.

Sidereon.GNSS.ReducedOrbit fits a list of ECEF position samples into a compact mean-element model that can be evaluated cheaply. It is not orbit determination and not a substitute for SGP4 or precise ephemeris: it deliberately discards short-period structure and reports the residual it leaves behind (rms_m/max_m on the fit, and sample-backed drift/3).

Models

Two models are available, chosen with the :model option on fit/2:

  • :circular_secular (the default) - a circular orbit intended for near-circular orbits (Galileo).
  • :eccentric_secular - adds eccentricity through a nonsingular (h, k) parameterization, recovering the radial a·e signal (~hundreds of km for GPS/BeiDou) while degrading smoothly to the circular model as e -> 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       = 0

and 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. The core fit returns e and ω. 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 values depend on the sample span, cadence, and orbit class. Measure a fitted model with drift/3 against caller-provided truth samples.

This is a compact approximation for caching or visibility, never a substitute for precise source data.

Summary

Functions

Evaluate the model error against truth samples.

Fit a mean-element model to ECEF samples.

Position of the model at epoch, ECEF (ITRF) meters by default.

Position and velocity of the model at epoch.

Types

epoch()

@type epoch() ::
  NaiveDateTime.t()
  | {{integer(), integer(), integer()}, {integer(), integer(), number()}}

t()

@type t() :: %Sidereon.GNSS.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()
}

vec3()

@type vec3() :: %{x_m: float(), y_m: float(), z_m: float()}

Functions

drift(model, samples, opts)

@spec drift(t(), [{epoch(), {number(), number(), number()}}], keyword()) ::
  {:ok, map()} | {:error, term()}

Evaluate the model error against truth samples.

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).

fit(source, opts \\ [])

@spec fit(
  [{epoch(), {number(), number(), number()}}],
  keyword()
) :: {:ok, t()} | {:error, term()}

Fit a mean-element model to ECEF samples.

Options

  • :model - :circular_secular (default) or :eccentric_secular. The circular model fixes eccentricity at zero (suited to near-circular orbits); the eccentric model recovers the a·e radial signal for GPS and other eccentric orbits. See the moduledoc accuracy table.
  • :frame - :ecef (default)
  • :time_scale - the scale the sample epochs are in ("UTC" default)

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, %Sidereon.GNSS.ReducedOrbit{}} or a tagged error: {:too_few_samples, got, required}, :invalid_window, :invalid_cadence, :singular_plane_fit, :raan_ambiguous, {:unsupported_source_frame, frame}, {:unsupported_model, model}, :transform_unavailable, :fit_did_not_converge.

position(model, epoch, opts \\ [])

@spec position(t(), epoch(), keyword()) :: {:ok, vec3()} | {:error, term()}

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_velocity(model, epoch, opts \\ [])

@spec position_velocity(t(), epoch(), keyword()) :: {:ok, map()} | {:error, term()}

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:}}}.