Skip to content

canvod.auxiliary API reference

SP3 ephemeris and CLK clock correction processing, interpolation, coordinate transformations, and the GNSS product registry.

Package

canvod-aux: Auxiliary data augmentation for GNSS VOD analysis

Handles downloading, parsing, and interpolating SP3 ephemerides and clock corrections for GNSS satellite data processing.

Sp3File

Bases: AuxFile

Handler for SP3 orbit files with multi-product support.

Now supports all IGS analysis centers via product registry: COD, GFZ, ESA, JPL, IGS, WHU, GRG, SHA

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True.

Attributes:

Name Type Description
date str

String in YYYYDOY format.

agency str

Agency code (e.g., "COD", "GFZ", "ESA").

product_type str

Product type ("final", "rapid").

ftp_server str

Base URL for downloads.

local_dir Path

Local storage directory.

add_velocities bool | None, default True

Whether to compute velocities.

dimensionless bool | None, default True

Whether to strip units (store magnitudes only).

product_spec (ProductSpec | None, optional)

Product specification resolved from the registry.

__post_init__()

Initialize with product validation.

get_interpolation_strategy()

Get appropriate interpolation strategy for SP3 files.

candidate_filenames()

SP3 file names, one per sampling the product has been published with.

Pattern: {PREFIX}{YYYYDOY}0000_01D_ORB.SP3

Example: COD0MGXFIN_20240150000_01D_05M_ORB.SP3

download_aux_file(filename)

Download one SP3 file, trying all servers from the product registry.

Servers are tried in priority order from products.toml. Auth-required servers are skipped when no credentials are configured. Warnings are printed when falling back to alternate servers.

Parameters:

Name Type Description Default
filename str

One of :meth:candidate_filenames.

required

Raises:

Type Description
RuntimeError

If download fails from all available servers.

ValueError

If GPS week calculation fails.

read_file()

Read and validate SP3 file.

Returns:

Type Description
Dataset

Dataset with satellite positions (X, Y, Z) in meters.

Raises:

Type Description
FileNotFoundError

If file does not exist.

ValueError

If validation fails.

join_days(previous, following, margin=DAY_JOIN_MARGIN)

Like :meth:AuxFile.join_days, with the velocities recomputed.

The velocities of each file have one-sided differences at its first and last epoch; recomputed on the joined data, the day's first and last epochs get central differences.

compute_velocity(ds)

Compute satellite velocities from position data.

Uses central differences for interior points, forward/backward differences for endpoints, with the actual epoch spacing (the days joined by :meth:join_days may come with different samplings).

Parameters:

Name Type Description Default
ds Dataset

Dataset with X, Y, Z coordinates.

required

Returns:

Type Description
Dataset

Dataset augmented (or updated) with Vx, Vy, Vz velocities.

ClkFile

Bases: AuxFile

Handler for GNSS clock files in CLK format.

This class reads and processes clock offset files containing satellite clock corrections. It handles the parsing of CLK format files and provides the data in xarray Dataset format.

Supports multiple analysis centers via product registry with proper FTP paths and filename conventions.

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True.

Attributes:

Name Type Description
date str

String in YYYYDOY format representing the start date.

agency str

Analysis center identifier (e.g., "COD", "GFZ").

product_type str

Product type ("final" or "rapid").

ftp_server str

Base URL for file downloads.

local_dir Path

Local storage directory.

dimensionless bool | None, default True

If True, outputs magnitude-only values (no units attached).

__post_init__()

Initialize CLK file handler.

get_interpolation_strategy()

Get appropriate interpolation strategy for CLK files.

candidate_filenames()

CLK file names, one per sampling the product has been published with.

Filename format: {PREFIX}{YYYYDOY}0000_01D_CLK.CLK

Raises:

Type Description
UnknownProductError

If agency/product combination not in registry.

ValueError

If the product publishes no clock files.

download_aux_file(filename)

Download one CLK file, trying all servers from the product registry.

Servers are tried in priority order from products.toml. Auth-required servers are skipped when no credentials are configured. Warnings are printed when falling back to alternate servers.

Parameters:

Name Type Description Default
filename str

One of :meth:candidate_filenames.

required

Raises:

Type Description
RuntimeError

If file cannot be downloaded from any available server.

ValueError

If GPS week calculation fails.

read_file()

Read and parse CLK file into xarray Dataset.

Uses modular parser for data extraction and validator for quality checks. The clock bias is in seconds (RINEX clock 3.04, Table A16).

Returns:

Type Description
Dataset

Clock offsets with dimensions (epoch, sv). Values are in seconds (or dimensionless if specified).

AuxFile

Bases: ABC

Abstract base class for GNSS auxiliary files (SP3, CLK, IONEX, etc.).

This class provides two ways to create instances: 1. from_datetime_date(): Create from a datetime.date object and metadata 2. from_file(): Create directly from an existing file path

The class handles both newly downloaded files and existing local files, maintaining consistent behavior regardless of how the instance is created.

FTP Server Configuration:

  • user_email: Optional email for NASA CDDIS authentication
  • If None: Uses ESA FTP server exclusively (no authentication required)
  • If provided: Enables NASA CDDIS as fallback server (requires registration)
  • To enable CDDIS, set processing.credentials.nasa_earthdata_acc_mail in config/canvod-settings.yaml

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True, and it uses ABC to define required subclass hooks.

data property

Access the file's data, loading it if necessary.

__post_init__()

Initialize after dataclass creation.

Sets up paths, downloader, and verifies local file existence.

from_datetime_date(date, agency, product_type, ftp_server, local_dir, **kwargs) classmethod

Create an AuxFile instance from a datetime.date.

Parameters:

Name Type Description Default
date date

Date for the desired auxiliary file.

