gistools/geometry/tools/polys/
clean.rs

1use crate::geometry::{Area, clean_linestring, dekink_polygon};
2use alloc::{vec, vec::Vec};
3use s2json::{BBox, FullXY};
4
5/// Ensures the collection of polygon ring order is correct, removes duplicate points,
6/// and runs a dekink to be thorough.
7///
8/// NOTE: This will not remove/reduce points that follow a path angle like [[0, 0], [0, 1], [0, 2], ...].
9/// The decision to leave this to the user is due to the fact that not all projections are guaranteed
10/// to support a linear relationship. Also sometimes the user want's to have these extra points for
11/// future/cleaner projection changes. For example, having higher precision works well when
12/// translating to spherical projections for instance. If you do want to remove these points, pass
13/// in true to `remove_collinear_points`
14///
15/// ## Parameters
16/// - `polygons`: the collection of polygon as either a VectorFeature, VectorMultiPolygonGeometry, or raw VectorMultiPolygon
17/// - `remove_collinear_points`: - if true, remove superfluous points
18/// - `clean_wgs84`: if true, clean WGS84 points to be valid WGS84 points
19///
20/// ## Returns
21/// The cleaned polygons as a new collection of polygons
22pub fn clean_polygons<P: FullXY>(
23    polygons: &[Vec<Vec<P>>],
24    remove_collinear_points: bool,
25    clean_wgs84: bool,
26) -> Option<(Vec<Vec<Vec<P>>>, BBox)> {
27    let mut res: Vec<Vec<Vec<P>>> = vec![];
28    let mut final_bbox: BBox = BBox::default();
29
30    for p in polygons {
31        if let Some((mut cleaned, bbox)) = clean_polygon(p, remove_collinear_points, clean_wgs84) {
32            res.append(&mut cleaned);
33            final_bbox.merge_in_place(&bbox);
34        }
35    }
36    if res.is_empty() { None } else { Some((res, final_bbox)) }
37}
38
39/// Ensures the polygon ring order is correct, removes duplicate points, and runs a dekink to be
40/// thorough.
41///
42/// NOTE: This will not remove/reduce points that follow a path angle like [[0, 0], [0, 1], [0, 2], ...].
43/// The decision to leave this to the user is due to the fact that not all projections are guaranteed
44/// to support a linear relationship. Also sometimes the user want's to have these extra points for
45/// future/cleaner projection changes. For example, having higher precision works well when
46/// translating to spherical projections for instance. If you do want to remove these points, pass
47/// in true to `remove_collinear_points`
48///
49/// ## Parameters
50/// - `polygon`: the polygon as either a VectorFeature, VectorPolygonGeometry, or raw VectorPolygon
51/// - `remove_collinear_points`: if true, remove superfluous points
52/// - `clean_wgs84`: if true, clean WGS84 points to be valid WGS84 points
53///
54/// ## Returns
55/// The cleaned polygon, split into a multi-polygon as necessary
56pub fn clean_polygon<P: FullXY>(
57    polygon: &[Vec<P>],
58    remove_collinear_points: bool,
59    clean_wgs84: bool,
60) -> Option<(Vec<Vec<Vec<P>>>, BBox)> {
61    // remove duplicates from the rings
62    let mut res: Vec<Vec<P>> = vec![];
63    for (index, ring) in polygon.iter().enumerate() {
64        let mut last_point: Option<&P> = None;
65
66        if remove_collinear_points {
67            match clean_linestring(ring, true, None, clean_wgs84) {
68                Some(cleaned) => res.push(cleaned),
69                None => {
70                    if index == 0 {
71                        return None;
72                    }
73                }
74            }
75        } else {
76            let mut new_ring: Vec<P> = vec![];
77            for point in ring {
78                if last_point.is_none() || *point != *last_point.unwrap() {
79                    new_ring.push(point.clone());
80                    last_point = Some(point);
81                }
82            }
83            if new_ring.len() >= 4 {
84                res.push(new_ring);
85            } else if index == 0 {
86                return None;
87            }
88        }
89    }
90    // run polygon_ring_area for each ring and invert if it's direction is wrong for the ring type
91    for (i, ring) in res.iter_mut().enumerate() {
92        let area = ring.area(Some(1.));
93        // flip the ring if outer-ring and area is negative OR inner-ring and area is positive
94        if if i == 0 { area < 0. } else { area > 0. } {
95            ring.reverse();
96        }
97    }
98
99    dekink_polygon(&res)
100}