SP3-c / SP3-d precise-ephemeris products (IGS precise orbits + clocks).
This is the Elixir surface over the astrodynamics-gnss SP3 parser and
scipy.interpolate-matched position/clock interpolation. It is not the
JPL-SPK reader (Orbis.Ephemeris): SP3 carries GNSS satellite states in the
ITRF/IGS ECEF frame, in meters, tagged by a GNSS satellite id like "G01".
A file is parsed once into a resource handle held by the BEAM; evaluation operates on that handle and never re-reads the file.
Example
{:ok, sp3} = Orbis.GNSS.SP3.load("/path/to/igs.sp3")
{:ok, state} =
Orbis.GNSS.SP3.position(sp3, "G01", ~N[2020-06-24 00:00:00])
state.x_m # ITRF/IGS ECEF X, meters
state.clock_s # satellite clock offset, seconds (or nil if no estimate)Epochs
The query epoch is interpreted in the file's own time scale (read from the
SP3 header — typically GPST). Pass a NaiveDateTime or a
{{year, month, day}, {hour, minute, second}} tuple; it is converted to the
split Julian date with the same midnight-boundary convention the parser uses
(no leap-second shifting — the epoch stays in the file's scale).
Summary
Functions
Return a copy of other with its clocks shifted onto reference's clock datum
(the clock-datum primitive, applied).
Estimate the per-epoch reference-clock offset of other relative to
reference (the clock-datum primitive).
Load and parse an SP3-c / SP3-d file into a product handle.
Like load/1 but raises on failure.
Merge several SP3 products from different analysis centers into one consistent precise-ephemeris dataset.
Parse an in-memory SP3 byte buffer (already decompressed) into a handle.
Interpolate the state of satellite sat_id at epoch.
Return the SP3/RINEX satellite identifiers declared by the product header.
Serialize the product to standard SP3-c / SP3-d text as iodata. Pure — no I/O.
Types
Functions
Return a copy of other with its clocks shifted onto reference's clock datum
(the clock-datum primitive, applied).
At every epoch the offset could be estimated, each clocked satellite's offset
has the datum subtracted, so the result's clocks are directly comparable to
reference's. Positions are untouched. Epochs without an estimate are left
unchanged. The returned product interpolates like any other SP3.
Returns {:ok, %Orbis.GNSS.SP3{}} or {:error, reason}.
Options
:min_common— minimum common clocked satellites per epoch (default5)
Estimate the per-epoch reference-clock offset of other relative to
reference (the clock-datum primitive).
Precise clock products from different centers are referenced to different
station/ensemble clocks, so their raw clocks differ by a per-epoch common
offset that drifts over the day. This returns that datum: a list of maps
%{jd_whole: float, jd_fraction: float, offset_s: float, satellites: integer},
one per epoch where at least :min_common common clocked satellites let the
(robust median) offset be estimated. Subtract offset_s from other's clocks
to put both products on reference's datum. Orbit positions need no such
treatment — every center reports ITRF center-of-mass coordinates.
Options
:min_common— minimum common clocked satellites per epoch (default5)
Load and parse an SP3-c / SP3-d file into a product handle.
Returns {:ok, %Orbis.GNSS.SP3{}} or {:error, reason}. The file is read and
parsed exactly once; the parsed product is held as a resource handle.
Like load/1 but raises on failure.
Merge several SP3 products from different analysis centers into one consistent precise-ephemeris dataset.
sources is a list of loaded products in precedence order (earlier wins
ties). This is orthogonal to time-stitching: it combines providers at the same
epochs. For every (epoch, satellite) cell in the union of the inputs:
- Union coverage — a satellite present in any input is present in the merged product for that epoch (filling a single center's dropouts).
- Consensus — the largest subset of sources agreeing within tolerance is
combined; sources outside it are recorded as outliers. A cell with no
agreeing subset of
:min_agreeis quarantined (omitted), never averaged across disagreeing centers. A lone source is carried through.
Returns {:ok, %Orbis.GNSS.SP3{}, report} or {:error, reason}, where
report is a map with :quarantined, :single_source, and
:position_outliers lists. Each entry is a map
%{satellite: "G03", jd_whole: float, jd_fraction: float, sources: [0, 2]}
(sources are zero-based indices into sources).
Options
:position_tolerance_m— position agreement tolerance, meters (default0.5):clock_tolerance_s— clock agreement tolerance, seconds (default5.0e-9):min_agree— agreeing sources required to accept a contested cell (default2):clock_min_common— common clocked satellites for the clock-datum estimate (default5):combine—:mean(default),:median, or:precedence
Parse an in-memory SP3 byte buffer (already decompressed) into a handle.
@spec position(t(), String.t(), NaiveDateTime.t() | tuple()) :: {:ok, Orbis.GNSS.SP3.State.t()} | {:error, term()}
Interpolate the state of satellite sat_id at epoch.
sat_id is the canonical SP3/RINEX token, e.g. "G01" (GPS PRN 1), "E12",
"C30". epoch is a NaiveDateTime or a
{{year, month, day}, {hour, minute, second}} tuple, interpreted in the
file's own time scale.
Returns {:ok, %Orbis.GNSS.SP3.State{}} or {:error, reason}.
Return the SP3/RINEX satellite identifiers declared by the product header.
These are canonical three-character tokens such as "G01", "E12", or
"C30". The list is read from the already-loaded SP3 handle; no file I/O or
interpolation is performed.
Examples
{:ok, sp3} = Orbis.GNSS.SP3.parse(sp3_bytes)
ids = Orbis.GNSS.SP3.satellite_ids(sp3)
"G01" in ids
Serialize the product to standard SP3-c / SP3-d text as iodata. Pure — no I/O.
This is the inverse of load/1 / parse/1: a read → (merge/2) → write
pipeline round-trips to a single standard SP3 file any reader consumes. The
output is deterministic (same product → identical bytes). Header fields
(version, epoch count, satellite list, time system, week / seconds-of-week /
MJD / interval) are derived from the product. A satellite absent at an epoch is
written as the SP3 missing-orbit sentinel — so a quarantined merge/2 cell
re-reads as missing, never a fabricated position.
To write to disk (optionally gzipped, with an atomic commit), use
Orbis.GNSS.Data.write_sp3/3.
Examples
{:ok, sp3} = Orbis.GNSS.SP3.load("igs.sp3")
iodata = Orbis.GNSS.SP3.to_iodata(sp3)
{:ok, reparsed} = Orbis.GNSS.SP3.parse(IO.iodata_to_binary(iodata))
Orbis.GNSS.SP3.satellite_ids(reparsed) == Orbis.GNSS.SP3.satellite_ids(sp3)
#=> true