use crate::{xt::PerpDistance, SimplifyMethod};
use rapidgeo_distance::LngLat;
#[cfg(feature = "batch")]
use rayon::prelude::*;
const PARALLEL_DISTANCE_THRESHOLD: usize = 100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BatchError {
BufferTooSmall {
needed: usize,
provided: usize,
},
}
#[cfg(feature = "batch")]
pub fn simplify_batch_par(
polylines: &[Vec<LngLat>],
tolerance_m: f64,
method: SimplifyMethod,
) -> Vec<Vec<LngLat>> {
polylines
.par_iter()
.map(|polyline| {
let mut simplified = Vec::new();
crate::simplify_dp_into(polyline, tolerance_m, method, &mut simplified);
simplified
})
.collect()
}
#[cfg(feature = "batch")]
pub fn simplify_batch_par_into(
polylines: &[Vec<LngLat>],
tolerance_m: f64,
method: SimplifyMethod,
output: &mut [Vec<LngLat>],
) -> Result<(), BatchError> {
if output.len() < polylines.len() {
return Err(BatchError::BufferTooSmall {
needed: polylines.len(),
provided: output.len(),
});
}
output[..polylines.len()]
.par_iter_mut()
.zip(polylines.par_iter())
.for_each(|(out_polyline, in_polyline)| {
crate::simplify_dp_into(in_polyline, tolerance_m, method, out_polyline);
});
Ok(())
}
pub fn simplify_batch(
polylines: &[Vec<LngLat>],
tolerance_m: f64,
method: SimplifyMethod,
) -> Vec<Vec<LngLat>> {
polylines
.iter()
.map(|polyline| {
let mut simplified = Vec::new();
crate::simplify_dp_into(polyline, tolerance_m, method, &mut simplified);
simplified
})
.collect()
}
pub fn simplify_batch_into(
polylines: &[Vec<LngLat>],
tolerance_m: f64,
method: SimplifyMethod,
output: &mut [Vec<LngLat>],
) -> Result<(), BatchError> {
if output.len() < polylines.len() {
return Err(BatchError::BufferTooSmall {
needed: polylines.len(),
provided: output.len(),
});
}
for (out_polyline, in_polyline) in output[..polylines.len()].iter_mut().zip(polylines.iter()) {
crate::simplify_dp_into(in_polyline, tolerance_m, method, out_polyline);
}
Ok(())
}
#[cfg(feature = "batch")]
pub fn simplify_dp_mask_par(
pts: &[LngLat],
tolerance_m: f64,
method: SimplifyMethod,
mask: &mut Vec<bool>,
) {
use crate::xt::*;
match method {
SimplifyMethod::GreatCircleMeters => {
let backend = XtGreatCircle;
simplify_mask_par(pts, tolerance_m, &backend, mask);
}
SimplifyMethod::PlanarMeters => {
let midpoint = crate::compute_midpoint(pts);
let backend = XtEnu { origin: midpoint };
simplify_mask_par(pts, tolerance_m, &backend, mask);
}
SimplifyMethod::EuclidRaw => {
let backend = XtEuclid;
simplify_mask_par(pts, tolerance_m, &backend, mask);
}
}
}
#[cfg(feature = "batch")]
pub fn simplify_dp_into_par(
pts: &[LngLat],
tolerance_m: f64,
method: SimplifyMethod,
out: &mut Vec<LngLat>,
) -> usize {
out.clear();
let mut mask = vec![false; pts.len()];
simplify_dp_mask_par(pts, tolerance_m, method, &mut mask);
for (i, &keep) in mask.iter().enumerate() {
if keep {
out.push(pts[i]);
}
}
out.len()
}
#[cfg(feature = "batch")]
fn simplify_mask_par<D: PerpDistance + Sync>(
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 candidate_range = (i + 1)..j;
let num_candidates = candidate_range.len();
let (max_distance, max_index) = if num_candidates > PARALLEL_DISTANCE_THRESHOLD {
candidate_range
.into_par_iter()
.map(|k| (backend.d_perp_m(pts[i], pts[j], pts[k]), k))
.reduce_with(|(max_dist, max_idx), (dist, idx)| {
if dist > max_dist {
(dist, idx)
} else {
(max_dist, max_idx)
}
})
.unwrap_or((0.0, i + 1))
} else {
let mut max_distance = 0.0;
let mut max_index = i + 1;
for k in candidate_range {
let distance = backend.d_perp_m(pts[i], pts[j], pts[k]);
if distance > max_distance {
max_distance = distance;
max_index = k;
}
}
(max_distance, max_index)
};
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 rapidgeo_distance::LngLat;
fn create_test_polylines() -> Vec<Vec<LngLat>> {
vec![
vec![
LngLat::new_deg(-122.4194, 37.7749), LngLat::new_deg(-121.5, 37.0),
LngLat::new_deg(-118.2437, 34.0522), ],
vec![
LngLat::new_deg(-74.0060, 40.7128), LngLat::new_deg(-75.0, 40.0),
LngLat::new_deg(-87.6298, 41.8781), ],
]
}
#[test]
fn test_simplify_batch() {
let polylines = create_test_polylines();
let simplified = simplify_batch(&polylines, 1000.0, SimplifyMethod::GreatCircleMeters);
assert_eq!(simplified.len(), 2);
for simplified_line in &simplified {
assert!(simplified_line.len() >= 2);
}
}
#[test]
fn test_simplify_batch_into() {
let polylines = create_test_polylines();
let mut output = vec![Vec::new(); 3];
let result = simplify_batch_into(
&polylines,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut output,
);
assert!(result.is_ok());
assert!(output[0].len() >= 2); assert!(output[1].len() >= 2); assert_eq!(output[2].len(), 0); }
#[test]
fn test_simplify_batch_into_too_small() {
let polylines = create_test_polylines();
let mut output = vec![Vec::new(); 1];
let result = simplify_batch_into(
&polylines,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut output,
);
assert!(result.is_err());
match result.unwrap_err() {
BatchError::BufferTooSmall { needed, provided } => {
assert_eq!(needed, 2);
assert_eq!(provided, 1);
}
}
}
#[test]
#[cfg(feature = "batch")]
fn test_simplify_batch_par() {
let polylines = create_test_polylines();
let simplified = simplify_batch_par(&polylines, 1000.0, SimplifyMethod::GreatCircleMeters);
assert_eq!(simplified.len(), 2);
for simplified_line in &simplified {
assert!(simplified_line.len() >= 2);
}
let serial_simplified =
simplify_batch(&polylines, 1000.0, SimplifyMethod::GreatCircleMeters);
assert_eq!(simplified, serial_simplified);
}
#[test]
#[cfg(feature = "batch")]
fn test_simplify_batch_par_into_too_small() {
let polylines = create_test_polylines();
let mut par_output = vec![Vec::new(); 1];
let result = simplify_batch_par_into(
&polylines,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut par_output,
);
assert!(result.is_err());
match result.unwrap_err() {
BatchError::BufferTooSmall { needed, provided } => {
assert_eq!(needed, 2);
assert_eq!(provided, 1);
}
}
}
#[test]
#[cfg(feature = "batch")]
fn test_simplify_batch_par_into() {
let polylines = create_test_polylines();
let mut par_output = vec![Vec::new(); 2];
let mut serial_output = vec![Vec::new(); 2];
let par_result = simplify_batch_par_into(
&polylines,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut par_output,
);
let serial_result = simplify_batch_into(
&polylines,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut serial_output,
);
assert!(par_result.is_ok());
assert!(serial_result.is_ok());
assert_eq!(par_output, serial_output);
}
#[test]
#[cfg(feature = "batch")]
fn test_simplify_dp_mask_par() {
let points = vec![
LngLat::new_deg(-122.0, 37.0),
LngLat::new_deg(-121.5, 37.5),
LngLat::new_deg(-121.0, 37.0),
];
let mut par_mask = Vec::new();
let mut serial_mask = Vec::new();
simplify_dp_mask_par(
&points,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut par_mask,
);
crate::simplify_dp_mask(
&points,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut serial_mask,
);
assert_eq!(par_mask, serial_mask);
assert!(par_mask[0]); assert!(par_mask[2]); }
#[test]
#[cfg(feature = "batch")]
fn test_simplify_dp_mask_par_planar_meters() {
let points = vec![
LngLat::new_deg(-122.0, 37.0),
LngLat::new_deg(-121.5, 37.5),
LngLat::new_deg(-121.0, 37.0),
];
let mut par_mask = Vec::new();
let mut serial_mask = Vec::new();
simplify_dp_mask_par(&points, 1000.0, SimplifyMethod::PlanarMeters, &mut par_mask);
crate::simplify_dp_mask(
&points,
1000.0,
SimplifyMethod::PlanarMeters,
&mut serial_mask,
);
assert_eq!(par_mask, serial_mask);
assert!(par_mask[0]); assert!(par_mask[2]); }
#[test]
#[cfg(feature = "batch")]
fn test_simplify_dp_mask_par_euclid_raw() {
let points = vec![
LngLat::new_deg(-122.0, 37.0),
LngLat::new_deg(-121.5, 37.5),
LngLat::new_deg(-121.0, 37.0),
];
let mut par_mask = Vec::new();
let mut serial_mask = Vec::new();
simplify_dp_mask_par(&points, 0.5, SimplifyMethod::EuclidRaw, &mut par_mask);
crate::simplify_dp_mask(&points, 0.5, SimplifyMethod::EuclidRaw, &mut serial_mask);
assert_eq!(par_mask, serial_mask);
assert!(par_mask[0]); assert!(par_mask[2]); }
#[test]
#[cfg(feature = "batch")]
fn test_simplify_dp_into_par() {
let points = vec![
LngLat::new_deg(-122.0, 37.0),
LngLat::new_deg(-121.5, 37.5),
LngLat::new_deg(-121.0, 37.0),
];
let mut par_output = Vec::new();
let mut serial_output = Vec::new();
let par_count = simplify_dp_into_par(
&points,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut par_output,
);
let serial_count = crate::simplify_dp_into(
&points,
1000.0,
SimplifyMethod::GreatCircleMeters,
&mut serial_output,
);
assert_eq!(par_count, serial_count);
assert_eq!(par_output, serial_output);
assert_eq!(par_count, par_output.len());
}
#[test]
#[cfg(feature = "batch")]
fn test_parallel_threshold_behavior() {
let mut large_polyline = Vec::new();
for i in 0..1000 {
large_polyline.push(LngLat::new_deg(
-122.0 + i as f64 * 0.001,
37.0 + (i as f64 * 0.1).sin() * 0.01,
));
}
let mut par_mask = Vec::new();
let mut serial_mask = Vec::new();
simplify_dp_mask_par(
&large_polyline,
50.0,
SimplifyMethod::GreatCircleMeters,
&mut par_mask,
);
crate::simplify_dp_mask(
&large_polyline,
50.0,
SimplifyMethod::GreatCircleMeters,
&mut serial_mask,
);
assert_eq!(par_mask, serial_mask);
assert!(par_mask[0]); assert!(par_mask[par_mask.len() - 1]); }
#[test]
fn test_different_methods_batch() {
let polylines = vec![vec![
LngLat::new_deg(-122.0, 37.0),
LngLat::new_deg(-121.5, 37.5),
LngLat::new_deg(-121.0, 37.0),
]];
for method in [
SimplifyMethod::GreatCircleMeters,
SimplifyMethod::PlanarMeters,
SimplifyMethod::EuclidRaw,
] {
let simplified = simplify_batch(&polylines, 1000.0, method);
assert_eq!(simplified.len(), 1);
assert!(simplified[0].len() >= 2); }
}
#[test]
fn test_empty_and_small_polylines() {
let polylines = vec![
vec![], vec![LngLat::new_deg(-122.0, 37.0)], vec![LngLat::new_deg(-122.0, 37.0), LngLat::new_deg(-121.0, 37.0)], ];
let simplified = simplify_batch(&polylines, 1000.0, SimplifyMethod::GreatCircleMeters);
assert_eq!(simplified.len(), 3);
assert_eq!(simplified[0].len(), 0); assert_eq!(simplified[1].len(), 1); assert_eq!(simplified[2].len(), 2); }
}