A unified satellite-ephemeris sampling surface over precise SP3 products and broadcast navigation messages.
sample/3 evaluates a list of satellites across an epoch range at a fixed step
and returns a tidy per-satellite, per-epoch table of ECEF position and satellite
clock bias. The call is identical whether the source is an Orbis.GNSS.SP3
precise product or an Orbis.GNSS.Broadcast navigation product, so a caller can
swap precise for broadcast transparently; the dispatch is on the handle's type.
This is Elixir orchestration over the existing per-satellite evaluators
(Orbis.GNSS.SP3.position/3 and Orbis.GNSS.Broadcast.position/3); no orbit
math lives here. The underlying products are parsed once into resource handles
and reused across every cell — no file is re-read per epoch.
The broadcast models follow IS-GPS-200 (GPS LNAV), the Galileo OS-SIS-ICD, and the BeiDou BDS-SIS-ICD; precise products are SP3-c / SP3-d (IGS).
Frame, time, and sign conventions
- Frame: position is ITRF/IGS-realization ECEF, in meters
(
x_m,y_m,z_m), the same frame both products evaluate in. - Time: every epoch is interpreted in GPS time (GPST). No leap-second shifting is applied to the supplied epochs; the broadcast evaluator maps GPST onto each system's own scale (BDT for BeiDou, UTC-referenced for GLONASS) internally.
- Clock sign:
clock_sis the satellite clock offset in seconds, with a positive value meaning the satellite clock is ahead of system time. The pseudorange geometric correction is thereforerange + c * clock_s. The SP3 and broadcast paths share this convention (the broadcast value is the clock-polynomial total including the relativistic eccentricity term and the broadcast group delay).
Gaps are explicit
A cell whose satellite has no valid ephemeris at its epoch carries
status: :no_ephemeris and nil position/clock fields. The sampler never
extrapolates beyond a product's validity: an SP3 epoch outside the file's span,
or a broadcast epoch outside any record's fit interval, is reported as a gap,
not filled.
Example
{:ok, sp3} = Orbis.GNSS.SP3.load("igs.sp3")
rows =
Orbis.GNSS.Ephemeris.sample(sp3, ["G01", "E11"], %{
from: ~N[2020-06-25 00:00:00],
to: ~N[2020-06-25 01:00:00],
step_s: 300
})
[%Orbis.GNSS.Ephemeris.Row{} = row | _] = rows
row.satellite_id # "G01"
row.status # :ok | :no_ephemeris
row.x_m # ITRF/IGS ECEF X, meters (nil on a gap)
Summary
Types
A parsed ephemeris source: a precise SP3 product or a broadcast product.
The sampling window: a from/to epoch pair (inclusive of from, and of to
when it falls on a step boundary) and a positive step_s in seconds.
Functions
Sample sat_ids across the window from a precise or broadcast source.
Types
@type source() :: Orbis.GNSS.SP3.t() | Orbis.GNSS.Broadcast.t()
A parsed ephemeris source: a precise SP3 product or a broadcast product.
@type window() :: %{ from: NaiveDateTime.t(), to: NaiveDateTime.t(), step_s: pos_integer() }
The sampling window: a from/to epoch pair (inclusive of from, and of to
when it falls on a step boundary) and a positive step_s in seconds.
Functions
@spec sample(source(), [String.t()], window()) :: [Orbis.GNSS.Ephemeris.Row.t()]
Sample sat_ids across the window from a precise or broadcast source.
source is a loaded Orbis.GNSS.SP3 or Orbis.GNSS.Broadcast handle — the
same call shape for both. sat_ids is a list of canonical RINEX tokens
("G01", "E11", "C06", "R07"). window is a map with :from, :to
(NaiveDateTime in GPST), and :step_s (a positive integer number of seconds).
Returns a flat list of Orbis.GNSS.Ephemeris.Row structs, one per
satellite-epoch cell, in sat_ids order then ascending epoch. A cell with no
valid ephemeris carries status: :no_ephemeris and nil values; the handle is
reused for every cell, so no file is re-read.
Raises ArgumentError for a non-positive step or a window with to before
from.