From e8c4eb92f90f7ff370d77af6c856356c1aa2ab49 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Thu, 8 Oct 2026 08:59:53 +0200 Subject: [PATCH] handle corrupt time data --- .../engine/src/core/statistics/statistics.rs | 9 +++- .../src/engine/derived/statistics_buffer.rs | 41 ++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/gpx-rs/engine/src/core/statistics/statistics.rs b/gpx-rs/engine/src/core/statistics/statistics.rs index ae7f75728..a426afead 100644 --- a/gpx-rs/engine/src/core/statistics/statistics.rs +++ b/gpx-rs/engine/src/core/statistics/statistics.rs @@ -283,13 +283,18 @@ pub struct GlobalStatistics { } impl GlobalStatistics { + /// Average speed over the total time, unknown if that is not a positive duration (for + /// example a range with a single timestamp, or timestamps going backwards). pub fn total_speed(&self) -> Option { - self.total_time.map(|t| speed(self.total_distance, t)) + self.total_time + .filter(|t| *t > 0) + .map(|t| speed(self.total_distance, t)) } + /// Average speed over the moving time, unknown if there is no moving time. pub fn moving_speed(&self) -> Option { self.moving_distance - .zip(self.moving_time) + .zip(self.moving_time.filter(|t| *t > 0)) .map(|(d, t)| speed(d, t)) } diff --git a/gpx-rs/engine/src/engine/derived/statistics_buffer.rs b/gpx-rs/engine/src/engine/derived/statistics_buffer.rs index c0b188b38..9b334eda3 100644 --- a/gpx-rs/engine/src/engine/derived/statistics_buffer.rs +++ b/gpx-rs/engine/src/engine/derived/statistics_buffer.rs @@ -195,7 +195,8 @@ impl StatisticsBuffer { } let delta = |values: &[f64]| values[end] - values[start]; - let delta_time = |values: &[i64]| values[end] - values[start]; + // the total time is the span of the timestamps, which is negative where they go backwards + let delta_time = |values: &[i64]| (values[end] - values[start]).max(0); let time = |i: usize| { let time = self.time.as_ref()?[i]; (time != NO_TIME).then_some(time) @@ -559,6 +560,44 @@ mod tests { assert_eq!(slice.end_time, global.end_time); } + #[test] + fn test_slices_of_corrupt_timestamps_are_never_negative_nor_infinite() { + let (mut segment, _) = computed("data/with_time.gpx"); + let n = segment.len(); + assert!(n > 40); + // timestamps that go back, then forward, and some that are missing + let times: Vec<_> = segment.iter().map(|p| p.time.unwrap()).collect(); + segment.update_all(|i, pt| { + pt.time = match i { + 10..=19 => Some(times[29 - i]), + 20..=24 => None, + 25..=29 => Some(times[0] - 1_000 * i as i64), + _ => pt.time, + }; + }); + let stats = Statistics::compute(&segment); + let mut buffer = StatisticsBuffer::default(); + buffer.update(&[(&segment, &stats)]); + + for start in 0..n { + for end in start..n { + let slice = buffer.slice(start, end).unwrap(); + for time in [slice.total_time, slice.moving_time].into_iter().flatten() { + assert!(time >= 0, "time of {start}..={end}"); + } + for speed in [slice.total_speed(), slice.moving_speed()] + .into_iter() + .flatten() + { + assert!( + speed.is_finite() && speed >= 0.0, + "speed of {start}..={end}" + ); + } + } + } + } + #[test] fn test_slice_of_a_range() { let (segment, s) = computed("data/with_time.gpx");