geometry_kernel/
canonicalize.rs1use std::cmp::Ordering;
2
3use crate::precision::PrecisionModel;
4use crate::predicates::{is_ring_ccw, signed_ring_area};
5use crate::types::{Coord, LinearRing, MultiPolygon, Polygon};
6
7pub fn canonicalize_ring(
8 ring: &LinearRing,
9 desired_ccw: bool,
10 precision: PrecisionModel,
11) -> LinearRing {
12 let mut coords = dedup_open_coords(&ring.coords, precision);
13 if coords.len() < 3 {
14 return LinearRing { coords: Vec::new() };
15 }
16
17 let mut closed = coords.clone();
18 closed.push(coords[0]);
19 let mut normalized = LinearRing::new(closed);
20
21 if signed_ring_area(&normalized).abs() <= precision.epsilon() {
22 return LinearRing { coords: Vec::new() };
23 }
24
25 if is_ring_ccw(&normalized) != desired_ccw {
26 coords.reverse();
27 }
28
29 rotate_to_smallest_coord(&mut coords);
30 coords.push(coords[0]);
31 normalized = LinearRing::new(coords);
32 normalized
33}
34
35pub fn canonicalize_polygon(polygon: &Polygon, precision: PrecisionModel) -> Polygon {
36 let exterior = canonicalize_ring(&polygon.exterior, true, precision);
37 if exterior.coords.is_empty() {
38 return Polygon::empty();
39 }
40
41 let mut holes = polygon
42 .holes
43 .iter()
44 .map(|ring| canonicalize_ring(ring, false, precision))
45 .filter(|ring| !ring.coords.is_empty())
46 .collect::<Vec<_>>();
47
48 holes.sort_by(compare_rings);
49 Polygon::new(exterior, holes)
50}
51
52pub fn canonicalize_multi_polygon(
53 multi_polygon: &MultiPolygon,
54 precision: PrecisionModel,
55) -> MultiPolygon {
56 let mut polygons = multi_polygon
57 .polygons
58 .iter()
59 .map(|polygon| canonicalize_polygon(polygon, precision))
60 .filter(|polygon| !polygon.is_empty())
61 .collect::<Vec<_>>();
62
63 polygons.sort_by(|left, right| {
64 let area_cmp = signed_ring_area(&right.exterior)
65 .abs()
66 .partial_cmp(&signed_ring_area(&left.exterior).abs())
67 .unwrap_or(Ordering::Equal);
68 area_cmp.then_with(|| compare_rings(&left.exterior, &right.exterior))
69 });
70
71 MultiPolygon::new(polygons)
72}
73
74fn dedup_open_coords(coords: &[Coord], precision: PrecisionModel) -> Vec<Coord> {
75 let mut out = Vec::new();
76 for coord in coords {
77 let snapped = precision.snap_coord(*coord);
78 if out
79 .last()
80 .is_none_or(|last| !precision.same_coord(*last, snapped))
81 {
82 out.push(snapped);
83 }
84 }
85
86 while out.len() > 1 && precision.same_coord(out[0], *out.last().expect("checked len")) {
87 out.pop();
88 }
89
90 out
91}
92
93fn rotate_to_smallest_coord(coords: &mut [Coord]) {
94 if coords.is_empty() {
95 return;
96 }
97
98 let min_index = coords
99 .iter()
100 .enumerate()
101 .min_by(|(_, left), (_, right)| compare_coords(left, right))
102 .map(|(index, _)| index)
103 .unwrap_or(0);
104
105 coords.rotate_left(min_index);
106}
107
108fn compare_rings(left: &LinearRing, right: &LinearRing) -> Ordering {
109 left.coords
110 .iter()
111 .zip(right.coords.iter())
112 .map(|(left, right)| compare_coords(left, right))
113 .find(|ordering| *ordering != Ordering::Equal)
114 .unwrap_or_else(|| left.coords.len().cmp(&right.coords.len()))
115}
116
117fn compare_coords(left: &Coord, right: &Coord) -> Ordering {
118 left.x
119 .partial_cmp(&right.x)
120 .unwrap_or(Ordering::Equal)
121 .then_with(|| left.y.partial_cmp(&right.y).unwrap_or(Ordering::Equal))
122}