Preprocessing pipeline¶
Before interpolation, auxiliary data must be restructured to match the (epoch × sid) layout of the observation datasets. This pipeline bridges the sv (satellite) indexing of SP3/CLK files and the sid (signal ID) indexing of the observations.
In a run, AuxDataPipeline calls prep_aux_ds() with the signal IDs kept by the sids: settings, so the orbits and clocks carry the same signals as the observations before they are interpolated.
The dimension mismatch problem¶
SP3 / CLK files
Index by satellite vehicle (sv):
sp3_data.dims
# {'epoch': 96, 'sv': 32}
GPS G01 has one entry: one position per epoch.
RINEX obs Dataset
Index by signal ID (sid):
rinex_data.dims
# {'epoch': 2880, 'sid': 384}
GPS G01 maps to up to 28 Signal IDs (L1C, L2W, L5Q, …).
Satellite position is frequency-independent (all signals leave the same antenna), so the sv \(\rightarrow\) sid mapping simply replicates each position across all signal IDs for that satellite.
Pipeline functions¶
| Function | Purpose | Input dims | Output dims |
|---|---|---|---|
preprocess_aux_for_interpolation() |
Minimal prep for interpolation | sv: 32 |
sid: 896 |
prep_aux_ds() |
Steps 1-4; with keep_sids, only those signals (used by runs) |
sv: 32 |
sid: 4429 |
map_aux_sv_to_sid() |
Step 1: expand sv \(\rightarrow\) sid |
sv: 32 |
sid: 896 |
pad_to_global_sid() |
Step 2: pad to all constellations | sid: 896 |
sid: 4429 |
normalize_sid_dtype() |
Step 3: convert to object dtype | Any | dtype fixed |
strip_fillvalue() |
Step 4: remove _FillValue attributes |
Any | attributes cleaned |
Step 1: map_aux_sv_to_sid()¶
Each satellite position is replicated across all its signal IDs:
from canvod.auxiliary.preprocessing import map_aux_sv_to_sid
sp3_sid = map_aux_sv_to_sid(sp3_data)
# G01 position is identical for every signal ID of G01
sp3_sid["X"].sel(sid="G01|L1|C") # 12 345 678.9 m
sp3_sid["X"].sel(sid="G01|L2|W") # 12 345 678.9 m (same)
sp3_sid["X"].sel(sid="G01|L5|I") # 12 345 678.9 m (same)
Signal IDs generated for GPS G01 (28 total):
G01|L1|C G01|L1|L G01|L1|M G01|L1|N G01|L1|P G01|L1|S G01|L1|W
G01|L1|X G01|L1|Y G01|L1|p G01|L1|u
G01|L2|C G01|L2|D G01|L2|L G01|L2|M G01|L2|N G01|L2|P G01|L2|S
G01|L2|W G01|L2|X G01|L2|Y G01|L2|l G01|L2|p G01|L2|u
G01|L5|I G01|L5|Q G01|L5|X G01|L5|u
Signal ID format: "{SV}|{BAND}|{CODE}", for example "G01|L1|C", "E08|E5a|Q". Uppercase codes are RINEX 3 attributes; the lowercase codes are the RINEX 2 tracking-code markers for signals whose code RINEX 2 does not record.
Step 2: pad_to_global_sid()¶
Pads the Dataset to include all possible Signal IDs across all supported constellations: every \(\text{SV} \times \text{band} \times \text{code}\) in the constellation BAND_CODES (4 429 at the time of writing). Signal IDs outside that set are dropped. Required for Icechunk storage, where sequentially appended datasets must share the same coordinate space.
from canvod.auxiliary.preprocessing import pad_to_global_sid
sp3_global = pad_to_global_sid(sp3_sid)
sp3_global.sizes["sid"] # 4429
Added SIDs are filled with NaN.
Step 3: normalize_sid_dtype()¶
Converts the sid coordinate to object dtype for Zarr/Icechunk compatibility. Fixed-length Unicode string types (<U12, etc.) cause dtype conflicts when datasets from different files are concatenated and appended.
from canvod.auxiliary.preprocessing import normalize_sid_dtype
ds = normalize_sid_dtype(ds)
ds["sid"].dtype # dtype('O')
Step 4: strip_fillvalue()¶
Removes _FillValue attributes that conflict with Icechunk's internal missing-data handling. NaN is the standard missing-value marker throughout the pipeline.
from canvod.auxiliary.preprocessing import strip_fillvalue
ds = strip_fillvalue(ds)
# No variable in ds.data_vars has a "_FillValue" encoding key
Scientific note¶
Replicating satellite positions across signal IDs is scientifically valid:
One position per satellite is enough for the angles
SP3 positions refer to the satellite's center of mass. The antenna phase centers of the signals sit up to a few meters from it and differ slightly between frequencies. Seen from a receiver about 20 000 km away, a few meters change \(\theta\) and \(\varphi\) by about \(10^{-7}\) rad, far below anything VOD can resolve, so every signal of a satellite can use the same position.