geo/algorithm/
extremes.rs

1use crate::CoordsIter;
2use crate::{Coord, CoordNum};
3
4/// Find the extreme coordinates and indices of a geometry.
5///
6/// # Examples
7///
8/// ```
9/// use geo::extremes::Extremes;
10/// use geo::polygon;
11///
12/// // a diamond shape
13/// let polygon = polygon![
14///     (x: 1.0, y: 0.0),
15///     (x: 2.0, y: 1.0),
16///     (x: 1.0, y: 2.0),
17///     (x: 0.0, y: 1.0),
18///     (x: 1.0, y: 0.0),
19/// ];
20///
21/// let extremes = polygon.extremes().unwrap();
22///
23/// assert_eq!(extremes.y_max.index, 2);
24/// assert_eq!(extremes.y_max.coord.x, 1.);
25/// assert_eq!(extremes.y_max.coord.y, 2.);
26/// ```
27pub trait Extremes<'a, T: CoordNum> {
28    fn extremes(&'a self) -> Option<Outcome<T>>;
29}
30
31#[derive(Debug, PartialEq, Eq)]
32pub struct Extreme<T: CoordNum> {
33    pub index: usize,
34    pub coord: Coord<T>,
35}
36
37#[derive(Debug, PartialEq, Eq)]
38pub struct Outcome<T: CoordNum> {
39    pub x_min: Extreme<T>,
40    pub y_min: Extreme<T>,
41    pub x_max: Extreme<T>,
42    pub y_max: Extreme<T>,
43}
44
45impl<'a, T, G> Extremes<'a, T> for G
46where
47    G: CoordsIter<'a, Scalar = T>,
48    T: CoordNum,
49{
50    fn extremes(&'a self) -> Option<Outcome<T>> {
51        let mut iter = self.exterior_coords_iter().enumerate();
52
53        let mut outcome = iter.next().map(|(index, coord)| Outcome {
54            x_min: Extreme { index, coord },
55            y_min: Extreme { index, coord },
56            x_max: Extreme { index, coord },
57            y_max: Extreme { index, coord },
58        })?;
59
60        for (index, coord) in iter {
61            if coord.x < outcome.x_min.coord.x {
62                outcome.x_min = Extreme { coord, index };
63            }
64
65            if coord.y < outcome.y_min.coord.y {
66                outcome.y_min = Extreme { coord, index };
67            }
68
69            if coord.x > outcome.x_max.coord.x {
70                outcome.x_max = Extreme { coord, index };
71            }
72
73            if coord.y > outcome.y_max.coord.y {
74                outcome.y_max = Extreme { coord, index };
75            }
76        }
77
78        Some(outcome)
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use super::*;
85    use crate::{coord, polygon, MultiPoint};
86
87    #[test]
88    fn polygon() {
89        // a diamond shape
90        let polygon = polygon![
91            (x: 1.0, y: 0.0),
92            (x: 2.0, y: 1.0),
93            (x: 1.0, y: 2.0),
94            (x: 0.0, y: 1.0),
95            (x: 1.0, y: 0.0),
96        ];
97
98        let actual = polygon.extremes();
99
100        assert_eq!(
101            Some(Outcome {
102                x_min: Extreme {
103                    index: 3,
104                    coord: coord! { x: 0.0, y: 1.0 }
105                },
106                y_min: Extreme {
107                    index: 0,
108                    coord: coord! { x: 1.0, y: 0.0 }
109                },
110                x_max: Extreme {
111                    index: 1,
112                    coord: coord! { x: 2.0, y: 1.0 }
113                },
114                y_max: Extreme {
115                    index: 2,
116                    coord: coord! { x: 1.0, y: 2.0 }
117                }
118            }),
119            actual
120        );
121    }
122
123    #[test]
124    fn empty() {
125        let multi_point: MultiPoint<f32> = MultiPoint::new(vec![]);
126
127        let actual = multi_point.extremes();
128
129        assert!(actual.is_none());
130    }
131}