geo_normalized/
lib.rs

1use geo::algorithm::winding_order::Winding;
2use geo::{Coordinate, Geometry, GeometryCollection, LineString, MultiPolygon, Polygon};
3use num_traits;
4
5pub trait Normalized<T: num_traits::Float> {
6    /// This trait returns a new geo-types Polygon/Multipolygon that follows the OGC winding rules
7    ///
8    /// The rust geo and geo-types crates are not as strict as the OGC guidelines,
9    /// and allow for polygons with inner and outer rings in any winding order.
10    /// This trait returns a Polygon/Multipolygon where all outer rings are clockwise,
11    /// and all inner rings are anti-clockwise.
12    ///
13    /// # Examples
14    ///
15    /// ```
16    /// // Anti-clockwise winding order for outer ring
17    /// let bad = polygon![
18    ///         (x: 1.0, y: 1.0),
19    ///         (x: 4.0, y: 1.0),
20    ///         (x: 4.0, y: 4.0),
21    ///         (x: 1.0, y: 4.0),
22    ///         (x: 1.0, y: 1.0),
23    ///         ];
24    ///
25    /// // Clockwise winding order for outer ring
26    /// let good = polygon![
27    ///         (x: 1.0, y: 1.0),
28    ///         (x: 1.0, y: 4.0),
29    ///         (x: 4.0, y: 4.0),
30    ///         (x: 4.0, y: 1.0),
31    ///         (x: 1.0, y: 1.0),
32    ///         ];
33    ///
34    /// let norm = bad.normalized();
35    /// // norm should have the same points and shape as `bad` but in the valid winding order
36    /// assert_eq!(norm, good);
37    /// ```
38    ///
39    fn normalized(&self) -> Self;
40}
41
42/** Geometry Collections */
43
44impl<T: num_traits::Float> Normalized<T> for GeometryCollection<T> {
45    fn normalized(&self) -> Self {
46        GeometryCollection(
47            self.0
48                .iter()
49                .map(|p| match p {
50                    Geometry::Polygon { .. } => {
51                        Geometry::Polygon(p.clone().into_polygon().unwrap().normalized())
52                    }
53                    Geometry::MultiPolygon { .. } => {
54                        Geometry::MultiPolygon(p.clone().into_multi_polygon().unwrap().normalized())
55                    }
56                    _ => p.clone(),
57                })
58                .collect::<Vec<Geometry<T>>>(),
59        )
60    }
61}
62
63/** Polygons */
64
65impl<T: num_traits::Float> Normalized<T> for MultiPolygon<T> {
66    fn normalized(&self) -> Self {
67        MultiPolygon::from(
68            self.0
69                .iter()
70                .map(|x| x.normalized())
71                .collect::<Vec<Polygon<T>>>(),
72        )
73    }
74}
75
76impl<T: num_traits::Float> Normalized<T> for Polygon<T> {
77    fn normalized(&self) -> Self {
78        normalized_polygon(self)
79    }
80}
81
82/// Return a new polygon where the exterior ring points are clockwise and interior ring points are
83/// counter-clockwise
84///
85fn normalized_polygon<T: num_traits::Float>(poly: &Polygon<T>) -> Polygon<T> {
86    Polygon::new(
87        LineString::from(
88            poly.exterior()
89                .points_cw()
90                .map(|x| x.0)
91                .collect::<Vec<Coordinate<T>>>(),
92        ),
93        poly.interiors()
94            .iter()
95            .map(|ring| {
96                LineString::from(
97                    ring.clone()
98                        .points_ccw()
99                        .map(|x| x.0)
100                        .collect::<Vec<Coordinate<T>>>(),
101                )
102            })
103            .collect(),
104    )
105}
106
107/** Tests */
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use geo::{line_string, point, polygon};
113
114    #[test]
115    fn does_not_change_good_polygon() {
116        let (good, _) = get_bad_outer_poly();
117        let norm = good.normalized();
118        assert_eq!(norm, good);
119    }
120
121    #[test]
122    fn can_normalize_bad_outer_polygon() {
123        let (good, bad) = get_bad_outer_poly();
124        let norm = bad.normalized();
125        assert_eq!(norm, good);
126    }
127
128    #[test]
129    fn can_normalize_good_outer_bad_inner_polygon() {
130        let (good, bad) = get_good_outer_bad_inner_poly();
131        let norm = bad.normalized();
132        assert_eq!(norm, good);
133    }
134
135    #[test]
136    fn can_normalize_bad_outer_bad_inner_polygon() {
137        let (good, bad) = get_bad_outer_bad_inner_poly();
138        let norm = bad.normalized();
139        assert_eq!(norm, good);
140    }
141
142    #[test]
143    fn can_normalize_bad_outer_good_inner_polygon() {
144        let (good, bad) = get_bad_outer_good_inner_poly();
145        let norm = bad.normalized();
146        assert_eq!(norm, good);
147    }
148
149    #[test]
150    fn can_process_multi_polygon() {
151        let (good, bad) = get_bad_outer_good_inner_poly();
152        let mp = MultiPolygon(vec![good.clone(), bad]);
153        let norm = mp.normalized();
154        for poly in norm {
155            assert_eq!(good, poly);
156        }
157    }
158
159    fn get_bad_outer_poly() -> (Polygon<f64>, Polygon<f64>) {
160        let bad = polygon![
161        (x: 1.0, y: 1.0),
162        (x: 4.0, y: 1.0),
163        (x: 4.0, y: 4.0),
164        (x: 1.0, y: 4.0),
165        (x: 1.0, y: 1.0),
166        ];
167        let good = polygon![
168        (x: 1.0, y: 1.0),
169        (x: 1.0, y: 4.0),
170        (x: 4.0, y: 4.0),
171        (x: 4.0, y: 1.0),
172        (x: 1.0, y: 1.0),
173        ];
174        (good, bad)
175    }
176
177    fn get_good_outer_bad_inner_poly() -> (Polygon<f64>, Polygon<f64>) {
178        let bad = polygon!(
179            exterior: [
180                (x: 0., y: 0.),
181                (x: 0., y: 50.),
182                (x: 50., y: 50.),
183                (x: 50., y: 0.),
184            ],
185            interiors: [
186                [
187                    (x: 10., y: 10.),
188                    (x: 10., y: 20.),
189                    (x: 20., y: 20.),
190                    (x: 20., y: 10.),
191                ],
192            ],
193        );
194        let good = polygon!(
195            exterior: [
196                (x: 0., y: 0.),
197                (x: 0., y: 50.),
198                (x: 50., y: 50.),
199                (x: 50., y: 0.),
200            ],
201            interiors: [
202                [
203                    (x: 10., y: 10.),
204                    (x: 20., y: 10.),
205                    (x: 20., y: 20.),
206                    (x: 10., y: 20.),
207                ],
208            ],
209        );
210        (good, bad)
211    }
212
213    fn get_bad_outer_bad_inner_poly() -> (Polygon<f64>, Polygon<f64>) {
214        let bad = polygon!(
215            exterior: [
216                (x: 0., y: 0.),
217                (x: 50., y: 0.),
218                (x: 50., y: 50.),
219                (x: 0., y: 50.),
220            ],
221            interiors: [
222                [
223                    (x: 10., y: 10.),
224                    (x: 10., y: 20.),
225                    (x: 20., y: 20.),
226                    (x: 20., y: 10.),
227                ],
228            ],
229        );
230        let good = polygon!(
231            exterior: [
232                (x: 0., y: 0.),
233                (x: 0., y: 50.),
234                (x: 50., y: 50.),
235                (x: 50., y: 0.),
236            ],
237            interiors: [
238                [
239                    (x: 10., y: 10.),
240                    (x: 20., y: 10.),
241                    (x: 20., y: 20.),
242                    (x: 10., y: 20.),
243                ],
244            ],
245        );
246        (good, bad)
247    }
248
249    fn get_bad_outer_good_inner_poly() -> (Polygon<f64>, Polygon<f64>) {
250        let bad = polygon!(
251            exterior: [
252                (x: 0., y: 0.),
253                (x: 50., y: 0.),
254                (x: 50., y: 50.),
255                (x: 0., y: 50.),
256            ],
257            interiors: [
258                [
259                    (x: 10., y: 10.),
260                    (x: 20., y: 10.),
261                    (x: 20., y: 20.),
262                    (x: 10., y: 20.),
263                ],
264            ],
265        );
266        let good = polygon!(
267            exterior: [
268                (x: 0., y: 0.),
269                (x: 0., y: 50.),
270                (x: 50., y: 50.),
271                (x: 50., y: 0.),
272            ],
273            interiors: [
274                [
275                    (x: 10., y: 10.),
276                    (x: 20., y: 10.),
277                    (x: 20., y: 20.),
278                    (x: 10., y: 20.),
279                ],
280            ],
281        );
282        (good, bad)
283    }
284}