Sidereon.GNSS.Antex (Sidereon v3.0.0)

Copy Markdown View Source

Parser and lookup helpers for ANTEX 1.4 receiver and satellite antenna blocks.

The ANTEX parser, satellite validity lookup, and PCO/PCV interpolation live in the Rust GNSS core. A parsed product keeps every record ANTEX 1.4 defines and keeps absent records absent:

  • header - ANTEX VERSION / SYST, the PCV TYPE / REFANT calibration type and reference antenna (so relative values can be told apart from absolute ones), the header comments, and whether END OF HEADER is present;
  • blocks - every antenna block in file order, including each validity interval of one TYPE / SERIAL NO id;
  • antennas - the latest block for each id, the view antenna/2 reads;
  • outer_comments - comments between and after the blocks, each placed by the number of blocks before it;
  • skipped_records - the count of records the forgiving parse passed over or found inconsistent (a line outside any record, a # OF FREQUENCIES count that disagrees with the sections read, a block or section not closed by its own end record, and similar); the blocks themselves are kept.

Each Sidereon.GNSS.Antex.Antenna keeps its comments (those before TYPE / SERIAL NO apart from the rest), its METH / BY / # / DATE records, DAZI and ZEN1 / ZEN2 / DZEN as nil when the block has no such record, its validity bounds with their exact seconds (Sidereon.GNSS.Antex.Epoch), and its frequency sections as a list in file order, each with its START OF FREQ RMS section when it has one. A frequency label may repeat; frequency/2, pco/2 and pcv/4 refuse a label whose sections differ as {:ambiguous_frequency, fields}.

Refusals

Parsing, writing and lookups return {:error, reason} with the core's typed reason and every field it carries:

  • {:invalid_field, %{antenna_id, record, field, value}} - a record field that is not a valid value, such as a blank, short or malformed validity seconds field or a second of 60, which GPS time does not have;
  • {:repeated_record, %{antenna_id, record}} - a once-per-block record repeated with different content;
  • {:degenerate_grid, %{antenna_id, frequency, reason}} - a PCV row that would put two values on one zenith;
  • {:invalid_input, %{field, reason}} - a lookup argument, such as a non-finite zenith;
  • {:unknown_frequency, %{antenna_id, frequency}}, {:ambiguous_frequency, %{antenna_id, frequency, sections}}, {:missing_pco, %{antenna_id, frequency}} and {:empty_pcv_grid, %{antenna_id, frequency}};
  • {:unwritable, %{field, reason}} - a product the writer cannot state exactly;
  • :invalid_datetime - an epoch outside the GPS calendar and clock.

Summary

Functions

Return the latest antenna block for a TYPE / SERIAL id, or nil.

Return the validity block of a TYPE / SERIAL id valid at epoch, or nil.

Return every validity block of a TYPE / SERIAL id, in file order.

Serialize a parsed ANTEX product back to ANTEX 1.4 text.

The frequency section a label selects.

Load and parse an ANTEX file from path.

Like load/1 but raises on failure.

Parse ANTEX text already in memory.

Frequency-dependent PCO (north/east/up in meters).

Like pco/2 but raises on a refused lookup.

Frequency-dependent phase-center variation in meters.

Return the satellite antenna block for PRN prn (e.g. "G05") valid at the given epoch, or nil if none.

Return the number of records the forgiving parse skipped or found inconsistent.

Types

parse_error()

@type parse_error() :: {:error, term()}

t()

@type t() :: %Sidereon.GNSS.Antex{
  antennas: %{optional(String.t()) => Sidereon.GNSS.Antex.Antenna.t()},
  blocks: [Sidereon.GNSS.Antex.Antenna.t()],
  handle: reference() | nil,
  header: Sidereon.GNSS.Antex.Header.t() | nil,
  outer_comments: [Sidereon.GNSS.Antex.OuterComment.t()],
  skipped_records: non_neg_integer()
}

Functions

antenna(antex, id)

@spec antenna(t(), String.t()) :: Sidereon.GNSS.Antex.Antenna.t() | nil

Return the latest antenna block for a TYPE / SERIAL id, or nil.

antenna_at(antex, id, epoch)

@spec antenna_at(t(), String.t(), NaiveDateTime.t() | Sidereon.GNSS.Antex.Epoch.t()) ::
  Sidereon.GNSS.Antex.Antenna.t() | nil | {:error, term()}

Return the validity block of a TYPE / SERIAL id valid at epoch, or nil.

epoch is a NaiveDateTime or a Sidereon.GNSS.Antex.Epoch in GPS time; bounds are inclusive and compared with their exact seconds.

antenna_intervals(antex, id)

@spec antenna_intervals(t(), String.t()) :: [Sidereon.GNSS.Antex.Antenna.t()]

Return every validity block of a TYPE / SERIAL id, in file order.

encode(antex)

@spec encode(t()) :: {:ok, String.t()} | {:error, term()}

Serialize a parsed ANTEX product back to ANTEX 1.4 text.

The writer states every record from a retained value and writes no record the source did not carry, apart from the start and end records of blocks and sections, with antenna blocks and frequency sections in file order and validity seconds exactly. Re-parsing the output yields an equal product. The serializer works on the full parsed product held alongside the decoded antennas, so every validity interval is re-emitted, not just the latest-wins view exposed by antenna/2.

The writer refuses a product it cannot state exactly - a field that overflows its columns, a value its precision cannot hold, a sample coordinate the reader would not reconstruct, a frequency label that is not a system flag and a two-column number, validity seconds no decimal form fits in 13 columns - as {:error, {:unwritable, %{field: field, reason: reason}}} rather than rounding or dropping it.

frequency(antenna, label)

@spec frequency(Sidereon.GNSS.Antex.Antenna.t(), String.t()) ::
  {:ok, Sidereon.GNSS.Antex.Frequency.t()} | {:error, term()}

The frequency section a label selects.

When several sections carry the label they must be identical; otherwise the lookup is refused as {:error, {:ambiguous_frequency, fields}}.

load(path)

@spec load(String.t()) :: {:ok, t()} | parse_error()

Load and parse an ANTEX file from path.

load!(path)

@spec load!(String.t()) :: t()

Like load/1 but raises on failure.

parse(text)

@spec parse(binary()) :: {:ok, t()} | parse_error()

Parse ANTEX text already in memory.

Returns {:ok, %Sidereon.GNSS.Antex{}} with every retained record, or {:error, reason} with the typed refusals listed in the module documentation.

pco(antenna, frequency)

@spec pco(Sidereon.GNSS.Antex.Antenna.t(), String.t()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}

Frequency-dependent PCO (north/east/up in meters).

Returns {:ok, {north, east, up}}, {:error, {:unknown_frequency, fields}}, or {:error, {:ambiguous_frequency, fields}}.

pco!(antenna, frequency)

@spec pco!(Sidereon.GNSS.Antex.Antenna.t(), String.t()) :: {float(), float(), float()}

Like pco/2 but raises on a refused lookup.

pcv(antenna, frequency, zenith_deg, azimuth_deg \\ nil)

@spec pcv(Sidereon.GNSS.Antex.Antenna.t(), String.t(), number(), number() | nil) ::
  {:ok, float()} | {:error, term()}

Frequency-dependent phase-center variation in meters.

Interpolation is linear in zenith and azimuth. Azimuth is optional: when not given (or when the antenna has no azimuth-dependent rows), the NOAZI row is used. A finite zenith outside the block's ZEN1..ZEN2 grid is clamped to it. Returns {:ok, value_m} or {:error, reason} with the typed lookup refusals (unknown or ambiguous frequency, a non-finite zenith as {:invalid_input, fields}, an empty grid as {:empty_pcv_grid, fields}).

pcv!(antenna, frequency, zenith_deg, azimuth_deg \\ nil)

@spec pcv!(Sidereon.GNSS.Antex.Antenna.t(), String.t(), number(), number() | nil) ::
  float()

Like pcv/4 but raises on a refused lookup.

satellite_antenna(antex, prn, epoch)

@spec satellite_antenna(
  t(),
  String.t(),
  NaiveDateTime.t() | Sidereon.GNSS.Antex.Epoch.t()
) ::
  Sidereon.GNSS.Antex.Antenna.t() | nil | {:error, term()}

Return the satellite antenna block for PRN prn (e.g. "G05") valid at the given epoch, or nil if none.

Every validity interval in the file is searched, not only the latest block of each id. epoch is a NaiveDateTime or a Sidereon.GNSS.Antex.Epoch in GPS time. An epoch outside the GPS calendar and clock returns {:error, :invalid_datetime}.

skipped_records(antex)

@spec skipped_records(t()) :: non_neg_integer()

Return the number of records the forgiving parse skipped or found inconsistent.