Satellite catalog (IGS SINEX metadata)¶
Why satellite metadata matters for GNSS-T¶
GNSS-Transmissometry derives vegetation properties from the attenuation of satellite signals passing through a canopy. Several satellite-level properties directly affect signal interpretation:
- Transmit power (TX): different satellite generations broadcast at different power levels (in the IGS file, for example GPS-IIF: 240 W, GPS-IIIA: 300 W). When computing transmittance as the ratio of canopy-to-reference SNR, the absolute power cancels, but mixed constellations and satellite replacements can introduce systematic biases if not accounted for.
- PRN reassignments: a PRN code (for example
G01) is not permanently bound to one satellite vehicle. When a satellite is decommissioned and its PRN reassigned to a new vehicle, the TX power, antenna pattern, and orbital characteristics change. A long time series that spans a reassignment can show a step that is not in the vegetation;reassignments_in_rangefinds them. - Orbital plane and slot: the satellite's position in the constellation determines its sky track over the receiver site, which affects the polar angle distribution and therefore the VOD sampling geometry.
The IGS SINEX catalog provides all of this information in a single, authoritative,
machine-readable file. canvodpy run does not use it: the catalog is a tool for
your own analysis (for example enrich_dataset on data read from a store).
Overview¶
The SatelliteCatalog class parses the IGS igs_satellite_metadata.snx SINEX file:
a single authoritative source of satellite metadata maintained by the IGS. It provides time-aware queries for all GNSS constellations:
GPS, GLONASS, Galileo, BeiDou, QZSS, and IRNSS. SBAS satellites are not in the
SINEX file; their SV list (S01–S99, \(nn = \text{PRN} - 100\)) comes from the RINEX
specification.
Offline-first design
SatelliteCatalog never fails without internet. A bundled fallback copy
ships with the package. Fresh copies are downloaded automatically when online
and cached locally.
What's in the SINEX file?¶
The file has eight SATELLITE/ blocks; the catalog reads the six below (not COM and ECCENTRICITY):
| SINEX Block | Data | Example |
|---|---|---|
SATELLITE/IDENTIFIER |
SVN, COSPAR ID, NORAD catalog number, block type, launch date | G063, 2011-036A, GPS-IIF |
SATELLITE/PRN |
PRN \(\leftrightarrow\) SVN assignments with validity periods | G01 \(\rightarrow\) G080 from 2024-12-17 to open |
SATELLITE/TX_POWER |
Transmit power (Watts) per SVN | G063: 240 W |
SATELLITE/MASS |
Satellite mass (kg) per SVN | G063: 1633.0 kg |
SATELLITE/FREQUENCY_CHANNEL |
GLONASS FDMA channel numbers per SVN | R730: channel 1 |
SATELLITE/PLANE |
Orbital plane and slot assignments | G063: plane 4, slot D2A |
Loading the catalog¶
Discovery chain¶
SatelliteCatalog.load() searches for the file in this order:
1. Explicit search_dirs (e.g. config aux_data_dir)
2. ~/.cache/canvod/
3. Download from IGS (if online and the cached copy is older than
`max_age_days`, default 7)
4. Bundled fallback (always works, ships with package)
from canvod.readers.gnss_specs import SatelliteCatalog
catalog = SatelliteCatalog.load()
catalog = SatelliteCatalog.load(allow_download=False)
catalog = SatelliteCatalog.from_file("/path/to/igs_satellite_metadata.snx")
catalog = SatelliteCatalog.load(
search_dirs=[Path("/data/aux/")],
max_age_days=14,
)
Placing the file manually
Drop a fresh copy of igs_satellite_metadata.snx into ~/.cache/canvod/,
or into a directory you pass as search_dirs.
Querying¶
All time-varying queries take an on_date parameter to resolve validity periods.
PRN ↔ SVN mapping¶
PRN codes (for example G01) are not permanent; they can be reassigned to different
satellite vehicles over time. The catalog tracks the full assignment history.
from datetime import date
catalog = SatelliteCatalog.load()
# What satellite vehicle was behind G01 on 2024-01-01?
svn = catalog.prn_to_svn("G01", date(2024, 1, 1)) # → "G063"
# Reverse lookup
prn = catalog.svn_to_prn("G063", date(2024, 1, 1)) # → "G01" (None from 2024-04-12 on)
# Full assignment history for a PRN
history = catalog.prn_history("G01")
for assignment in history:
print(f" {assignment.svn}: {assignment.start} → {assignment.end or 'active'}")
Reassignment detection¶
# Were there any PRN reassignments in my processing window?
changes = catalog.reassignments_in_range("G01", date(2020, 1, 1), date(2025, 12, 31))
for r in changes:
print(f" {r.prn}: {r.old_svn} → {r.new_svn} on {r.new_start}")
Why reassignments matter
If a PRN is reassigned during your observation period, the satellite behind that PRN changed, with different transmit power, antenna pattern, and orbital characteristics. Time series continuity assumptions break.
Active PRNs per constellation¶
# All GPS satellites active on a given date
gps_prns = catalog.active_prns("G", date(2025, 1, 1)) # → ["G01", "G02", ...]
# Galileo
gal_prns = catalog.active_prns("E", date(2025, 1, 1)) # → ["E01", "E02", ...]
Satellite metadata¶
# Block type (satellite generation)
block = catalog.satellite_block("G063") # → "GPS-IIF"
# Transmit power
power = catalog.tx_power("G063", date(2025, 1, 1)) # → 240 (Watts)
# Mass
mass = catalog.mass("G063", date(2025, 1, 1)) # → 1633.0 (kg)
# GLONASS frequency channel
channel = catalog.glonass_channel("R730", date(2025, 1, 1)) # → 1
# Orbital plane and slot
plane, slot = catalog.plane_and_slot("G063", date(2025, 1, 1)) # → ("4", "D2A")
Combined metadata lookup¶
# Get everything for a PRN on a date
meta = catalog.get_prn_metadata("G01", date(2024, 1, 1))
# → {
# "prn": "G01", "svn": "G063", "block": "GPS-IIF",
# "cospar_id": "2011-036A", "satcat": 37753,
# "comment": "Launched 2011-07-16; NAVSTAR 66",
# "tx_power_watts": 240, "mass_kg": 1633.0,
# "plane": "4", "slot": "D2A", "glonass_channel": None,
# }
Polars DataFrame export¶
The catalog can be exported as a Polars DataFrame for interactive exploration in marimo notebooks.
df = catalog.to_dataframe(on_date=date(2025, 1, 1))
# Columns: prn, svn, constellation, block, cospar_id, satcat,
# tx_power_watts, mass_kg, plane, slot, glonass_channel, launch
df = catalog.to_dataframe()
# Columns: prn, svn, constellation, block, start, end
# One row per PRN assignment (all time periods)
Quick analysis in marimo
import marimo as mo
df = catalog.to_dataframe(on_date=date(2025, 1, 1))
# TX power by constellation
mo.ui.table(
df.group_by("constellation").agg(
pl.col("tx_power_watts").mean().alias("mean_power_W"),
pl.col("tx_power_watts").min().alias("min_power_W"),
pl.col("tx_power_watts").max().alias("max_power_W"),
pl.len().alias("count"),
)
)
Dataset enrichment¶
enrich_dataset() adds satellite metadata as sid-level coordinates on an
existing xarray Dataset. This attaches SVN, block type, TX power, mass, plane,
and slot to every signal identifier.
catalog = SatelliteCatalog.load()
# ds has dims (epoch, sid) from any reader
enriched = catalog.enrich_dataset(ds, on_date=date(2025, 1, 1))
# New coordinates on the sid dimension:
enriched.coords["svn"] # "G063", "E210", ...
enriched.coords["block"] # "GPS-IIF", "GAL-FOC", ...
enriched.coords["tx_power_watts"] # 240.0, 265.0, ...
enriched.coords["mass_kg"] # 1633.0, 733.0, ...
enriched.coords["plane"] # "4", ... (as written in the SINEX file)
enriched.coords["slot"] # "D2A", ...
Date inference
If on_date is not provided, it is inferred from the first epoch in the
dataset. Unknown SIDs (not in the catalog) get empty strings / NaN values.
Use case: filter by satellite generation¶
# Select only GPS-III satellites (higher TX power)
gps3_mask = enriched.coords["block"].str.startswith("GPS-III")
gps3_data = enriched.sel(sid=gps3_mask)
Integration with constellations¶
Each constellation class has an update_svs_from_catalog() method that
replaces the static SV list with the actual active PRNs from the catalog:
from canvod.readers.gnss_specs.constellations import GPS
gps = GPS()
print(len(gps.svs)) # 32 (static default)
gps.update_svs_from_catalog(on_date=date(2025, 1, 1))
print(len(gps.svs)) # the PRNs active on that date (32 on 2025-01-01)
Summary of catalog contents¶
catalog = SatelliteCatalog.load()
print(catalog.summary())
# counts change with every IGS update; e.g.
# {
# "total_svns": 294,
# "constellations": {"GPS": 80, "GLONASS": 101, "Galileo": 36, "BeiDou": 60,
# "QZSS": 7, "IRNSS": 10},
# "prn_assignments": 399,
# "tx_power_records": 229,
# "mass_records": 505,
# "frequency_channels": 149,
# "plane_slots": 259,
# }
Data classes reference¶
| Class | Fields | Description |
|---|---|---|
SatelliteIdentity |
svn, cospar_id, satcat, block, comment |
Static satellite info |
PrnAssignment |
svn, prn, start, end |
PRN \(\leftrightarrow\) SVN with validity period |
TxPowerRecord |
svn, power_watts, start, end, comment |
TX power with validity |
MassRecord |
svn, mass_kg, start, end, comment |
Mass with validity |
FrequencyChannel |
svn, channel, start, end, comment |
GLONASS FDMA channel |
PlaneSlot |
svn, plane, slot, start, end, comment |
Orbital plane and slot |
Reassignment |
prn, old_svn, new_svn, old_end, new_start |
PRN reassignment event |
References¶
- IGS Satellite Metadata SINEX
- Steigenberger, P., Thoelert, S., Montenbruck, O. (2018). "GNSS satellite transmit power and its impact on orbit determination." Journal of Geodesy, 92(6), 609–624. doi:10.1007/s00190-017-1082-2
- IGS SINEX format specification v2.02