Sidereon.GNSS.Constellation (Sidereon v0.29.1)

Copy Markdown View Source

GNSS constellation identity catalogs and validation helpers.

This module is a data/catalog layer: it builds normalized satellite identity records from public sources and compares those records with GNSS products. It does not alter positioning solves or infer application-specific health rules.

The identity, parsing, validation, and diff logic lives in the sidereon_core constellation module (the shared Rust core), reached through the NIF. The core covers all five GNSS constellations: GPS, Galileo, GLONASS, BeiDou, and QZSS, so this module is multi-system: from_celestrak_omm/2 dispatches on the constellation atom (:gps, :galileo, :glonass, :beidou, :qzss) to the per-system identity adapter in the core.

OMM/JSON source records are parsed from OBJECT_NAME and rendered as the SP3/RINEX id ("G13", "E07", "R13", "C19", "J02"). GPS constellation status HTML can be parsed and merged as an optional SVN/usability overlay.

Examples

iex> omms = [
...>   %{"OBJECT_NAME" => "GPS BIIF-8  (PRN 03)", "NORAD_CAT_ID" => 40294}
...> ]
iex> {:ok, [record]} = Sidereon.GNSS.Constellation.from_celestrak_omm(:gps, omms)
iex> {record.system, record.prn, record.norad_id, record.sp3_id}
{:gps, 3, 40294, "G03"}

iex> record = %Sidereon.GNSS.Constellation.Record{
...>   system: :gps,
...>   prn: 3,
...>   svn: nil,
...>   norad_id: 40294,
...>   sp3_id: "G03",
...>   active?: true,
...>   usable?: true,
...>   source: %{}
...> }
iex> Sidereon.GNSS.Constellation.to_csv([record])
"prn,norad_cat_id,active,sp3_id\n3,40294,true,G03\n"

Use from_celestrak_omm/2, from_celestrak_omm_lenient/2, parse_navcen_html/1, and merge_navcen/2 with caller-provided text or decoded maps.

Summary

Functions

Returns true when a constellation diff has any findings.

Compare two constellation catalog snapshots by {system, prn} identity.

Build records for system from a JSON OMM feed.

Build a lenient catalog from a JSON OMM feed.

Build records for system from CelesTrak OMM/JSON maps, failing on the first entry whose OBJECT_NAME does not resolve to a PRN for system.

Build records for system from CelesTrak OMM/JSON maps, skipping (rather than aborting on) entries whose OBJECT_NAME does not resolve to a PRN for system.

Resolve a Galileo GSATdddd build id to its SVID/PRN (E-number), or nil when no SVID is assigned yet.

Resolve a GLONASS orbital slot (1..24) to its FDMA L1/L2 frequency-channel number k, or nil for a slot outside the operational range.

Resolve a GLONASS (Uragan) number to its orbital slot (1..24), or nil when the number is not in the published constellation table.

Merge NAVCEN status rows into normalized records by PRN.

Parse NAVCEN's GPS constellation status HTML.

Render the canonical SP3/RINEX satellite token for a supported GNSS PRN.

Export records as the compact mapping CSV

Returns true when a validation report has no findings.

Validate catalog identity without an SP3 product.

Validate catalog identity against SP3 satellite ids.

Validate against SP3 satellite ids and raise unless the catalog is clean.

Types

error()

@type error() ::
  {:error, {:unsupported_system, term()}}
  | {:error, {:bad_celestrak_record, term(), map()}}
  | {:error, {:invalid_input, term()}}
  | {:error, {:bad_navcen_html, term()}}
  | {:error, term()}

system()

@type system() :: :gps | :glonass | :galileo | :beidou | :qzss

Functions

changed?(diff)

Returns true when a constellation diff has any findings.

diff(previous, current)

Compare two constellation catalog snapshots by {system, prn} identity.

The result separates added/removed PRNs from identity/status changes on a PRN that exists in both snapshots. Assumes each input has at most one record per {system, prn}; run validate/1 first on hand-edited catalogs.

iex> previous = [
...>   %Sidereon.GNSS.Constellation.Record{
...>     system: :gps, prn: 3, svn: 69, norad_id: 40294, sp3_id: "G03",
...>     active?: true, usable?: true, source: %{}
...>   }
...> ]
iex> current = [%{hd(previous) | norad_id: 99999, active?: false}]
iex> diff = Sidereon.GNSS.Constellation.diff(previous, current)
iex> [norad] = diff.norad_reassigned
iex> {norad.system, norad.prn, norad.from, norad.to}
{:gps, 3, 40294, 99999}
iex> [activity] = diff.activity_changed
iex> {activity.system, activity.prn, activity.from, activity.to}
{:gps, 3, true, false}
iex> Sidereon.GNSS.Constellation.changed?(diff)
true

from_celestrak_json(json, system \\ :gps)

@spec from_celestrak_json(String.t(), system()) ::
  {:ok, [Sidereon.GNSS.Constellation.Record.t()]} | error()

Build records for system from a JSON OMM feed.

This is the string-input sibling of from_celestrak_omm/2; decoded entries are still resolved by the shared Rust core.

from_celestrak_json_lenient(json, system \\ :gps)

@spec from_celestrak_json_lenient(String.t(), system()) ::
  {:ok, Sidereon.GNSS.Constellation.Catalog.t()} | error()

Build a lenient catalog from a JSON OMM feed.

This is the string-input sibling of from_celestrak_omm_lenient/2.

from_celestrak_omm(system, omms)

@spec from_celestrak_omm(system(), [map()]) ::
  {:ok, [Sidereon.GNSS.Constellation.Record.t()]} | error()

