use crate::xt::PerpDistance;
use rapidgeo_distance::LngLat;
#[cfg_attr(test, inline(never))]
pub fn simplify_mask<D: PerpDistance>(
pts: &[LngLat],
tolerance_m: f64,
backend: &D,
mask: &mut Vec<bool>,
) {
let n = pts.len();
mask.clear();
mask.resize(n, false);
if n <= 2 {
for item in mask.iter_mut().take(n) {
*item = true;
}
return;
}
let first_point = pts[0];
let all_identical = pts
.iter()
.all(|&p| p.lng_deg == first_point.lng_deg && p.lat_deg == first_point.lat_deg);
if all_identical {
for item in mask.iter_mut().take(n) {
*item = true;
}
return;
}
mask[0] = true;
mask[n - 1] = true;
let mut stack = Vec::new();
stack.push((0, n - 1));
while let Some((i, j)) = stack.pop() {
if j <= i + 1 {
continue;
}
let mut max_distance = 0.0;
let mut max_index = i + 1;
for k in (i + 1)..j {
let distance = backend.d_perp_m(pts[i], pts[j], pts[k]);
if distance > max_distance {
max_distance = distance;
max_index = k;
}
}
if max_distance > tolerance_m {
mask[max_index] = true;
stack.push((i, max_index));
stack.push((max_index, j));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xt::XtEuclid;
use rapidgeo_distance::LngLat;
#[test]
fn test_simplify_mask_all_identical_large() {
let pts = vec![LngLat::new_deg(0.0, 0.0); 100];
let mut mask = Vec::new();
let backend = XtEuclid;
simplify_mask(&pts, 5.0, &backend, &mut mask);
assert_eq!(mask.len(), 100);
for (i, &keep) in mask.iter().enumerate() {
assert!(
keep,
"Point {} should be kept when all points are identical",
i
);
}
}
#[test]
fn test_simplify_mask_two_identical_points() {
let pts = vec![LngLat::new_deg(-122.0, 37.0), LngLat::new_deg(-122.0, 37.0)];
let mut mask = Vec::new();
let backend = XtEuclid;
simplify_mask(&pts, 10.0, &backend, &mut mask);
assert_eq!(mask.len(), 2);
assert!(mask[0], "First point should be kept");
assert!(mask[1], "Second point should be kept");
}
#[test]
fn mask_clear_and_resize_happens() {
let pts = vec![
LngLat::new_deg(0.0, 0.0),
LngLat::new_deg(1.0, 0.0),
LngLat::new_deg(2.0, 0.0),
];
let mut mask = vec![true, true, true, true, true];
simplify_mask(&pts, 0.0, &XtEuclid, &mut mask);
assert_eq!(mask.len(), 3);
assert_eq!(mask, vec![true, false, true]);
}
#[test]
fn n_zero_one_two() {
let mut m = Vec::new();
simplify_mask(&[], 1.0, &XtEuclid, &mut m);
assert!(m.is_empty());
let pts1 = vec![LngLat::new_deg(0.0, 0.0)];
simplify_mask(&pts1, 1.0, &XtEuclid, &mut m);
assert_eq!(m, vec![true]);
let pts2 = vec![LngLat::new_deg(0.0, 0.0), LngLat::new_deg(1.0, 0.0)];
simplify_mask(&pts2, 1.0, &XtEuclid, &mut m);
assert_eq!(m, vec![true, true]);
}
#[test]
fn all_identical_three_points() {
let pts = vec![
LngLat::new_deg(7.0, -3.0),
LngLat::new_deg(7.0, -3.0),
LngLat::new_deg(7.0, -3.0),
];
let mut mask = Vec::new();
simplify_mask(&pts, 5.0, &XtEuclid, &mut mask);
assert_eq!(mask, vec![true, true, true]);
}
#[test]
fn single_split_then_children_continue() {
let pts = vec![
LngLat::new_deg(0.0, 0.0), LngLat::new_deg(1.0, 5.0), LngLat::new_deg(2.0, 0.0), ];
let mut mask = Vec::new();
simplify_mask(&pts, 0.0001, &XtEuclid, &mut mask);
assert_eq!(mask, vec![true, true, true]);
}
#[test]
fn high_tolerance_endpoints_only() {
let pts = vec![
LngLat::new_deg(0.0, 0.0),
LngLat::new_deg(0.2, 0.5),
LngLat::new_deg(0.4, -0.5),
LngLat::new_deg(0.6, 0.5),
LngLat::new_deg(0.8, -0.5),
LngLat::new_deg(1.0, 0.0),
];
let mut mask = Vec::new();
simplify_mask(&pts, 1_000_000.0, &XtEuclid, &mut mask);
assert_eq!(mask, vec![true, false, false, false, false, true]);
}
#[test]
fn tolerance_equality_boundary_no_split() {
let pts = vec![
LngLat::new_deg(0.0, 0.0), LngLat::new_deg(1.0, 1.0), LngLat::new_deg(2.0, 0.0), ];
let mut mask = Vec::new();
simplify_mask(&pts, 1.0, &XtEuclid, &mut mask);
assert_eq!(mask, vec![true, false, true]);
}
#[test]
fn tolerance_just_below_splits() {
let pts = vec![
LngLat::new_deg(0.0, 0.0), LngLat::new_deg(1.0, 1.0), LngLat::new_deg(2.0, 0.0), ];
let mut mask = Vec::new();
simplify_mask(&pts, 0.9999, &XtEuclid, &mut mask);
assert_eq!(mask, vec![true, true, true]);
}
#[test]
fn negative_tolerance_keeps_every_point() {
let mut pts = Vec::new();
for i in 0..50 {
let x = i as f64 * 0.1;
let y = if i % 2 == 0 { 0.0 } else { 1.0 };
pts.push(LngLat::new_deg(x, y));
}
let mut mask = Vec::new();
simplify_mask(&pts, -1.0, &XtEuclid, &mut mask);
assert_eq!(mask.len(), pts.len());
assert!(mask.iter().all(|&b| b));
}
}