required
agency str

Agency providing the data (e.g., "COD", "IGS").

required
product_type str

Product type ("final" or "rapid").

required
ftp_server str

Base URL for file downloads.

required
local_dir Path

Directory for storing files locally.

required
**kwargs Any

Extra keyword arguments for subclass construction.

{}

Returns:

Type Description
AuxFile

A new instance of the AuxFile subclass.

from_file(fpath, **kwargs) classmethod

Create an AuxFile instance from an existing file path.

Parameters:

Name Type Description Default
fpath Path

Path to the existing GNSS file.

required
**kwargs Any

Extra keyword arguments for subclass construction.

{}

Returns:

Type Description
AuxFile

A new instance of the AuxFile subclass.

Raises:

Type Description
FileNotFoundError

If the specified file does not exist.

UnknownProductError

If no registry product has the prefix of the file name.

download_file(url, destination, file_info=None)

Download a file using the configured downloader.

Parameters:

Name Type Description Default
url str

Download URL.

required
destination Path

Local file destination.

required
file_info dict

Extra info passed to the downloader.

None

Returns:

Type Description
Path

Path to the downloaded file.

download_with_fallback(ftp_path, destination, file_info=None, product_spec=None)

Download a file, trying all servers from the product spec in priority order.

Iterates through the product's FTP server list (from products.toml), skipping servers that require authentication when no credentials are configured. Warns when falling back to an alternate server. Raises a clear error when all servers fail.

Parameters:

Name Type Description Default
ftp_path str

Server-relative path (e.g. /gnss/products/2345/FILE.gz).

required
destination Path

Local file destination.

required
file_info dict

Extra context passed to the downloader.

None
product_spec ProductSpec

Product specification with ftp_servers list. If None, falls back to single-server download using self.ftp_server.

None

Returns:

Type Description
Path

Path to the downloaded file.

Raises:

Type Description
RuntimeError

If download fails from all available servers.

read_file() abstractmethod

Read and parse the auxiliary file.

Returns:

Type Description
Dataset

Parsed dataset representation of the file.

get_interpolation_strategy() abstractmethod

Get the interpolation strategy for this file type.

join_days(previous, following, margin=DAY_JOIN_MARGIN)

This file's data with the end of the previous and the start of the next day.

A daily file can end before midnight (e.g. a 15 min orbit file at 23:45), and its first and last epochs have neighbors on one side only. With margin of the neighboring days on both sides, the whole day lies inside the data: nothing is extrapolated.

Parameters:

Name Type Description Default
previous AuxFile

The same product for the previous and the next day.

required
following AuxFile

The same product for the previous and the next day.

required
margin timedelta64

How much of each neighboring day is kept.

DAY_JOIN_MARGIN

Returns:

Type Description
Dataset

The epochs of this file, plus those of the neighbors within margin of this day that this file lacks (e.g. this file's 24:00 epoch wins over the next file's 00:00), sorted by epoch. Satellites missing in a file are NaN there. Attrs of this file.

check_file_exists()

Return the local file, downloading it if needed.

The candidate names (one per sampling the product has been published with) are tried in order: first a local copy of any of them, then a download of each in turn.

Returns:

Type Description
Path

Local file path.

Raises:

Type Description
RuntimeError

If no candidate could be downloaded; the message names every file tried.

candidate_filenames() abstractmethod

File names of this file, in the order they are tried.

download_aux_file(filename) abstractmethod

Download one candidate file into local_dir.

Parameters:

Name Type Description Default
filename str

One of :meth:candidate_filenames.

required

Interpolator

Bases: ABC

Abstract base class for interpolation strategies.

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True and uses ABC to define required interpolation hooks.

interpolate(ds, target_epochs) abstractmethod

Interpolate dataset to match target epochs.

Parameters:

Name Type Description Default
ds Dataset

Source dataset with (epoch, sid) dimensions.

required
target_epochs ndarray

Target epoch grid (datetime64).

required

Returns:

Type Description
Dataset

Interpolated dataset at target epochs.

to_attrs()

Convert interpolator to attrs-compatible dictionary.

InterpolatorConfig

Base class for interpolator configuration.

to_dict()

Convert config to dictionary for attrs storage.

DatasetMatcher

Match auxiliary datasets to a reference RINEX dataset temporally.

Handles temporal alignment of datasets with different sampling rates using appropriate interpolation strategies. The reference dataset (typically RINEX observations) remains unchanged while auxiliary datasets are interpolated to match its epochs.

The matcher: 1. Validates all datasets have required dimensions (epoch, sid) 2. Determines relative temporal resolutions 3. Applies appropriate interpolation: - Higher resolution aux → nearest neighbor - Lower resolution aux → specialized interpolator from metadata

Examples:

>>> from canvod.auxiliary.matching import DatasetMatcher
>>>
>>> matcher = DatasetMatcher()
>>> matched = matcher.match_datasets(
...     rinex_ds,
...     ephemerides=sp3_data,
...     clock=clk_data
... )
>>>
>>> # Auxiliary datasets now aligned to RINEX epochs
>>> len(matched['ephemerides'].epoch) == len(rinex_ds.epoch)
True
>>> len(matched['clock'].epoch) == len(rinex_ds.epoch)
True

Notes

  • Reference dataset should be the RINEX observations
  • Auxiliary datasets should have 'interpolator_config' in attrs
  • If no interpolator config, falls back to nearest neighbor
  • Temporal distance is tracked for quality assessment

match_datasets(reference_ds, **aux_datasets)

Match auxiliary datasets to reference dataset epochs.

Parameters:

Name Type Description Default
reference_ds Dataset

Primary dataset (usually RINEX observations) that defines the target epoch timeline. This dataset remains unchanged.

