This commit is contained in:
vcoppe committed 2026-10-08 21:04:29 +02:00
1 parent b085a1d476
commit 42a55e6759
51 files changed
+5756 -3854

No files matched your search

+88 -1
View File
@@ -1,4 +1,91 @@
use crate::{Trackpoint, distance};
use crate::{Statistics, TrackSegment, Trackpoint, distance};
/// The new timestamps of the trackpoints of `segments`, one after the other: each segment goes on
/// from the end of the previous one, the first one starts at `start_time` (in milliseconds).
///
/// A segment that has timestamps keeps its durations multiplied by `ratio`; one that has none
/// (judged by its first trackpoint) follows the previous trackpoint at `speed` (km/h). See
/// [`shifted_and_compressed`] and [`with_timestamps`].
pub fn changed_segment_times(
segments: &[&TrackSegment],
start_time: i64,
speed: f64,
ratio: f64,
) -> Vec<Vec<Option<i64>>> {
let mut last: Option<Trackpoint> = None;
segments
.iter()
.map(|segment| {
let points: Vec<Trackpoint> = segment.iter().cloned().collect();
let Some(first) = points.first() else {
return vec![];
};
let start = last.clone().unwrap_or_else(|| {
let mut start = first.clone();
start.time = Some(start_time);
start
});
let points = if first.time.is_none() {
with_timestamps(points, Some(speed), Some(&start), Some(start_time))
} else {
shifted_and_compressed(points, Some(speed), ratio, &start)
};
last = points.last().cloned();
points.iter().map(|point| point.time).collect()
})
.collect()
}
/// Makes up timestamps for the trackpoints of `segments`, one after the other: they start at
/// `start_time` (in milliseconds) and last `total_time` seconds together, the longer and the
/// steeper an interval the more of it. Each segment goes on from the end of the previous one.
pub fn artificial_segment_times(
segments: &[&TrackSegment],
start_time: i64,
total_time: f64,
) -> Vec<Vec<Option<i64>>> {
let mut weights: Vec<Vec<f64>> = segments
.iter()
.map(|segment| {
let points: Vec<Trackpoint> = segment.iter().cloned().collect();
let slopes: Vec<f64> = Statistics::compute(segment)
.local
.iter()
.map(|l| l.slope)
.collect();
artificial_weights(&points, &slopes)
})
.collect();
let mut total: f64 = weights.iter().flatten().sum();
if total.is_nan() || total <= 0.0 {
// no distance to share the time by: every interval takes as long
for weights in &mut weights {
weights.iter_mut().for_each(|w| *w = 1.0);
}
total = weights.iter().map(Vec::len).sum::<usize>() as f64;
}
let ms_per_weight = if total > 0.0 {
total_time * 1000.0 / total
} else {
0.0
};
let mut start = start_time;
segments
.iter()
.zip(&weights)
.map(|(segment, weights)| {
let points: Vec<Trackpoint> = segment.iter().cloned().collect();
if points.is_empty() {
return vec![];
}
let points = with_artificial_timestamps(points, weights, ms_per_weight, start);
// the next segment goes on from the end of this one
start = points.last().and_then(|point| point.time).unwrap_or(start);
points.iter().map(|point| point.time).collect()
})
.collect()
}
/// Replaces the trackpoints `start..end` of `trkpt` by `points`, keeping the timestamps of the
/// whole path consistent. It is the work of the old `replaceTrackPoints` of the TS library.
+9 -2
View File
@@ -3,10 +3,17 @@
///
/// The codes are the positions of the names in the order of first appearance. The table only
/// grows: a code never changes meaning, so it stays valid in every state of the history.
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Categories(Vec<String>);
impl Categories {
/// A table with these names, in the order of their codes (at most [`Self::CAPACITY`] of them
/// are kept).
pub fn from_names(mut names: Vec<String>) -> Self {
names.truncate(Self::CAPACITY);
Self(names)
}
/// Maximum number of distinct values.
pub const CAPACITY: usize = u8::MAX as usize;
@@ -36,7 +43,7 @@ impl Categories {
/// The categories of the data of the trackpoints. There is one table per engine, shared by all
/// its files.
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct TrackpointCategories {
pub surface: Categories,
pub highway: Categories,
+3 -3
View File
@@ -2,7 +2,7 @@
macro_rules! uuid_id {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct $name(pub uuid::Uuid);
impl Default for $name {
@@ -15,13 +15,13 @@ macro_rules! uuid_id {
pub(crate) use uuid_id;
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Link {
pub href: String,
pub text: Option<String>,
}
#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, Copy)]
pub struct LngLat {
pub lng: f64,
pub lat: f64,
+2 -2
View File
@@ -12,7 +12,7 @@ pub struct File {
pub wpt: Waypoints,
}
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct FileInfo {
pub name: String,
pub desc: Option<String>,
@@ -21,7 +21,7 @@ pub struct FileInfo {
pub time: Option<i64>,
}
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Author {
pub name: Option<String>,
pub email: Option<String>,
+1 -1
View File
@@ -11,7 +11,7 @@ pub struct Track {
pub trkseg: Vec<TrackSegment>,
}
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct TrackInfo {
pub name: Option<String>,
pub cmt: Option<String>,
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::LngLat;
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone)]
pub struct Trackpoint {
pub coordinates: LngLat,
pub ele: f64,
+1 -1
View File
@@ -4,7 +4,7 @@ use super::common::uuid_id;
uuid_id!(WaypointId);
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone)]
pub struct Waypoint {
pub id: WaypointId,
pub coordinates: LngLat,
@@ -69,18 +69,18 @@ impl Statistics {
self.global.total_distance += dist;
if let Some(time) = time {
let speed = speed(dist, time);
if (0.5..=1500.0).contains(&speed) {
self.global.moving_distance = self
.global
.moving_distance
.map_or(Some(dist), |d| Some(d + dist));
self.global.moving_time = self
.global
.moving_time
.map_or(Some(time), |t| Some(t + time));
}
let moving = time
.zip(time.and_then(|time| speed(dist, time)))
.filter(|(_, speed)| (0.5..=1500.0).contains(speed));
if let Some((time, _)) = moving {
self.global.moving_distance = self
.global
.moving_distance
.map_or(Some(dist), |d| Some(d + dist));
self.global.moving_time = self
.global
.moving_time
.map_or(Some(time), |t| Some(t + time));
}
}
@@ -119,8 +119,7 @@ impl Statistics {
};
let (left, right) = (timed(left), timed(right));
self.local[i.flat].speed = time_diff(trkseg[right].time, trkseg[left].time)
.filter(|time| *time > 0)
.map(|time| {
.and_then(|time| {
speed(
self.local[right.flat].total_distance
- self.local[left.flat].total_distance,
@@ -286,16 +285,14 @@ 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<f64> {
self.total_time
.filter(|t| *t > 0)
.map(|t| speed(self.total_distance, t))
self.total_time.and_then(|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<f64> {
self.moving_distance
.zip(self.moving_time.filter(|t| *t > 0))
.map(|(d, t)| speed(d, t))
.zip(self.moving_time)
.and_then(|(d, t)| speed(d, t))
}
pub fn merge(&mut self, other: &GlobalStatistics) {
+2 -2
View File
@@ -1,5 +1,5 @@
mod statistics;
mod compute;
mod utils;
pub use statistics::*;
pub use compute::*;
pub use utils::*;
+11 -6
View File
@@ -22,9 +22,10 @@ pub fn time_diff(a: Option<i64>, b: Option<i64>) -> Option<i64> {
Some(a? - b?)
}
/// Computes the speed for a given distance in kilometers and a time in milliseconds
pub fn speed(distance: f64, time: i64) -> f64 {
distance / (time as f64 / 3_600_000.0)
/// Computes the speed in km/h for a given distance in kilometers and a time in milliseconds.
/// Unknown if the time is not a positive duration.
pub fn speed(distance: f64, time: i64) -> Option<f64> {
(time > 0).then(|| distance / (time as f64 / 3_600_000.0))
}
pub fn slope(ele: f64, distance: f64) -> f64 {
@@ -125,7 +126,7 @@ mod tests {
assert_eq!(time_diff(None, Some(1)), None);
assert_eq!(time_diff(Some(1), None), None);
// 1 km in one hour
assert!((speed(1.0, 3_600_000) - 1.0).abs() < 1e-12);
assert!((speed(1.0, 3_600_000).unwrap() - 1.0).abs() < 1e-12);
}
#[test]
@@ -154,9 +155,13 @@ mod tests {
#[test]
fn test_speed() {
// 1 km in 1 h
assert!((speed(1.0, 3_600_000) - 1.0).abs() < 1e-12);
assert!((speed(1.0, 3_600_000).unwrap() - 1.0).abs() < 1e-12);
// 10 km in 30 min
assert!((speed(10.0, 1_800_000) - 20.0).abs() < 1e-12);
assert!((speed(10.0, 1_800_000).unwrap() - 20.0).abs() < 1e-12);
// no duration, no speed
assert_eq!(speed(1.0, 0), None);
assert_eq!(speed(0.0, 0), None);
assert_eq!(speed(1.0, -1000), None);
}
#[test]