This commit is contained in:
vcoppe
2026-10-03 18:03:13 +02:00
parent 639e7491eb
commit a994e16f51
3 changed files with 434 additions and 6 deletions
+141 -1
View File
@@ -32,12 +32,77 @@ pub struct TrackSegment {
impl TrackSegment {
pub fn push(&mut self, chunk: TrackpointChunk) {
self.push_shared(Rc::new(chunk));
}
fn push_shared(&mut self, chunk: Rc<TrackpointChunk>) {
if chunk.trkpt.is_empty() {
return;
}
self.cumul_length
.push(self.cumul_length.last().copied().unwrap_or_default() + chunk.trkpt.len());
self.chunks.push(Rc::new(chunk));
self.chunks.push(chunk);
}
/// Replaces the points in `start..end` by `points`. Panics if the range is out of bounds.
///
/// Chunks that are not concerned are kept as they are (shared), only the chunks around the
/// range are copied and refilled.
pub fn splice(&mut self, start: usize, end: usize, points: Vec<Trackpoint>) {
assert!(
start <= end && end <= self.len(),
"splice range out of bounds"
);
let old = std::mem::take(&mut self.chunks);
self.cumul_length.clear();
let mut pending = TrackpointChunk::default();
let mut inserted = false;
let mut offset = 0;
for chunk in old {
let (lo, hi) = (offset, offset + chunk.trkpt.len());
offset = hi;
// a chunk ending at `start` is extended unless it is full, to avoid tiny chunks
if hi < start || (hi == start && chunk.is_full()) {
self.push_shared(chunk);
continue;
}
if !inserted {
self.fill(&mut pending, chunk.trkpt[..start - lo].iter().cloned());
self.fill(&mut pending, points.iter().cloned());
inserted = true;
}
if hi <= end {
continue;
}
if lo >= end {
self.flush(&mut pending);
self.push_shared(chunk);
} else {
self.fill(&mut pending, chunk.trkpt[end - lo..].iter().cloned());
}
}
if !inserted {
self.fill(&mut pending, points);
}
self.flush(&mut pending);
}
fn fill(
&mut self,
pending: &mut TrackpointChunk,
points: impl IntoIterator<Item = Trackpoint>,
) {
for trkpt in points {
pending.trkpt.push(trkpt);
if pending.is_full() {
self.flush(pending);
}
}
}
fn flush(&mut self, pending: &mut TrackpointChunk) {
self.push(std::mem::take(pending));
}
pub fn len(&self) -> usize {
@@ -193,6 +258,81 @@ mod tests {
trkseg
}
fn eles(trkseg: &TrackSegment) -> Vec<f64> {
trkseg.iter().map(|p| p.ele).collect()
}
fn points(eles: &[f64]) -> Vec<Trackpoint> {
eles.iter()
.map(|&ele| Trackpoint {
ele,
..Default::default()
})
.collect()
}
#[test]
fn test_splice_replace_insert_delete_append() {
// chunks of 1..=5 points: [0] [1 2] [3 4 5] [6 7 8 9] [10..=14]
let mut trkseg = create_track_segment(5);
trkseg.splice(4, 8, points(&[-1.0, -2.0]));
let mut expected: Vec<f64> = (0..4).map(f64::from).collect();
expected.extend([-1.0, -2.0]);
expected.extend((8..15).map(f64::from));
assert_eq!(eles(&trkseg), expected);
assert_eq!(trkseg.len(), expected.len());
for i in 0..trkseg.len() {
assert_eq!(trkseg[i].ele, expected[i]);
}
trkseg.splice(2, 2, points(&[100.0]));
expected.insert(2, 100.0);
assert_eq!(eles(&trkseg), expected);
trkseg.splice(0, 3, vec![]);
expected.drain(..3);
assert_eq!(eles(&trkseg), expected);
let len = trkseg.len();
trkseg.splice(len, len, points(&[7.0, 8.0]));
expected.extend([7.0, 8.0]);
assert_eq!(eles(&trkseg), expected);
let len = trkseg.len();
trkseg.splice(0, len, vec![]);
assert_eq!(trkseg.len(), 0);
assert!(trkseg.first_index().is_none());
}
#[test]
fn test_splice_keeps_untouched_chunks_shared() {
let mut trkseg = create_track_segment(5);
let before = trkseg.chunks.clone();
// inside the third chunk only
trkseg.splice(4, 5, points(&[-1.0]));
assert!(Rc::ptr_eq(&trkseg.chunks[0], &before[0]));
assert!(Rc::ptr_eq(&trkseg.chunks[1], &before[1]));
assert!(Rc::ptr_eq(trkseg.chunks.last().unwrap(), &before[4]));
assert!(Rc::ptr_eq(
&trkseg.chunks[trkseg.chunks.len() - 2],
&before[3]
));
}
#[test]
fn test_splice_append_extends_last_chunk_and_respects_max_size() {
let mut trkseg = TrackSegment::default();
for i in 0..10_000 {
let len = trkseg.len();
trkseg.splice(len, len, points(&[i as f64]));
}
assert_eq!(trkseg.len(), 10_000);
assert!(trkseg.chunks.len() <= 3);
assert!(trkseg.chunks.iter().all(|c| c.trkpt.len() <= 4096));
assert_eq!(trkseg[9_999].ele, 9_999.0);
assert_eq!(*trkseg.cumul_length.last().unwrap(), 10_000);
}
#[test]
fn test_len() {
let nb_chunks = 10;
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::LngLat;
#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct Trackpoint {
pub coordinates: LngLat,
pub ele: f64,