gnssrefl.tracks module
Build, manage, and tag multi-GNSS satellite tracks.
The on-disk tracks.json catalogs the periodic ground tracks visible at a
station, keyed by track_id and grouped by (sat, freq). Each track holds
a list of epochs describing the matching parameters (repeat interval,
anchor MJD, mean azimuth, drift rate).
The module has two halves: a build side (build_tracks and helpers) that
collects per-arc geometry and writes the JSON, and a runtime side
(load_tracks_json, build_lookup_index, lookup_arc,
attach_track_id) that tags arcs with their (track_id, track_epoch) at
extract time. track_id is stable across every artifact derived from a
tracks-shaped JSON (tracks.json, vwc_tracks.json, per-day phase files, vwc
output files).
See docs/pages/tracks.md for the full design and workflow.
- gnssrefl.tracks.active_epoch_days(tracks_json)
Set of (year, doy) pairs spanning the union of active epoch windows.
- gnssrefl.tracks.assign_tracks(df_freq, T_candidates_solar)
Walk arcs forward in MJD and assign track ids.
Returns (df_sorted, track_ids, match_T) where match_T[i] is the candidate T (in solar days) used to extend arc i, or NaN if arc i seeded a new track.
- gnssrefl.tracks.attach_legacy_apriori(arcs, station, extension='')
Tag arcs with legacy GPS-only per-freq apriori_rh_{fr}.txt entries.
Groups arcs by frequency, loads
apriori_rh_{fr}.txtonce per freq, and matches each arc to a track by (satellite, circular azimuth distance <= 3 deg), the same rule used historically by the legacy VWC pipeline.Sets
meta['apriori_RH'],meta['track_azim'],meta['track_id'], andmeta['track_epoch']on every arc.apriori_RH/track_azimareNoneon miss;track_id/track_epochare-1on miss.track_epochis always0on match (the legacy path has only one epoch per track).
- gnssrefl.tracks.attach_track_id(arcs, track_file_path, year, doy, track_cache=None)
Tag each arc’s metadata with track info from tracks-shaped JSON.
Works against both
tracks.json(the station-wide catalog) andvwc_tracks.json(the VWC-eligible filtered subset, which adds a per epochapriori_RHfield).- Each
metadatadict gets these new keys: track_id,track_epoch(both -1 on no match),track_azim(az_avg_minelof the matched epoch, orNone),apriori_RH(matched epoch’sapriori_RH, orNone; only present invwc_tracks.json).
- Parameters:
arcs (list of (metadata, data) tuples) – Output of
extract_arcs, modified in place.track_file_path (path-like) – Path to a tracks-shaped JSON file (
tracks.jsonfrom build_tracks, orvwc_tracks.jsonfromvwc_input).year (int) – Year and day-of-year of the arcs (used together with each arc’s
arc_timestampto compute MJD for the lookup).doy (int) – Year and day-of-year of the arcs (used together with each arc’s
arc_timestampto compute MJD for the lookup).track_cache (dict, optional) – Path-keyed cache of prebuilt lookup indexes. Defaults to the module-level
TRACK_INDEX_CACHE. Cache entries are never invalidated; restart the process if a tracks file is rewritten on disk.
- Returns:
The same
arcslist (modified in place), for chaining.- Return type:
list
- Each
- gnssrefl.tracks.build_lookup_index(tracks_json)
Build a (sat, freq) -> [(track_id, track_epoch, def_dict), …] index.
- Each
def_dictcarries the fields needed by lookup_arc: rise, repeat_interval_d, anchor_mjd, az_avg_minel, az_drift_rate, first_mjd, last_mjd, epoch_type
- Each
- gnssrefl.tracks.build_tracks(station, year, year_end=None, extension='', snr_type=66, source='auto')
Build tracks.json for a station over [year .. year_end].
Collects per-arc geometry (sat, freq, mjd, azim, rise), folds arcs into periodic tracks, drops fragment tracks below the per-freq filter threshold (10 percent of per-freq median arcs per track), fits a single periodic epoch per surviving track, and writes the JSON.
Two arc sources are supported, selected by
source:'snr'walk SNR files viaload_arcs(slow, covers everyfrequency in the SNR file).
'results'readresults/+failQC/viaload_arcs(..., fast=True)(fast, covers only frequencies gnssir was run with).
'auto'(default) prefer'results'if anyresults/dir ispopulated in range; else fall back to
'snr'.
- Parameters:
station (str) – 4-char station name (lowercase)
year (int) – Start year
year_end (int, optional) – End year inclusive. Defaults to
year.extension (str) – Strategy extension subdirectory. Default ‘’.
snr_type (int) – SNR file type for the SNR-walk path. Default 66.
source (str) – Arc source: ‘auto’ | ‘results’ | ‘snr’. Default ‘auto’.
- Returns:
tracks_json (dict) – In-memory tracks_json matching the on-disk JSON.
arcs_df (pandas.DataFrame or None) – Per-arc DataFrame in the extract_arcs_gnssir_results schema (mjd, azim, constellation, RH, match_T, track_id, track_epoch), or None when source=’snr’ (no RH available).
- gnssrefl.tracks.doy_hour_to_mjd(year, doy, hours)
Convert (year, doy, fractional hours UTC) to MJD.
- gnssrefl.tracks.fit_segment(arcs)
Fit T and azimuth model for a single track’s arcs.
Returns (T_fit, anchor_mjd, az_avg_minel, az_drift_rate). az_drift_rate is only nonzero for BeiDou (secular azimuth drift).
- gnssrefl.tracks.iso_to_mjd(iso_str)
ISO 8601 ‘YYYY-MM-DDTHH:MM:SSZ’ UTC string -> MJD float.
- gnssrefl.tracks.load_arcs(station, year, year_end, extension, snr_type=66, fast=False)
Collect per-arc geometry for station across [year..year_end] into a DataFrame.
Returns columns
(year, doy, sat, freq, mjd, azim, rise); the fast path additionally carriesRH.fast=False(default): walk SNR files day by day viaextract_arcs_from_station, covering every frequency the SNR file contains.fast=True: read the gnssirresults/+failQC/artifacts viaload_results_with_failqc. Orders of magnitude faster, but only covers frequencies gnssir was configured to run, and requires a prior gnssir run withsave_failqc=True.
BeiDou GEO/IGSO PRNs in BEIDOU_NON_MEO_SATS are skipped here so the rest of the pipeline never sees them.
- gnssrefl.tracks.load_tracks_json(path)
Load a tracks.json file from disk and return the tracks_json dict.
- gnssrefl.tracks.lookup_arc(sat, freq, obs_time_mjd, obs_az_minel, track_lookup_index, az_tol=5.0, time_tol_min=30)
Look up the (track_id, track_epoch, epoch_entry) for a single arc.
Active matches require the query to fall inside the track’s
[first_mjd, last_mjd]interval AND fit the periodic model withintime_tol_minandaz_tol.- Parameters:
sat (int) – Satellite number and frequency code identifying the candidate list.
freq (int) – Satellite number and frequency code identifying the candidate list.
obs_time_mjd (float) – Arc observation time in MJD.
obs_az_minel (float) – Arc azimuth at minimum elevation (degrees). Compared against each candidate’s drift-corrected expected azimuth.
track_lookup_index (dict) – Pre-built (sat, freq) -> [(track_id, track_epoch, entry_dict), …] candidate index produced by
build_lookup_index(tracks_json). Eachentry_dictcarries the epoch’s matching parameters (first_mjd,last_mjd,anchor_mjd,repeat_interval_d,az_avg_minel,az_drift_rate,epoch_type,ignored_ranges).
- Returns:
(track_id, track_epoch, entry) –
(-1, -1, None)if no track def covers this arc.entryis the matched epoch dict frombuild_lookup_index(keys includeaz_avg_minelandapriori_RH) when the match succeeds.- Return type:
tuple
- gnssrefl.tracks.mjd_to_iso_ceil(mjd)
MJD -> ISO 8601 Z UTC string, rounded UP to the nearest second.
- gnssrefl.tracks.mjd_to_iso_floor(mjd)
MJD -> ISO 8601 Z UTC string, rounded DOWN to the nearest second.
- gnssrefl.tracks.results_dir_has_files(station, year, year_end, extension)
True if any year in the range has a populated results/ dir for station.
- gnssrefl.tracks.unwrap_az(az)
Bring all azimuths into a single ±180° window centered on az[0].
- gnssrefl.tracks.warn_legacy_apriori_and_exit(station, missing_file, extension='')
If any GPS
apriori_rh_{fr}.txtexists, print a-legacy Thint and exit.Called from modern-path entry points when
missing_file(e.g.vwc_tracks.json) is absent.
- gnssrefl.tracks.write_tracks_json(tracks_json, f)
Write tracks_json to file handle
fwith oneignored_rangespair per line.