geo/algorithm/
orient.rs

1use crate::{GeoNum, MultiPolygon, Polygon};
2
3use crate::winding_order::{Winding, WindingOrder};
4
5pub trait Orient {
6    /// Orients a Polygon's exterior and interior rings according to convention
7    ///
8    /// By default, the exterior ring of a Polygon is oriented counter-clockwise, and any interior
9    /// rings are oriented clockwise.
10    ///
11    /// # Examples
12    ///
13    /// ```
14    /// use geo::orient::{Direction, Orient};
15    /// use geo::polygon;
16    ///
17    /// // a diamond shape
18    /// let polygon = polygon![
19    ///     // exterior oriented clockwise
20    ///     exterior: [
21    ///         (x: 1.0, y: 0.0),
22    ///         (x: 0.0, y: 1.0),
23    ///         (x: 1.0, y: 2.0),
24    ///         (x: 2.0, y: 1.0),
25    ///         (x: 1.0, y: 0.0),
26    ///     ],
27    ///     // interior oriented counter-clockwise
28    ///     interiors: [
29    ///         [
30    ///             (x: 1.0, y: 0.5),
31    ///             (x: 1.5, y: 1.0),
32    ///             (x: 1.0, y: 1.5),
33    ///             (x: 0.5, y: 1.0),
34    ///             (x: 1.0, y: 0.5),
35    ///         ],
36    ///     ],
37    /// ];
38    ///
39    /// let oriented = polygon.orient(Direction::Default);
40    ///
41    /// // a diamond shape
42    /// let expected = polygon![
43    ///     // exterior oriented counter-clockwise
44    ///     exterior: [
45    ///         (x: 1.0, y: 0.0),
46    ///         (x: 2.0, y: 1.0),
47    ///         (x: 1.0, y: 2.0),
48    ///         (x: 0.0, y: 1.0),
49    ///         (x: 1.0, y: 0.0),
50    ///     ],
51    ///     // interior oriented clockwise
52    ///     interiors: [
53    ///         [
54    ///             (x: 1.0, y: 0.5),
55    ///             (x: 0.5, y: 1.0),
56    ///             (x: 1.0, y: 1.5),
57    ///             (x: 1.5, y: 1.0),
58    ///             (x: 1.0, y: 0.5),
59    ///         ],
60    ///     ],
61    /// ];
62    ///
63    /// assert_eq!(expected, oriented);
64    /// ```
65    fn orient(&self, orientation: Direction) -> Self;
66}
67
68impl<T> Orient for Polygon<T>
69where
70    T: GeoNum,
71{
72    fn orient(&self, direction: Direction) -> Polygon<T> {
73        orient(self, direction)
74    }
75}
76
77impl<T> Orient for MultiPolygon<T>
78where
79    T: GeoNum,
80{
81    fn orient(&self, direction: Direction) -> MultiPolygon<T> {
82        MultiPolygon::new(self.iter().map(|poly| poly.orient(direction)).collect())
83    }
84}
85
86/// By default, a properly-oriented Polygon has its outer ring oriented counter-clockwise,
87/// and its inner ring(s) oriented clockwise. Selecting `Reversed` will result in a Polygon
88/// with a clockwise-oriented exterior ring, and counter-clockwise interior ring(s)
89#[derive(Copy, Clone, Debug)]
90pub enum Direction {
91    /// exterior ring is oriented counter-clockwise, interior rings are oriented clockwise
92    Default,
93    /// exterior ring is oriented clockwise, interior rings are oriented counter-clockwise
94    Reversed,
95}
96
97// orient a Polygon according to convention
98// by default, the exterior ring will be oriented ccw
99// and the interior ring(s) will be oriented clockwise
100fn orient<T>(poly: &Polygon<T>, direction: Direction) -> Polygon<T>
101where
102    T: GeoNum,
103{
104    let interiors = poly
105        .interiors()
106        .iter()
107        .map(|l| {
108            l.clone_to_winding_order(match direction {
109                Direction::Default => WindingOrder::Clockwise,
110                Direction::Reversed => WindingOrder::CounterClockwise,
111            })
112        })
113        .collect();
114
115    let ext_ring = poly.exterior().clone_to_winding_order(match direction {
116        Direction::Default => WindingOrder::CounterClockwise,
117        Direction::Reversed => WindingOrder::Clockwise,
118    });
119
120    Polygon::new(ext_ring, interiors)
121}
122
123#[cfg(test)]
124mod test {
125    use super::*;
126    use crate::{LineString, Polygon};
127    #[test]
128    fn test_polygon_orientation() {
129        // a diamond shape, oriented clockwise outside
130        let points_ext = vec![(1.0, 0.0), (0.0, 1.0), (1.0, 2.0), (2.0, 1.0), (1.0, 0.0)];
131        // counter-clockwise interior
132        let points_int = vec![(1.0, 0.5), (1.5, 1.0), (1.0, 1.5), (0.5, 1.0), (1.0, 0.5)];
133        let poly1 = Polygon::new(
134            LineString::from(points_ext),
135            vec![LineString::from(points_int)],
136        );
137        // a diamond shape, oriented counter-clockwise outside,
138        let oriented_ext = vec![(1.0, 0.0), (2.0, 1.0), (1.0, 2.0), (0.0, 1.0), (1.0, 0.0)];
139        let oriented_ext_ls = LineString::from(oriented_ext);
140        // clockwise interior
141        let oriented_int_raw = vec![(1.0, 0.5), (0.5, 1.0), (1.0, 1.5), (1.5, 1.0), (1.0, 0.5)];
142        let oriented_int_ls = LineString::from(oriented_int_raw);
143        // build corrected Polygon
144        let oriented = orient(&poly1, Direction::Default);
145        assert_eq!(oriented.exterior().0, oriented_ext_ls.0);
146        assert_eq!(oriented.interiors()[0].0, oriented_int_ls.0);
147    }
148}