Sidereon.GNSS.Staleness (Sidereon v0.8.0)

Copy Markdown View Source

Product-staleness graceful degradation for time-varying GNSS products.

Time-varying products (IONEX vertical-TEC maps, rapid/predicted SP3 orbit and clock files) publish with latency and gaps, so the product for the exact requested epoch is not always on hand. This is the Elixir surface over the sidereon-core selection layer: given a SET of already-parsed products and a requested epoch (or epoch range), it returns a usable product plus a Sidereon.GNSS.Staleness.StalenessMetadata describing which source epoch was used and how stale it is, falling back to the most-recent product within a configurable staleness cap. A request that would rely on a product older than the cap fails with a typed error instead of returning data past the cap, so a degraded answer is never substituted silently.

This layer is pure and does no networking: it selects among products the caller has already parsed (Sidereon.GNSS.SP3.load/1, Sidereon.GNSS.Ionosphere.load_ionex/1). Fetching the products is a separate, per-binding concern.

Degradation paths

  • :exact - a product covers the requested epoch; it is returned untouched, so the downstream evaluation is bit-for-bit identical to querying it directly. Staleness is zero.
  • :nearest_prior (SP3) - no product covers the epoch, so the most-recent prior product is used as-is, with staleness measured from its last epoch.
  • :diurnal_shift (IONEX) - no product covers the requested day, so a prior day's grid is advanced by whole days onto the requested epoch (TEC is approximately 24-hour periodic). Only the epoch axis moves; grid values are unchanged.

Epochs

Epochs are a NaiveDateTime or {{y, m, d}, {h, min, s}} tuple, interpreted in the product's own time scale (no leap-second shifting), and converted to seconds since J2000 via Sidereon.GNSS.Time. The IONEX map-epoch axis is integer seconds, so an IONEX request must be a whole-second epoch.

Summary

Types

An epoch as a NaiveDateTime or {{y, m, d}, {h, min, s}} tuple.

A typed selection failure. info for :beyond_staleness_cap is a map with the requested/source epochs, the staleness, and the cap, all in J2000 seconds.

Functions

Select an IONEX product usable at epoch, degrading to a diurnal-shifted prior product within policy.

Select an IONEX product usable across [start_epoch, end_epoch].

Select the SP3 product to use for epoch, degrading to the most-recent prior product within policy.

Select an SP3 product usable across [start_epoch, end_epoch].

Types

epoch()

@type epoch() :: NaiveDateTime.t() | tuple()

An epoch as a NaiveDateTime or {{y, m, d}, {h, min, s}} tuple.

selection_error()

@type selection_error() ::
  :empty_product_set
  | {:invalid_range, float(), float()}
  | {:no_prior_product, float()}
  | {:beyond_staleness_cap, map()}
  | {:invalid_product, String.t()}
  | {:invalid_policy, float()}
  | {:overflow, String.t()}

A typed selection failure. info for :beyond_staleness_cap is a map with the requested/source epochs, the staleness, and the cap, all in J2000 seconds.

Functions

select_ionex(handles, epoch, policy \\ Policy.default())

@spec select_ionex([reference()], epoch(), Sidereon.GNSS.Staleness.Policy.t()) ::
  {:ok, Sidereon.GNSS.Staleness.IonexSelection.t()} | {:error, term()}

Select an IONEX product usable at epoch, degrading to a diurnal-shifted prior product within policy.

handles is a list of parsed-IONEX references from Sidereon.GNSS.Ionosphere.parse_ionex/1 or load_ionex/1. The IONEX axis is integer seconds, so epoch must be a whole-second epoch. Returns {:ok, %Sidereon.GNSS.Staleness.IonexSelection{}} or {:error, reason}.

select_ionex_over_range(handles, start_epoch, end_epoch, policy \\ Policy.default())

@spec select_ionex_over_range(
  [reference()],
  epoch(),
  epoch(),
  Sidereon.GNSS.Staleness.Policy.t()
) ::
  {:ok, Sidereon.GNSS.Staleness.IonexSelection.t()} | {:error, term()}

Select an IONEX product usable across [start_epoch, end_epoch].

See select_ionex/3; this is the range case.

select_sp3(products, epoch, policy \\ Policy.default())

Select the SP3 product to use for epoch, degrading to the most-recent prior product within policy.

An :exact result covers epoch; a :nearest_prior result is the best in-cap candidate and may end before epoch (its coverage gap is the reported staleness), so a downstream solve against it can still fail to serve epoch.

products is a list of parsed Sidereon.GNSS.SP3 products. Returns {:ok, %Sidereon.GNSS.Staleness.Sp3Selection{}} or {:error, reason}.

select_sp3_over_range(products, start_epoch, end_epoch, policy \\ Policy.default())

@spec select_sp3_over_range(
  [Sidereon.GNSS.SP3.t()],
  epoch(),
  epoch(),
  Sidereon.GNSS.Staleness.Policy.t()
) :: {:ok, Sidereon.GNSS.Staleness.Sp3Selection.t()} | {:error, term()}

Select an SP3 product usable across [start_epoch, end_epoch].

See select_sp3/3; this is the range case, where staleness is measured to the range end (the most-stale point).