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.
GPS is supported first. CelesTrak gps-ops OMM/JSON is the base source for
current NORAD catalog ids and PRN assignments; the PRN is parsed from
OBJECT_NAME and rendered as the SP3/RINEX id ("G13"). NAVCEN's GPS
constellation page can be parsed and merged as an optional status overlay for
SVN and NANU usability details.
Examples
iex> omms = [
...> %{"OBJECT_NAME" => "GPS BIIF-8 (PRN 03)", "NORAD_CAT_ID" => 40294}
...> ]
iex> {:ok, [record]} = Orbis.GNSS.Constellation.from_celestrak_omm(omms)
iex> {record.system, record.prn, record.norad_id, record.sp3_id}
{:gps, 3, 40294, "G03"}
iex> record = %Orbis.GNSS.Constellation.Record{
...> system: :gps,
...> prn: 3,
...> svn: nil,
...> norad_id: 40294,
...> sp3_id: "G03",
...> active?: true,
...> usable?: true,
...> source: %{}
...> }
iex> Orbis.GNSS.Constellation.to_csv([record])
"prn,norad_cat_id,active,sp3_id\n3,40294,true,G03\n"Live fetching is available through fetch_gps/1, but tests and production
pipelines that need deterministic behavior should use from_celestrak_omm/1,
parse_navcen_html/1, and merge_navcen/2 with cached bytes.
Summary
Functions
Returns true when a constellation diff has any findings.
Compare two constellation catalog snapshots by {system, prn} identity.
Fetch the current GPS catalog from public sources.
Build GPS records from CelesTrak gps-ops OMM/JSON maps.
Classify one catalog record's health state.
Build a deterministic health timeline from timestamped catalog snapshots.
Convert a health timeline to a versioned, JSON-friendly map.
Merge NAVCEN status rows into normalized GPS 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
Functions
@spec changed?(Orbis.GNSS.Constellation.Diff.t()) :: boolean()
Returns true when a constellation diff has any findings.
@spec diff([Orbis.GNSS.Constellation.Record.t()], [ Orbis.GNSS.Constellation.Record.t() ]) :: Orbis.GNSS.Constellation.Diff.t()
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.
diff/2 assumes each input has at most one record per {system, prn}. Run
validate/1 on both snapshots first when ingesting external or hand-edited
catalogs, and treat duplicate findings as malformed input rather than a
constellation change.
iex> previous = [
...> %Orbis.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 = Orbis.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> Orbis.GNSS.Constellation.changed?(diff)
true
@spec fetch_gps(keyword()) :: {:ok, [Orbis.GNSS.Constellation.Record.t()]} | error()
Fetch the current GPS catalog from public sources.
CelesTrak gps-ops OMM/JSON is always fetched and used as the base identity
source. By default, NAVCEN's GPS constellation table is also fetched and merged
as a status/SVN overlay.
Options:
:include_navcen- fetch and merge NAVCEN status rows (default:true):navcen_html- use this already-fetched NAVCEN HTML instead of network access; impliesinclude_navcen: true:timeout_ms- NAVCEN request timeout when fetching the overlay
Returns {:ok, [Record.t()]} or a tagged {:error, reason}.
@spec from_celestrak_omm([map()]) :: {:ok, [Orbis.GNSS.Constellation.Record.t()]} | error()
Build GPS records from CelesTrak gps-ops OMM/JSON maps.
CelesTrak does not publish SVN in this feed, so records built from this source
alone have svn: nil.
@spec health_state(Orbis.GNSS.Constellation.Record.t()) :: Orbis.GNSS.Constellation.HealthInterval.state()
Classify one catalog record's health state.
This is intentionally small policy: explicit :health_state metadata wins
when present, otherwise active+usable is healthy, active+unusable is unhealthy,
and inactive records are unknown.
iex> r = %Orbis.GNSS.Constellation.Record{
...> system: :gps,
...> prn: 3,
...> svn: nil,
...> norad_id: 40294,
...> sp3_id: "G03",
...> active?: true,
...> usable?: false,
...> source: %{}
...> }
iex> Orbis.GNSS.Constellation.health_state(r)
:unhealthy
@spec health_timeline( [tuple() | map()], keyword() ) :: {:ok, Orbis.GNSS.Constellation.HealthTimeline.t()} | {:error, term()}
Build a deterministic health timeline from timestamped catalog snapshots.
Snapshots may be {epoch, records} tuples or %{epoch: epoch, records: records}
maps. Epochs are normalized to UTC NaiveDateTime; DateTime inputs are
shifted to UTC before conversion.
Options:
:as_of- close the final intervals at this epoch.:stale_after_s- mark the timeline stale whenas_of - latest_epochexceeds this many seconds.
changes contains diff/2 reports plus derived health-state transitions for
each snapshot transition. Duplicate PRNs are still the caller's
responsibility: validate snapshots before building a watcher timeline.
iex> base = %Orbis.GNSS.Constellation.Record{
...> system: :gps,
...> prn: 3,
...> svn: 69,
...> norad_id: 40294,
...> sp3_id: "G03",
...> active?: true,
...> usable?: true,
...> source: %{}
...> }
iex> {:ok, timeline} =
...> Orbis.GNSS.Constellation.health_timeline([
...> {~N[2026-06-09 00:00:00], [base]},
...> {~N[2026-06-09 01:00:00], [%{base | usable?: false}]}
...> ])
iex> Enum.map(timeline.intervals, &{&1.prn, &1.state, &1.from, &1.to})
[
{3, :healthy, ~N[2026-06-09 00:00:00], ~N[2026-06-09 01:00:00]},
{3, :unhealthy, ~N[2026-06-09 01:00:00], nil}
]
@spec health_timeline_to_map(Orbis.GNSS.Constellation.HealthTimeline.t()) :: map()
Convert a health timeline to a versioned, JSON-friendly map.
The map is intended for watcher state files or notifications; rebuild a fresh timeline from source snapshots when possible.
@spec sp3_id(:gps, pos_integer()) :: String.t()
Render the canonical SP3/RINEX satellite token for a supported GNSS PRN.
iex> Orbis.GNSS.Constellation.sp3_id(:gps, 7)
"G07"
@spec to_csv( [Orbis.GNSS.Constellation.Record.t()], keyword() ) :: String.t()
Export records as the compact mapping CSV:
prn,norad_cat_id,active,sp3_idThe active column is true only when record.active? and record.usable?
are both true.
Options
:booleans- how theactivecolumn is rendered::lower(default —true/false, the conventional CSV form) or:title(True/False, for a consumer such as pandas that reads the column as Python booleans).
@spec valid?(Orbis.GNSS.Constellation.Validation.t()) :: boolean()
Returns true when a validation report has no findings.
@spec validate([Orbis.GNSS.Constellation.Record.t()]) :: Orbis.GNSS.Constellation.Validation.t()
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.
@spec validate_sp3( [Orbis.GNSS.Constellation.Record.t()], Orbis.GNSS.SP3.t() | [String.t()] ) :: Orbis.GNSS.Constellation.Validation.t()
Validate catalog identity against SP3 satellite ids.
The second argument may be a loaded %Orbis.GNSS.SP3{} or a list of SP3/RINEX
satellite tokens. missing_sp3_ids reports active+usable catalog GPS ids that
are absent from the product; extra_sp3_ids reports GPS ids present in the SP3
product but absent from the active+usable catalog.
@spec validate_sp3!( [Orbis.GNSS.Constellation.Record.t()], Orbis.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 validate_sp3/2 reports no findings,
otherwise raises ArgumentError describing them — for example an
active-and-usable catalog PRN that is missing from a current SP3 product (a
stale-active satellite). Intended for catalog-build / automation steps, not the
positioning runtime.