required
**aux_datasets dict[str, Dataset]

Named auxiliary datasets to align to reference epochs. Keys become the names in the returned dict. Example: ephemerides=sp3_data, clock=clk_data

{}

Returns:

Type Description
dict[str, Dataset]

Dictionary of matched auxiliary datasets, all aligned to reference_ds.epoch. Keys match the input **aux_datasets keys.

Raises:

Type Description
ValueError
  • If no auxiliary datasets provided
  • If datasets missing required dimensions
  • If interpolation config missing (warns, doesn't raise)

Examples:

>>> matcher = DatasetMatcher()
>>> matched = matcher.match_datasets(
...     rinex_ds,
...     ephemerides=sp3_data,
...     clock=clk_data
... )
>>> matched.keys()
dict_keys(['ephemerides', 'clock'])

ECEFPosition

Bases: BaseModel

Earth-Centered, Earth-Fixed (ECEF) position in meters.

ECEF is a Cartesian coordinate system with: - Origin at Earth's center of mass - X-axis pointing to 0° latitude, 0° longitude (Prime Meridian at Equator) - Y-axis pointing to 0° latitude, 90° East longitude - Z-axis pointing to North Pole

Parameters:

Name Type Description Default
x float

X coordinate in meters.

required
y float

Y coordinate in meters.

required
z float

Z coordinate in meters.

required

Raises:

Type Description
ValidationError

If any component is non-finite (NaN/inf), or the position is exactly (0, 0, 0) -- Earth's center, never a physically valid ECEF point for anything this codebase computes geometry relative to. This is deliberately a general sanity check (applies to satellite positions too, not just receivers) -- see from_ds_metadata() for the additional ground-station-specific plausibility check.

Examples:

>>> # From RINEX dataset metadata
>>> pos = ECEFPosition.from_ds_metadata(rinex_ds)
>>> print(f"X: {pos.x:.3f} m")
>>> # Manual creation
>>> pos = ECEFPosition(x=4194304.123, y=176481.234, z=4780013.456)
>>> lat, lon, alt = pos.to_geodetic()

to_geodetic()

Convert ECEF to geodetic coordinates.

Returns:

Type Description
tuple[float, float, float]

(latitude, longitude, altitude) where: - latitude: degrees [-90, 90] - longitude: degrees [-180, 180] - altitude: meters above WGS84 ellipsoid

from_ds_metadata(ds) classmethod

Extract ECEF position from RINEX dataset metadata.

Reads from standard RINEX header attributes.

Parameters:

Name Type Description Default
ds Dataset

RINEX dataset with position in attributes.

required

Returns:

Type Description
ECEFPosition

Receiver position in ECEF.

Raises:

Type Description
KeyError

If position attributes not found in dataset.

ValidationError

If the position is non-finite or exactly (0, 0, 0) (see ECEFPosition's own validator).

ValueError

If the position's ECEF magnitude falls outside the plausible ground-station band (_GROUND_STATION_MIN_RADIUS_M .. _GROUND_STATION_MAX_RADIUS_M, i.e. on/near the WGS84 ellipsoid) -- e.g. an unpopulated RINEX header defaulted to a position that happens to be finite and non-zero but still not a real ground station (this check is specific to this parse boundary, not a general ECEFPosition constraint -- see the class docstring).

Examples:

>>> from canvod.readers import Rnxv3Obs
>>> rnx = Rnxv3Obs(fpath="station.24o")
>>> ds = rnx.to_ds()
>>> pos = ECEFPosition.from_ds_metadata(ds)

GeodeticPosition dataclass

Geodetic (WGS84) position.

Parameters:

Name Type Description Default
lat float

Latitude in degrees [-90, 90].

required
lon float

Longitude in degrees [-180, 180].

required
alt float

Altitude in meters above WGS84 ellipsoid.

required

Examples:

>>> pos = GeodeticPosition(lat=48.208, lon=16.373, alt=200.0)
>>> print(f"Vienna: {pos.lat}°N, {pos.lon}°E, {pos.alt}m")

to_ecef()

Convert geodetic to ECEF coordinates.

Returns:

Type Description
ECEFPosition

Position in ECEF frame.

Preprocessing

Auxiliary-data preprocessing pipelines.

Composes the SID-space primitives from :mod:canvod.readers.preprocessing (sv->sid conversion, global SID padding, dtype/encoding normalization) into the two pipelines specific to auxiliary (SP3/CLK) data: preparing a dataset for Icechunk storage, and preparing one for interpolation.

The primitives themselves live in canvod-readers because they only depend on GNSS signal/constellation definitions (canvod.readers.gnss_specs), not on anything ephemeris- or interpolation-specific -- keeping them here made canvod-readers (which calls them from every reader's to_ds()) depend on canvod-auxiliary, which already depends on canvod-readers, a genuine import cycle that broke standalone canvod-readers installs.

prep_aux_ds(aux_ds, fill_value=np.nan, aggregate_glonass_fdma=True, keep_sids=None)

Preprocess auxiliary dataset before writing to Icechunk.

Performs complete 4-step preprocessing: 1. Convert sv → sid dimension 2. Pad to global sid list (all constellations) or filter to keep_sids 3. Normalize sid dtype to object 4. Strip _FillValue attributes

Parameters:

Name Type Description Default
aux_ds Dataset

Dataset with 'sv' dimension.

required
fill_value float

Fill value for missing entries.

np.nan
aggregate_glonass_fdma bool

Whether to aggregate GLONASS FDMA bands.

True
keep_sids list[str] | None

List of specific SIDs to keep. If None, keeps all possible SIDs.

None

Returns:

Type Description
Dataset

Fully preprocessed dataset ready for Icechunk or interpolation.

preprocess_aux_for_interpolation(aux_ds, fill_value=np.nan, full_preprocessing=False, aggregate_glonass_fdma=True)

Preprocess auxiliary dataset before interpolation.

Converts satellite vehicle (sv) dimension to Signal ID (sid) dimension, which is required for matching with RINEX observations after interpolation.

Parameters:

Name Type Description Default
aux_ds Dataset

Raw auxiliary dataset with 'sv' dimension.

required
fill_value float

Fill value for missing entries.

np.nan
full_preprocessing bool

If True, applies full 4-step preprocessing (pad_to_global_sid, normalize_sid_dtype, strip_fillvalue). If False, only converts sv → sid (sufficient for interpolation).

False
aggregate_glonass_fdma bool

Whether to aggregate GLONASS FDMA bands.

True

Returns:

Type Description
Dataset

Preprocessed dataset with 'sid' dimension.

Notes

This must be called BEFORE interpolation. The workflow is: 1. Load raw SP3/CLK data (sv dimension) 2. Convert sv → sid (this function) 3. Interpolate to target epochs 4. Match with RINEX data (sid dimension)

For most interpolation use cases, full_preprocessing=False is sufficient. Use full_preprocessing=True when preparing data for Icechunk storage.

Examples:

>>> # Load raw SP3 data
>>> sp3_data = Sp3File(...).to_dataset()
>>> sp3_data.dims
{'epoch': 96, 'sv': 32}
>>>
>>> # Preprocess before interpolation (minimal)
>>> sp3_preprocessed = preprocess_aux_for_interpolation(sp3_data)
>>> sp3_preprocessed.dims
{'epoch': 96, 'sid': 384}
>>>
>>> # Preprocess before Icechunk (full)
>>> sp3_preprocessed = preprocess_aux_for_interpolation(
...     sp3_data,
...     full_preprocessing=True,
... )
>>> sp3_preprocessed.dims
{'epoch': 96, 'sid': ~2000}  # Padded to all possible sids
>>>
>>> # Now interpolate
>>> sp3_interp = interpolator.interpolate(sp3_preprocessed, target_epochs)

Interpolation

Interpolation strategies for GNSS auxiliary data.

ClockConfig

Bases: InterpolatorConfig

Configuration for clock correction interpolation.

Attributes:

Name Type Description
window_size int, default 9

Window size for discontinuity detection.

jump_threshold float, default 1e-6

Threshold for detecting clock jumps (seconds).

ClockInterpolationStrategy

Bases: Interpolator[ClockConfig]

Piecewise linear interpolation for clock corrections.

Detects and handles discontinuities (clock jumps) properly.

Examples:

>>> from canvod.auxiliary.interpolation import (
...     ClockInterpolationStrategy, ClockConfig,
... )
>>>
>>> config = ClockConfig(window_size=9, jump_threshold=1e-6)
>>> interpolator = ClockInterpolationStrategy(config=config)
>>>
>>> # Interpolate to RINEX epochs
>>> clk_interp = interpolator.interpolate(clk_data, rinex_epochs)

interpolate(ds, target_epochs)

Interpolate clock corrections with discontinuity handling.

Interpolator

Bases: ABC

Abstract base class for interpolation strategies.

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True and uses ABC to define required interpolation hooks.

interpolate(ds, target_epochs) abstractmethod

Interpolate dataset to match target epochs.

Parameters:

Name Type Description Default
ds Dataset

Source dataset with (epoch, sid) dimensions.

required
target_epochs ndarray

Target epoch grid (datetime64).

required

Returns:

Type Description
Dataset

Interpolated dataset at target epochs.

to_attrs()

Convert interpolator to attrs-compatible dictionary.

InterpolatorConfig

Base class for interpolator configuration.

to_dict()

Convert config to dictionary for attrs storage.

Sp3Config

Bases: InterpolatorConfig

Configuration for SP3 ephemeris interpolation.

Attributes:

Name Type Description
use_velocities bool, default True

Use Hermite splines with satellite velocities if available.

fallback_method str, default 'linear'

Interpolation method when velocities are unavailable.

Sp3InterpolationStrategy

Bases: Interpolator[Sp3Config]

Hermite cubic spline interpolation for SP3 ephemeris data.

Uses satellite velocities (Vx, Vy, Vz) for higher accuracy. Falls back to linear interpolation if velocities unavailable.

Examples:

>>> from canvod.auxiliary.interpolation import Sp3InterpolationStrategy, Sp3Config
>>>
>>> config = Sp3Config(use_velocities=True, fallback_method='linear')
>>> interpolator = Sp3InterpolationStrategy(config=config)
>>>
>>> # Interpolate to RINEX epochs
>>> sp3_interp = interpolator.interpolate(sp3_data, rinex_epochs)

interpolate(ds, target_epochs)

Interpolate SP3 ephemeris to target epochs.

aux_epoch_grid(day_start, interval_s)

Epoch grid of one day: day_start plus multiples of interval_s.

Parameters:

Name Type Description Default
day_start datetime64

00:00 of the day.

required
interval_s float

Grid spacing in seconds (the sampling interval of the observations), resolved to the nanosecond.

required

Returns:

Type Description
ndarray

datetime64[ns] epochs within the day.

Raises:

Type Description
ValueError

If interval_s is not positive.

interpolate_aux_day(ephem_ds, clock_ds, target_epochs)

Interpolate one day of orbits (and clocks) onto target_epochs.

Orbits: cubic Hermite spline with the satellite velocities (:class:Sp3InterpolationStrategy). Clocks: piecewise linear between clock jumps (:class:ClockInterpolationStrategy). Each part records its interpolator in attrs["interpolator_config"].

Parameters:

Name Type Description Default
ephem_ds Dataset

Orbits with X, Y, Z and Vx, Vy, Vz.

required
clock_ds Dataset | None

Clock offsets, or None when clocks are not fetched.

required
target_epochs ndarray

Epoch grid, see :func:aux_epoch_grid.

required

Returns:

Type Description
Dataset

Interpolated orbits, merged with the interpolated clocks if given.

sampling_interval_from_epochs(epochs)

Sampling interval of observation epochs, in seconds.

The most common difference between consecutive epochs, so data gaps do not change the result.

Parameters:

Name Type Description Default
epochs ndarray

Observation epochs (datetime64), in time order.

required

Returns:

Type Description
float

Sampling interval in seconds.

Raises:

Type Description
ValueError

If there are fewer than two distinct epochs.

create_interpolator_from_attrs(attrs)

Recreate interpolator instance from dataset attributes.

Parameters:

Name Type Description Default
attrs dict

Dataset attributes containing interpolator_config.

required

Returns:

Type Description
Interpolator

Reconstructed interpolator instance.

Examples:

>>> # Save interpolator config in dataset
>>> ds.attrs['interpolator_config'] = interpolator.to_attrs()
>>>
>>> # Later, recreate interpolator
>>> interpolator = create_interpolator_from_attrs(ds.attrs)

Ephemeris (SP3)

Ephemeris (satellite orbit) data handling.

This module provides tools for reading, parsing, and validating satellite ephemeris data from SP3 format files, as well as the EphemerisProvider ABC for augmenting GNSS datasets with angular coordinates.

Sp3Parser

Parser for SP3 (Standard Product #3) orbit files.

Handles parsing of SP3 format files containing precise satellite orbit data. Implements optimized single-pass reading for performance.

Parameters:

Name Type Description Default
fpath Path

Path to SP3 file.

required
dimensionless bool

If True, strip units from output.

True

__init__(fpath, dimensionless=True)

Initialize SP3 parser.

parse()

Parse SP3 file to xarray Dataset.

Returns:

Type Description
Dataset

Dataset with satellite positions (X, Y, Z) in meters.

Raises:

Type Description
FileNotFoundError

If file does not exist.

ValueError

If file format is invalid.

AgencyEphemerisProvider

Bases: EphemerisProvider

SP3/CLK-based ephemeris from analysis centers.

Downloads final or rapid products from COD, ESA, IGS etc., interpolates via Hermite cubic splines, and computes theta/phi via ECEF→spherical coordinate transformation.

Parameters:

Name Type Description Default
agency str

Analysis center code ("COD", "ESA", "GFZ", "JPL").

'COD'
product_type str

Product type ("final" or "rapid").

'final'
aux_data_dir Path

Directory for cached aux Zarr files.

None
keep_sids list[str]

SID filter list.

None
store_radial_distance bool

Whether to keep r in the output.

False
fetch_clock bool

Whether to also download/interpolate CLK clock-correction files. If None, uses config.processing.aux_data.fetch_clock.

None

preprocess_day(date, site_config)

Download and read the SP3 (and CLK) products of one day.

The products are interpolated later, in :meth:augment_dataset, onto the day's epoch grid at the sampling interval of the dataset being augmented, the same grid canvodpy run uses (see :mod:canvod.auxiliary.interpolation.day_grid). The interpolated day is written once per sampling interval to aux_<date>_<interval>s.zarr in the returned directory.

Parameters:

Name Type Description Default
date str

Date in YYYYDOY format.

required
site_config Any

Site configuration (needs gnss_site_data_root, receiver info).

required

Returns:

Type Description
Path

Directory of the interpolated aux Zarr stores.

augment_dataset(ds, receiver_position)

Add theta/phi to dataset using preprocessed SP3/CLK data.

Parameters:

Name Type Description Default
ds Dataset

GNSS observation dataset.

required
receiver_position ECEFPosition

Receiver ECEF position.

required

Returns:

Type Description
Dataset

Augmented dataset with theta, phi (and optionally r).

Raises:

Type Description
RuntimeError

If preprocess_day() has not been called.

EphemerisProvider

Bases: ABC

Abstract base class for satellite ephemeris providers.

Implementations augment a GNSS dataset with angular coordinates (theta, phi) relative to a receiver position.

augment_dataset(ds, receiver_position) abstractmethod

Add theta and phi (and optionally r) to ds.

Parameters:

Name Type Description Default
ds Dataset

GNSS observation dataset with (epoch, sid) dims.

required
receiver_position ECEFPosition

Receiver ECEF coordinates.

required

Returns:

Type Description
Dataset

Dataset with theta, phi (and optionally r) added.

preprocess_day(date, site_config) abstractmethod

Download / prepare ephemeris data for one day.

Parameters:

Name Type Description Default
date str

Date in YYYYDOY format.

required
site_config Any

Site configuration object providing receiver info, data root, etc.

required

Returns:

Type Description
Path or None

Path to cached/preprocessed ephemeris data, or None if preparation is not needed (e.g. broadcast provider).

SbfBroadcastProvider

Bases: EphemerisProvider

Ephemeris from SBF SatVisibility broadcast data.

Transfers theta/phi directly from SBF metadata datasets, skipping external orbit/clock downloads entirely. Only works when the source format is SBF.

Only angles the receiver computed from the broadcast ephemeris are used (broadcast_angle_source == 2). Almanac-based angles become NaN: on the canVODpy test data (2025-001) they differ from the ephemeris-based angles by up to 0.26 deg for GLONASS and up to 104 deg for BeiDou.

Parameters:

Name Type Description Default
canopy_file Path

Path to canopy SBF file for reference receivers in shared-position mode. When provided, the canopy file's geometry overrides the reference file's own geometry.

None
canopy_reader_format str

Reader format for the canopy file. Only "sbf" carries SatVisibility geometry.

'sbf'
keep_sids list of str

SIDs to keep when reading canopy_file. Only saves work: the geometry is aligned to the observation SIDs either way.

None

preprocess_day(date, site_config)

No preprocessing needed for broadcast ephemeris.

augment_dataset(ds, receiver_position, *, aux_datasets=None)

Add theta/phi from SBF SatVisibility metadata.

Parameters:

Name Type Description Default
ds Dataset

SBF observation dataset.

required
receiver_position ECEFPosition

Receiver ECEF position (unused but required by ABC).

required
aux_datasets dict

Auxiliary datasets from reader.to_ds_and_auxiliary(). Must contain "sbf_obs" with theta/phi.

None

Returns:

Type Description
Dataset

Dataset with theta/phi added from SBF broadcast. Cells without an ephemeris-based angle are NaN.

Raises:

Type Description
ValueError

If no sbf_obs is available, it lacks the SatVisibility variables, or it holds no ephemeris-based angle for any observation.

Sp3File

Bases: AuxFile

Handler for SP3 orbit files with multi-product support.

Now supports all IGS analysis centers via product registry: COD, GFZ, ESA, JPL, IGS, WHU, GRG, SHA

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True.

Attributes:

Name Type Description
date str

String in YYYYDOY format.

agency str

Agency code (e.g., "COD", "GFZ", "ESA").

product_type str

Product type ("final", "rapid").

ftp_server str

Base URL for downloads.

local_dir Path

Local storage directory.

add_velocities bool | None, default True

Whether to compute velocities.

dimensionless bool | None, default True

Whether to strip units (store magnitudes only).

product_spec (ProductSpec | None, optional)

Product specification resolved from the registry.

__post_init__()

Initialize with product validation.

get_interpolation_strategy()

Get appropriate interpolation strategy for SP3 files.

candidate_filenames()

SP3 file names, one per sampling the product has been published with.

Pattern: {PREFIX}{YYYYDOY}0000_01D_ORB.SP3

Example: COD0MGXFIN_20240150000_01D_05M_ORB.SP3

download_aux_file(filename)

Download one SP3 file, trying all servers from the product registry.

Servers are tried in priority order from products.toml. Auth-required servers are skipped when no credentials are configured. Warnings are printed when falling back to alternate servers.

Parameters:

Name Type Description Default
filename str

One of :meth:candidate_filenames.

required

Raises:

Type Description
RuntimeError

If download fails from all available servers.

ValueError

If GPS week calculation fails.

read_file()

Read and validate SP3 file.

Returns:

Type Description
Dataset

Dataset with satellite positions (X, Y, Z) in meters.

Raises:

Type Description
FileNotFoundError

If file does not exist.

ValueError

If validation fails.

join_days(previous, following, margin=DAY_JOIN_MARGIN)

Like :meth:AuxFile.join_days, with the velocities recomputed.

The velocities of each file have one-sided differences at its first and last epoch; recomputed on the joined data, the day's first and last epochs get central differences.

compute_velocity(ds)

Compute satellite velocities from position data.

Uses central differences for interior points, forward/backward differences for endpoints, with the actual epoch spacing (the days joined by :meth:join_days may come with different samplings).

Parameters:

Name Type Description Default
ds Dataset

Dataset with X, Y, Z coordinates.

required

Returns:

Type Description
Dataset

Dataset augmented (or updated) with Vx, Vy, Vz velocities.

Sp3Validator

Validator for SP3 orbit files.

Performs format and data quality checks on parsed SP3 datasets.

Parameters:

Name Type Description Default
dataset Dataset

Parsed SP3 dataset.

required
fpath Path

Path to the original file.

required

__init__(dataset, fpath)

Initialize validator.

validate()

Run all validation checks.

Returns:

Type Description
FileValidationResult

Validation result with errors and warnings.

get_summary()

Get validation summary.

Clock (CLK)

Optional: only when aux_data.fetch_clock is true (the default is false); the VOD formula doesn't use them.

Clock correction data handling.

This module provides tools for reading, parsing, and validating satellite clock correction data from RINEX CLK format files.

ClkFile

Bases: AuxFile

Handler for GNSS clock files in CLK format.

This class reads and processes clock offset files containing satellite clock corrections. It handles the parsing of CLK format files and provides the data in xarray Dataset format.

Supports multiple analysis centers via product registry with proper FTP paths and filename conventions.

Notes

This is a Pydantic dataclass with arbitrary_types_allowed=True.

Attributes:

Name Type Description
date str

String in YYYYDOY format representing the start date.

agency str

Analysis center identifier (e.g., "COD", "GFZ").

product_type str

Product type ("final" or "rapid").

ftp_server str

Base URL for file downloads.

local_dir Path

Local storage directory.

dimensionless bool | None, default True

If True, outputs magnitude-only values (no units attached).

__post_init__()

Initialize CLK file handler.

get_interpolation_strategy()

Get appropriate interpolation strategy for CLK files.

candidate_filenames()

CLK file names, one per sampling the product has been published with.

Filename format: {PREFIX}{YYYYDOY}0000_01D_CLK.CLK

Raises:

Type Description
UnknownProductError

If agency/product combination not in registry.

ValueError

If the product publishes no clock files.

download_aux_file(filename)

Download one CLK file, trying all servers from the product registry.

Servers are tried in priority order from products.toml. Auth-required servers are skipped when no credentials are configured. Warnings are printed when falling back to alternate servers.

Parameters:

Name Type Description Default
filename str

One of :meth:candidate_filenames.

required

Raises:

Type Description
RuntimeError

If file cannot be downloaded from any available server.

ValueError

If GPS week calculation fails.

read_file()

Read and parse CLK file into xarray Dataset.

Uses modular parser for data extraction and validator for quality checks. The clock bias is in seconds (RINEX clock 3.04, Table A16).

Returns:

Type Description
Dataset

Clock offsets with dimensions (epoch, sv). Values are in seconds (or dimensionless if specified).

parse_clk_data(file_handle)

Parse CLK data records using two-pass strategy.

Two-Pass Strategy: Pass 1: Collect all unique epochs and satellites Pass 2: Fill data arrays with clock offsets

This ensures we capture all satellites, even if they're not in the header, and handles satellites appearing/disappearing during the time period.

Parameters:

Name Type Description Default
file_handle TextIO

Open file object positioned after header.

required

Returns:

Type Description
tuple[list[datetime], list[str], ndarray]

(epochs, satellites, clock_offsets) where: - epochs: list of datetime objects - satellites: sorted list of satellite codes - clock_offsets: 2D array (epochs × satellites) in seconds

parse_clk_file(filepath)

Parse complete CLK file.

Parameters:

Name Type Description Default
filepath Path

Path to CLK file.

required

Returns:

Type Description
tuple[list[datetime], list[str], ndarray]

(epochs, satellites, clock_offsets).

parse_clk_header(file_handle)

Parse CLK file header to extract satellite list.

Parameters:

Name Type Description Default
file_handle TextIO

Open file object positioned at start.

required

Returns:

Type Description
set[str]

Satellite identifiers (e.g., "G01", "R01").

Raises:

Type Description
ValueError

If header format is invalid, or if the time system (TIME SYSTEM ID; GPS if absent) is not GPS time.

check_clk_data_quality(ds, min_coverage=80.0)

Check if CLK data meets minimum quality requirements.

Parameters:

Name Type Description Default
ds Dataset

Dataset from a CLK file.

required
min_coverage float

Minimum required data coverage percentage.

80.0

Returns:

Type Description
bool

True if data quality is acceptable.

validate_clk_dataset(ds)

Validate CLK dataset structure and data quality.

Checks: - Required variable exists (clock_offset) - Required coordinates exist (epoch, sv) - Data completeness (percentage of valid values) - Temporal consistency (monotonic epochs)

Parameters:

Name Type Description Default
ds Dataset

Dataset from a CLK file.

required

Returns:

Type Description
dict[str, bool | float | int]

Validation results with keys: - has_clock_offset - has_epoch - has_sv - valid_data_percent - epochs_monotonic - num_epochs - num_satellites

Position and coordinates

Position and coordinate transformations for GNSS data.

Provides ECEF/geodetic position representations and spherical coordinate computation for satellite-receiver geometry analysis.

ECEFPosition

Bases: BaseModel

Earth-Centered, Earth-Fixed (ECEF) position in meters.

ECEF is a Cartesian coordinate system with: - Origin at Earth's center of mass - X-axis pointing to 0° latitude, 0° longitude (Prime Meridian at Equator) - Y-axis pointing to 0° latitude, 90° East longitude - Z-axis pointing to North Pole

Parameters:

Name Type Description Default
x float

X coordinate in meters.

required
y float

Y coordinate in meters.

required
z float

Z coordinate in meters.

required

Raises:

Type Description
ValidationError

If any component is non-finite (NaN/inf), or the position is exactly (0, 0, 0) -- Earth's center, never a physically valid ECEF point for anything this codebase computes geometry relative to. This is deliberately a general sanity check (applies to satellite positions too, not just receivers) -- see from_ds_metadata() for the additional ground-station-specific plausibility check.

Examples:

>>> # From RINEX dataset metadata
>>> pos = ECEFPosition.from_ds_metadata(rinex_ds)
>>> print(f"X: {pos.x:.3f} m")
>>> # Manual creation
>>> pos = ECEFPosition(x=4194304.123, y=176481.234, z=4780013.456)
>>> lat, lon, alt = pos.to_geodetic()

to_geodetic()

Convert ECEF to geodetic coordinates.

Returns:

Type Description
tuple[float, float, float]

(latitude, longitude, altitude) where: - latitude: degrees [-90, 90] - longitude: degrees [-180, 180] - altitude: meters above WGS84 ellipsoid

from_ds_metadata(ds) classmethod

Extract ECEF position from RINEX dataset metadata.

Reads from standard RINEX header attributes.

Parameters:

Name Type Description Default
ds Dataset

RINEX dataset with position in attributes.

required

Returns:

Type Description
ECEFPosition

Receiver position in ECEF.

Raises:

Type Description
KeyError

If position attributes not found in dataset.

ValidationError

If the position is non-finite or exactly (0, 0, 0) (see ECEFPosition's own validator).

ValueError

If the position's ECEF magnitude falls outside the plausible ground-station band (_GROUND_STATION_MIN_RADIUS_M .. _GROUND_STATION_MAX_RADIUS_M, i.e. on/near the WGS84 ellipsoid) -- e.g. an unpopulated RINEX header defaulted to a position that happens to be finite and non-zero but still not a real ground station (this check is specific to this parse boundary, not a general ECEFPosition constraint -- see the class docstring).

Examples:

>>> from canvod.readers import Rnxv3Obs
>>> rnx = Rnxv3Obs(fpath="station.24o")
>>> ds = rnx.to_ds()
>>> pos = ECEFPosition.from_ds_metadata(ds)

GeodeticPosition dataclass

Geodetic (WGS84) position.

Parameters:

Name Type Description Default
lat float

Latitude in degrees [-90, 90].

required
lon float

Longitude in degrees [-180, 180].

required
alt float

Altitude in meters above WGS84 ellipsoid.

required

Examples:

>>> pos = GeodeticPosition(lat=48.208, lon=16.373, alt=200.0)
>>> print(f"Vienna: {pos.lat}°N, {pos.lon}°E, {pos.alt}m")

to_ecef()

Convert geodetic to ECEF coordinates.

Returns:

Type Description
ECEFPosition

Position in ECEF frame.

add_broadcast_spherical_coords_to_dataset(ds, theta, phi)

Add broadcast spherical coordinates to xarray Dataset.

Same convention as :func:add_spherical_coords_to_dataset but attrs reflect that values come from the receiver's SBF SatVisibility block (broadcast ephemeris) rather than independently-computed SP3/CLK ephemerides.

Parameters:

Name Type Description Default
ds Dataset

Dataset with 'epoch' and 'sid' dimensions.

required
theta ndarray

Polar angles in radians [0, π].

required
phi ndarray

Azimuthal angles in radians [0, 2π).

required

Returns:

Type Description
Dataset

Dataset with phi and theta variables added.

add_spherical_coords_to_dataset(ds, r, theta, phi)

Add spherical coordinates to xarray Dataset with proper metadata.

Parameters:

Name Type Description Default
ds Dataset

Dataset with 'epoch' and 'sid' dimensions.

required
r ndarray

Radial distances in meters.

required
theta ndarray

Polar angles in radians [0, π].

required
phi ndarray

Azimuthal angles in radians [0, 2π).

required

Returns:

Type Description
Dataset

Dataset with phi, theta, r variables added.

Notes

Variables are added with CF-compliant attributes following physics convention.

Examples:

>>> # After computing spherical coordinates
>>> r, theta, phi = compute_spherical_coordinates(sat_x, sat_y, sat_z, rx_pos)
>>>
>>> # Add to RINEX dataset
>>> augmented_ds = add_spherical_coords_to_dataset(rinex_ds, r, theta, phi)
>>> print(augmented_ds.phi.attrs['description'])

compute_spherical_coordinates(sat_x, sat_y, sat_z, rx_pos)

Compute spherical coordinates (r, theta, phi) in navigation convention.

Uses local ENU (East-North-Up) topocentric frame centered at receiver.

Navigation Convention: - theta: Polar angle from +z axis (zenith), [0, π] radians * theta = 0 → zenith (straight up) * theta = π/2 → horizon * theta > π/2 → below horizon (set to NaN) - phi: Azimuthal angle from North, clockwise, [0, 2π) radians * phi = 0 → North * phi = π/2 → East * phi = π → South * phi = 3π/2 → West - r: Radial distance in meters

Note

The phi convention follows geographic/navigation standards where: - 0° points North (positive Y in ENU frame) - Angles increase clockwise when viewed from above - This is computed as arctan2(East, North), giving North=0° reference - Differs from physics convention which uses East=0° reference

Parameters:

Name Type Description Default
sat_x ndarray

Satellite X coordinates in ECEF (meters).

required
sat_y ndarray

Satellite Y coordinates in ECEF (meters).

required
sat_z ndarray

Satellite Z coordinates in ECEF (meters).

required
rx_pos ECEFPosition

Receiver position in ECEF.

required

Returns:

Type Description
tuple[ndarray, ndarray, ndarray]

(r, theta, phi) where: - r: distances in meters - theta: polar angles in radians [0, π] - phi: azimuthal angles in radians [0, 2π)

Notes

Satellites below horizon (theta > π/2) are set to NaN.

Examples:

>>> from canvod.auxiliary.position import (
...     ECEFPosition, compute_spherical_coordinates,
... )
>>>
>>> # Receiver position
>>> rx = ECEFPosition(x=4194304.0, y=176481.0, z=4780013.0)
>>>
>>> # Satellite positions (example)
>>> sat_x = np.array([16364123.0, 10205789.0])
>>> sat_y = np.array([12123456.0, -8901234.0])
>>> sat_z = np.array([18456789.0, 21234567.0])
>>>
>>> r, theta, phi = compute_spherical_coordinates(sat_x, sat_y, sat_z, rx)
>>> print(f"Distance: {r[0]/1e6:.2f} Mm")
>>> print(f"Polar angle: {np.degrees(theta[0]):.1f}°")
>>> print(f"Azimuth: {np.degrees(phi[0]):.1f}°")

Product registry

Product registry and specifications for IGS analysis centers.

ProductSpec

Bases: BaseModel

Product specification with structural validation.

A product is published as one SP3 and, if it has clocks, one CLK file per day. Agencies have changed the sampling of their files over the years, so sp3_samplings and clk_samplings list every sampling the product has been published with, in the order they are tried.

Validates data structure only (fast). FTP availability is checked during download, and for the whole registry by python -m canvod.auxiliary.products.check (just check-products).

Notes

This is a Pydantic BaseModel.

has_clock property

Whether the product publishes CLK files.

available_formats property

File formats the product publishes.

validate_samplings(v) classmethod

Validate sampling codes such as 05M or 30S.

validate_prefix_matches_agency(v, info) classmethod

Validate prefix starts with agency code.

sp3_filenames(yyyydoy)

SP3 file names for one day, in the order they are tried.

Parameters:

Name Type Description Default
yyyydoy str

Day as YYYYDOY.

required

Returns:

Type Description
list[str]

One name per sampling, without the .gz of the server copy.

clk_filenames(yyyydoy)

CLK file names for one day, in the order they are tried.

Parameters:

Name Type Description Default
yyyydoy str

Day as YYYYDOY.

required

Returns:

Type Description
list[str]

One name per sampling; empty if the product has no clocks.

get_product_spec(agency, product_type)

Get product spec from global registry.

list_agencies()

List agencies from global registry.

get_products_for_agency(agency)

Get products for agency from global registry.