TelemetrySign in

GPS validation

Algorithm validate-gps-1.0.0 · parameters from v1-default · source engine/src/dtta/engine/validate/gps.py.

Validation is a pure function of the GPS table and the parameter set. It never deletes rows: it sets a bit mask quality_flags on every sample and derives is_valid. Downstream stages use only valid samples and carry the flags through to the profiles.

Flags

bit name rule (parameter) invalidates
1 NO_FIX fix quality < gps_fix_min (3, i.e. no 3-D fix), or latitude and longitude both exactly 0 yes
2 HIGH_DOP dilution of precision > gps_dop_max (3.0); confidence is halved above gps_dop_confidence_taper (2.0) yes
4 DUPLICATE_STATIONARY identical position to the previous sample while speed < gps_stationary_speed_ms (0.5 m/s): the receiver is holding, treated as stationary no
8 POSITION_JUMP identical position while moving (≥ gps_moving_speed_ms, 3 m/s), or a step from the last good sample larger than max(2vΔt+3m,  60m/sΔt)\max(2\,v\,\Delta t + 3\,\mathrm{m},\; 60\,\mathrm{m/s}\cdot\Delta t) (gps_jump_speed_factor, gps_jump_noise_m, gps_jump_speed_floor_ms) yes
16 SPEED_SPIKE reported speed > gps_speed_max_ms (80 m/s); or a speed step whose acceleration exceeds gps_spike_accel_ms2 (15 m/s²) and reverses within gps_spike_recovery_samples (3); or a Hampel outlier (window gps_hampel_window 7, gps_hampel_nsigma 3 × 1.4826 MAD, and at least gps_hampel_min_ms 2 m/s away) yes
32 ACCEL_IMPLAUSIBLE sustained |dv/dt| > gps_implausible_accel_ms2 (12 m/s²) for longer than gps_implausible_accel_s (0.5 s): confidence lowered to 0.3 no
64 TIME_NONMONOTONIC sample time not increasing yes
128 INTERPOLATED speed filled by linear interpolation inside a gap ≤ speed_gap_fill_s (0.5 s) no
256 PRELOCK every sample before the first run of gps_prelock_valid_run_s (2 s) of fixed, low-DOP samples yes
512 EDGE first and last 0.5 s of each contiguous run (filter warm-up); excluded from peak statistics no
1024 PROJ_LOST reserved for samples whose track projection was lost; the projection stage reports this per lap (proj_lost_count) rather than in the GPS flags no

is_valid is true when none of the invalidating bits is set.

Session-level checks

  • Valid fraction = valid samples / all samples. Below gps_valid_fraction_min (0.8) the session is blocked ("not enough valid GPS").
  • Longest valid run must be at least gps_min_valid_run_s (60 s) or nothing can be analysed.
  • Gaps: a gap is any interval longer than gps_gap_factor (3) sample periods; gaps > gps_gap_s (1.0 s) split the data into separate runs for lap detection.
  • Effective rate = (valid samples − 1) / (time span of valid samples), reported in Hz.
  • Speed consistency (independent witness): over gps_speed_consistency_window_s (2 s) windows the position-derived speed and the receiver's reported speed must agree within gps_speed_consistency_max_ms (1.5 m/s) in at least gps_speed_consistency_fraction (95 %) of moving windows; otherwise a warning is raised and confidence is downgraded. Calibrated in Phase 4 on the 18 Hz HERO9 file (docs/proofs/phase-04/qc_report.md).

Projection to metres

Positions are converted to a local east-north plane about the track origin: x=(λλ0)cosφ0Reπ/180x = (\lambda - \lambda_0)\cos\varphi_0 \, R_e \, \pi/180, y=(φφ0)Reπ/180y = (\varphi - \varphi_0) R_e \pi/180 with Re=6371008.8R_e = 6\,371\,008.8 m (earth_radius_m). Track extents are under 1 km, so the flat-earth error is below 1 mm.

What was verified on real data

  • HERO9 (18 Hz, GPS5): 7 speed spikes with |dv/dt| > 15 m/s² in one heat, all caught; 958 duplicate positions, all while stationary.
  • HERO9 chaptered file: the first 18 samples have fix = 0 and DOP 99.99 (PRELOCK).
  • Valid fraction on the fixture sessions 99.8–100 %; effective rates 10.0 Hz (HERO11) and 17.7 Hz (HERO9).

Tests: engine/tests/unit/test_validate_gps.py (every rule triggered and not triggered), engine/tests/synthetic (injected spike, pre-lock zeros, duplicates, jump, gap), regression goldens for flag counts on both fixtures.