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: |
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: |
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. |
required |
destination
|
Path
|
Local file destination. |
required |
file_info
|
dict
|
Extra context passed to the downloader. |
None
|
product_spec
|
ProductSpec
|
Product specification with |
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
|
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: |
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
|
|
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 |
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
|
ValueError
|
If the position's ECEF magnitude falls outside the plausible
ground-station band ( |
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
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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 |
required |
clock_ds
|
Dataset | None
|
Clock offsets, or |
required |
target_epochs
|
ndarray
|
Epoch grid, see :func: |
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
|
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'
|
product_type
|
str
|
Product type ( |
'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 |
False
|
fetch_clock
|
bool
|
Whether to also download/interpolate CLK clock-correction files.
If None, uses |
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 |
required |
site_config
|
Any
|
Site configuration (needs |
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 |
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 |
required |
receiver_position
|
ECEFPosition
|
Receiver ECEF coordinates. |
required |
Returns:
| Type | Description |
|---|---|
Dataset
|
Dataset with |
preprocess_day(date, site_config)
abstractmethod
¶
Download / prepare ephemeris data for one day.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
date
|
str
|
Date in |
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 |
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'
|
keep_sids
|
list of str
|
SIDs to keep when reading |
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 |
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 |
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: |
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 |
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: |
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 ( |
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 |
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
|
ValueError
|
If the position's ECEF magnitude falls outside the plausible
ground-station band ( |
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 |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
One name per sampling, without the |
clk_filenames(yyyydoy)
¶
CLK file names for one day, in the order they are tried.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
yyyydoy
|
str
|
Day as |
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.