Build records for system from CelesTrak OMM/JSON maps, failing on the first entry whose OBJECT_NAME does not resolve to a PRN for system.

CelesTrak does not publish SVN, so records built from this source alone have svn: nil. Records are returned sorted by {system, prn}.

from_celestrak_omm_lenient(json)

@spec from_celestrak_omm_lenient(String.t()) ::
  {:ok, Sidereon.GNSS.Constellation.Catalog.t()} | error()

Build records for system from CelesTrak OMM/JSON maps, skipping (rather than aborting on) entries whose OBJECT_NAME does not resolve to a PRN for system.

The lenient sibling of from_celestrak_omm/2: every OMM that resolves becomes a Record; every entry that does not (an other-constellation satellite in a combined gnss feed, or a freshly launched vehicle whose name does not yet resolve) is collected into Catalog.skipped with its identity. Resolvable records are returned sorted by {system, prn}.

Leniency covers identity resolution only: an entry missing a valid NORAD_CAT_ID still aborts with {:error, reason}.

Examples

iex> omms = [
...>   %{"OBJECT_NAME" => "GPS BIIF-8  (PRN 03)", "NORAD_CAT_ID" => 40294},
...>   %{"OBJECT_NAME" => "GSAT0210 (GALILEO 13)", "NORAD_CAT_ID" => 41859}
...> ]
iex> {:ok, catalog} = Sidereon.GNSS.Constellation.from_celestrak_omm_lenient(:gps, omms)
iex> Enum.map(catalog.records, & &1.sp3_id)
["G03"]
iex> Enum.map(catalog.skipped, &{&1.object_name, &1.norad_id})
[{"GSAT0210 (GALILEO 13)", 41859}]

from_celestrak_omm_lenient(json, system)

@spec from_celestrak_omm_lenient(String.t(), system()) ::
  {:ok, Sidereon.GNSS.Constellation.Catalog.t()} | error()
@spec from_celestrak_omm_lenient(system(), [map()]) ::
  {:ok, Sidereon.GNSS.Constellation.Catalog.t()} | error()

galileo_prn_for_gsat(gsat)

@spec galileo_prn_for_gsat(integer()) :: integer() | nil

Resolve a Galileo GSATdddd build id to its SVID/PRN (E-number), or nil when no SVID is assigned yet.

iex> Sidereon.GNSS.Constellation.galileo_prn_for_gsat(210)
1

glonass_fdma_channel(slot)

@spec glonass_fdma_channel(integer()) :: integer() | nil

Resolve a GLONASS orbital slot (1..24) to its FDMA L1/L2 frequency-channel number k, or nil for a slot outside the operational range.

iex> Sidereon.GNSS.Constellation.glonass_fdma_channel(1)
1
iex> Sidereon.GNSS.Constellation.glonass_fdma_channel(2)
-4
iex> Sidereon.GNSS.Constellation.glonass_fdma_channel(0)
nil

glonass_slot_for_number(number)

@spec glonass_slot_for_number(integer()) :: integer() | nil

Resolve a GLONASS (Uragan) number to its orbital slot (1..24), or nil when the number is not in the published constellation table.

iex> Sidereon.GNSS.Constellation.glonass_slot_for_number(730)
1

merge_navcen(records, statuses)

Merge NAVCEN status rows into normalized records by PRN.

NAVCEN does not publish NORAD ids, so CelesTrak stays the identity base. When a PRN is present in both sources and the block types are compatible, this fills svn, updates usable?, and records the NAVCEN provenance under source.navcen. A NAVCEN row that matches the PRN but carries an incompatible block type (a PRN transition) is recorded under source.navcen_conflict.

parse_navcen_html(html)

@spec parse_navcen_html(String.t()) ::
  {:ok, [Sidereon.GNSS.Constellation.NavcenStatus.t()]} | error()

Parse NAVCEN's GPS constellation status HTML.

Returns status rows keyed by PRN/SVN; merge them into CelesTrak records with merge_navcen/2.

sp3_id(system, prn)

@spec sp3_id(system(), pos_integer()) :: String.t()

Render the canonical SP3/RINEX satellite token for a supported GNSS PRN.

iex> Sidereon.GNSS.Constellation.sp3_id(:gps, 7)
"G07"
iex> Sidereon.GNSS.Constellation.sp3_id(:galileo, 7)
"E07"

to_csv(records, opts \\ [])

Export records as the compact mapping CSV:

prn,norad_cat_id,active,sp3_id

The active column is true only when record.active? and record.usable? are both true. Records are sorted by {system, prn}.

Options

  • :booleans - how the active column is rendered: :lower (default: true/false) or :title (True/False, for a pandas consumer).

valid?(report)

Returns true when a validation report has no findings.

validate(records)

Validate catalog identity without an SP3 product.

Reports duplicate PRNs, duplicate NORAD ids, and PRNs that are inactive or unusable according to the normalized records.

validate_sp3(records, sp3)

Validate catalog identity against SP3 satellite ids.

The second argument may be a loaded %Sidereon.GNSS.SP3{} or a list of SP3/RINEX satellite tokens. missing_sp3_ids reports active+usable catalog ids absent from the product; extra_sp3_ids reports product ids absent from the active+usable catalog, restricted to the systems the catalog covers.

validate_sp3!(records, sp3)

@spec validate_sp3!(
  [Sidereon.GNSS.Constellation.Record.t()],
  Sidereon.GNSS.SP3.t() | [String.t()]
) ::
  :ok

Validate against SP3 satellite ids and raise unless the catalog is clean.

A build-time gate: returns :ok when the catalog has no findings, otherwise raises ArgumentError describing them. Intended for catalog-build / automation steps, not the positioning runtime.