gistools/geometry/tools/lines/
clean.rs

1use crate::geometry::ClampWGS84Point;
2use alloc::{vec, vec::Vec};
3use libm::fabs;
4use s2json::{GetXY, SetXY};
5
6/// Removes superfluous/collinear points from a collection of linestrings
7///
8/// ## Parameters
9/// - `lines`: the linestring to clean
10/// - `eps`: the tolerance. Defaults to `1e-12`
11/// - `clean_wgs84`: if true, clean WGS84 points to be valid WGS84 points
12///
13/// ## Returns
14/// The cleaned linestrings
15pub fn clean_linestrings<P: GetXY + SetXY + Clone + PartialEq>(
16    lines: &[Vec<P>],
17    is_poly: bool,
18    eps: Option<f64>,
19    clean_wgs84: bool,
20) -> Option<Vec<Vec<P>>> {
21    let res: Vec<Vec<P>> =
22        lines.iter().filter_map(|p| clean_linestring(p, is_poly, eps, clean_wgs84)).collect();
23    if res.is_empty() { None } else { Some(res) }
24}
25
26/// Removes superfluous/collinear points from a linestring
27///
28/// ## Parameters
29/// - `line`: the linestring to clean
30/// - `eps`: the toleranc to check if the segments are superfluous/collinear. Defaults to `1e-12`
31/// - `clean_wgs84`: if true, clean WGS84 points to be valid WGS84 points
32///
33/// ## Returns
34/// The cleaned linestring
35pub fn clean_linestring<P: GetXY + SetXY + Clone + PartialEq>(
36    line: &[P],
37    is_poly: bool,
38    eps: Option<f64>,
39    clean_wgs84: bool,
40) -> Option<Vec<P>> {
41    if if is_poly { line.len() < 4 } else { line.len() < 2 } {
42        return None;
43    }
44    let eps = eps.unwrap_or(1e-12);
45    // First remove all duplicates
46    let mut no_dups: Vec<&P> = vec![&line[0]];
47    for line in line.iter().skip(1) {
48        if line != no_dups[no_dups.len() - 1] {
49            no_dups.push(line);
50        }
51    }
52    // Then remove superfluous/collinear points
53    let mut cleaned: Vec<P> = vec![no_dups[0].clone()];
54    for i in 1..no_dups.len() - 1 {
55        let prev = &no_dups[i - 1];
56        let curr = &no_dups[i];
57        let next = &no_dups[i + 1];
58        let area = (curr.y() - prev.y()) * (next.x() - curr.x())
59            - (curr.x() - prev.x()) * (next.y() - curr.y());
60        if fabs(area) > eps {
61            cleaned.push((*curr).clone());
62        }
63    }
64    cleaned.push(no_dups[no_dups.len() - 1].clone());
65    // check again if linestring is valid
66    if if is_poly { cleaned.len() < 4 } else { cleaned.len() < 2 } {
67        return None;
68    }
69    // if user want's valid WGS84 points let's fix them
70    if clean_wgs84 {
71        cleaned.iter_mut().for_each(|p| p.clamp_wgs84());
72    }
73
74    Some(cleaned)
75}