mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-17 08:42:28 +00:00
142 lines
4.0 KiB
Rust
142 lines
4.0 KiB
Rust
#[macro_export]
|
|
macro_rules! for_each_window {
|
|
(
|
|
$trkseg:expr,
|
|
$left:expr,
|
|
$right:expr,
|
|
$window:expr,
|
|
|$a:ident, $b:ident| $distance:expr,
|
|
|$i:ident, $l:ident, $r:ident| $body:block,
|
|
) => {{
|
|
let mut window_start = $left.unwrap_or_default();
|
|
let mut window_center = $left;
|
|
let mut window_end = $left.unwrap_or_default();
|
|
while let Some(idx) = window_center {
|
|
// advance left if needed
|
|
while window_start != idx && {
|
|
let $a = window_start;
|
|
let $b = idx;
|
|
$distance
|
|
} > $window {
|
|
if let Some(next) = $trkseg.next_index(Some(window_start)) {
|
|
if next == idx {
|
|
break;
|
|
}
|
|
window_start = next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// advance right if needed
|
|
window_end = window_end.max(idx);
|
|
while let Some(next) = $trkseg.next_index(Some(window_end)) {
|
|
if window_end != idx && {
|
|
let $a = idx;
|
|
let $b = next;
|
|
$distance
|
|
} > $window {
|
|
break;
|
|
}
|
|
window_end = next;
|
|
}
|
|
// apply body
|
|
let $i = idx;
|
|
let $l = window_start;
|
|
let $r = window_end;
|
|
$body
|
|
// go next
|
|
if window_center == $right {
|
|
break;
|
|
}
|
|
window_center = $trkseg.next_index(window_center);
|
|
}
|
|
}};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{TrackSegment, Trackpoint, TrackpointChunk};
|
|
|
|
fn segment(eles: &[f64]) -> TrackSegment {
|
|
let mut trkseg = TrackSegment::default();
|
|
// several small chunks, to also exercise the chunk boundaries
|
|
for part in eles.chunks(3) {
|
|
let mut chunk = TrackpointChunk::default();
|
|
for ele in part {
|
|
chunk.trkpt.push(Trackpoint {
|
|
ele: *ele,
|
|
..Default::default()
|
|
});
|
|
}
|
|
trkseg.push(chunk);
|
|
}
|
|
trkseg
|
|
}
|
|
|
|
/// Returns (center, left, right) flat indices for each visited center.
|
|
fn windows(len: usize, window: f64) -> Vec<(usize, usize, usize)> {
|
|
let trkseg = segment(&vec![0.0; len]);
|
|
let mut visited = vec![];
|
|
for_each_window!(
|
|
trkseg,
|
|
trkseg.first_index(),
|
|
trkseg.last_index(),
|
|
window,
|
|
|a, b| (b.flat - a.flat) as f64,
|
|
|i, l, r| {
|
|
visited.push((i.flat, l.flat, r.flat));
|
|
},
|
|
);
|
|
visited
|
|
}
|
|
|
|
#[test]
|
|
fn test_window_bounds() {
|
|
let visited = windows(10, 2.0);
|
|
assert_eq!(visited.len(), 10);
|
|
for (i, l, r) in visited {
|
|
assert_eq!(l, i.saturating_sub(2), "left of {i}");
|
|
assert_eq!(r, (i + 2).min(9), "right of {i}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_window_always_includes_neighbours() {
|
|
for (i, l, r) in windows(6, 0.0) {
|
|
assert_eq!((l, r), (i.saturating_sub(1), (i + 1).min(5)));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_window_larger_than_segment() {
|
|
for (_, l, r) in windows(5, 100.0) {
|
|
assert_eq!((l, r), (0, 4));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_window_sub_range() {
|
|
let trkseg = segment(&[0.0; 10]);
|
|
let mut centers = vec![];
|
|
let mut first = trkseg.first_index();
|
|
for _ in 0..3 {
|
|
first = trkseg.next_index(first);
|
|
}
|
|
let mut last = first;
|
|
for _ in 0..2 {
|
|
last = trkseg.next_index(last);
|
|
}
|
|
for_each_window!(
|
|
trkseg,
|
|
first,
|
|
last,
|
|
1.0,
|
|
|a, b| (b.flat - a.flat) as f64,
|
|
|i, _l, _r| {
|
|
centers.push(i.flat);
|
|
},
|
|
);
|
|
assert_eq!(centers, vec![3, 4, 5]);
|
|
}
|
|
}
|