RINEX 3 observation products: parse a station's observation file, expose its
header (including the surveyed APPROX POSITION XYZ), and extract the
single-frequency pseudoranges that Orbis.PointPositioning.solve/4 consumes.
This is the Elixir surface over the astrodynamics-gnss RINEX observation
parser and its Hatanaka (CRINEX) decoder. A file is parsed once into a
resource handle held by the BEAM; accessors operate on that handle and never
re-read the file.
Both plain RINEX (.rnx) and Hatanaka-compressed CRINEX (.crx) text are
accepted: load/1 and parse_auto/1 sniff the first line for the
CRINEX VERS / TYPE marker and decode CRINEX before parsing. Gzip is handled
upstream by Orbis.GnssData.fetch/2, so this module only ever sees plain text
or CRINEX text.
Example
{:ok, obs} = Orbis.RinexObs.load("ESBC00DNK_..._MO.crx")
Orbis.RinexObs.approx_position(obs)
# => {3_582_105.291, 532_589.7313, 5_232_754.8054}
[%{index: i, epoch: epoch} | _] = Orbis.RinexObs.epochs(obs)
{:ok, prs} = Orbis.RinexObs.pseudoranges(obs, i, codes: %{"G" => ["C1C"]})
# prs :: [{"G01", range_m}, ...] — feeds solve/4 verbatimDefault pseudorange codes
The per-system defaults are version-aware: GPS C1C, Galileo C1C then
C1X, BeiDou C1I for RINEX 3.02 / C2I for 3.01 and 3.03+ (the B1I label
changed between minor versions), GLONASS C1C. Override per system with the
:codes option, e.g. codes: %{"G" => ["C1C"], "C" => ["C2I"]}.
Summary
Types
An epoch descriptor as returned by epochs/1.
A pseudorange observation {satellite_id, range_m}.
Functions
The surveyed a-priori receiver position {x_m, y_m, z_m} (ECEF meters), or
nil if the file carries no APPROX POSITION XYZ.
Decode Hatanaka CRINEX text into the plain RINEX observation text it expands
to. Returns {:ok, rinex_text} or {:error, reason}.
The epoch list as [%{index:, epoch:, flag:, sat_count:}]. The :epoch is a
{{y, mo, d}, {h, mi, second_float}} tuple in the file's time scale — exactly
the form Orbis.PointPositioning.solve/4 accepts.
Load and parse a RINEX observation file from disk.
Like load/1 but raises on failure.
The per-constellation observation-code table as a map of system letter to the
ordered code list, e.g. %{"G" => ["C1C", ...], "E" => [...]}.
Parse plain RINEX 3 observation text into a handle.
Parse text, auto-detecting plain RINEX vs CRINEX from the first line.
Decode Hatanaka CRINEX text and parse the result into a handle.
Extract single-frequency pseudoranges for one epoch.
Types
@type epoch_entry() :: %{ index: non_neg_integer(), epoch: {{integer(), integer(), integer()}, {integer(), integer(), float()}}, flag: 0..255, sat_count: non_neg_integer() }
An epoch descriptor as returned by epochs/1.
A pseudorange observation {satellite_id, range_m}.
@type t() :: %Orbis.RinexObs{handle: reference()}
Functions
The surveyed a-priori receiver position {x_m, y_m, z_m} (ECEF meters), or
nil if the file carries no APPROX POSITION XYZ.
Decode Hatanaka CRINEX text into the plain RINEX observation text it expands
to. Returns {:ok, rinex_text} or {:error, reason}.
@spec epochs(t()) :: [epoch_entry()]
The epoch list as [%{index:, epoch:, flag:, sat_count:}]. The :epoch is a
{{y, mo, d}, {h, mi, second_float}} tuple in the file's time scale — exactly
the form Orbis.PointPositioning.solve/4 accepts.
Load and parse a RINEX observation file from disk.
The file may be plain RINEX (.rnx) or Hatanaka CRINEX (.crx); the first
line is sniffed for the CRINEX marker and decoded if present. Returns
{:ok, %Orbis.RinexObs{}} or {:error, reason}.
Like load/1 but raises on failure.
The per-constellation observation-code table as a map of system letter to the
ordered code list, e.g. %{"G" => ["C1C", ...], "E" => [...]}.
Parse plain RINEX 3 observation text into a handle.
Parse text, auto-detecting plain RINEX vs CRINEX from the first line.
Decode Hatanaka CRINEX text and parse the result into a handle.
@spec pseudoranges(t(), non_neg_integer() | tuple(), keyword()) :: {:ok, [observation()]} | {:error, term()}
Extract single-frequency pseudoranges for one epoch.
epoch is either the integer epoch index (from epochs/1) or an epoch tuple
{{y, mo, d}, {h, mi, s}}, which is resolved to its index.
Without :codes, the version-aware defaults are applied across every system.
When :codes is given it defines the whole policy: only the listed systems
are extracted, each with its given code preference, e.g. codes: %{"G" => ["C1C"]} yields GPS-only pseudoranges and codes: %{"G" => ["C1C"], "C" => ["C2I"]} yields GPS + BeiDou.
Returns {:ok, [{"G01", range_m}, ...]} (ascending satellite id) or
{:error, :epoch_out_of_range} / {:error, :unknown_epoch}.