mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-04 12:58:23 +00:00
splice
This commit is contained in:
@@ -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,6 +1,6 @@
|
||||
use crate::LngLat;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Trackpoint {
|
||||
pub coordinates: LngLat,
|
||||
pub ele: f64,
|
||||
|
||||
Reference in New Issue
Block a user