geo_normalized2/
lib.rs

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