geo/algorithm/
bounding_rect.rs

1use crate::utils::{partial_max, partial_min};
2use crate::{coord, geometry::*, CoordNum, GeometryCow};
3use geo_types::private_utils::{get_bounding_rect, line_string_bounding_rect};
4
5/// Calculation of the bounding rectangle of a geometry.
6pub trait BoundingRect<T: CoordNum> {
7    type Output: Into<Option<Rect<T>>>;
8
9    /// Return the bounding rectangle of a geometry
10    ///
11    /// # Examples
12    ///
13    /// ```
14    /// use geo::BoundingRect;
15    /// use geo::line_string;
16    ///
17    /// let line_string = line_string![
18    ///     (x: 40.02f64, y: 116.34),
19    ///     (x: 42.02f64, y: 116.34),
20    ///     (x: 42.02f64, y: 118.34),
21    /// ];
22    ///
23    /// let bounding_rect = line_string.bounding_rect().unwrap();
24    ///
25    /// assert_eq!(40.02f64, bounding_rect.min().x);
26    /// assert_eq!(42.02f64, bounding_rect.max().x);
27    /// assert_eq!(116.34, bounding_rect.min().y);
28    /// assert_eq!(118.34, bounding_rect.max().y);
29    /// ```
30    fn bounding_rect(&self) -> Self::Output;
31}
32
33impl<T> BoundingRect<T> for Coord<T>
34where
35    T: CoordNum,
36{
37    type Output = Rect<T>;
38
39    /// Return the bounding rectangle for a `Coord`. It will have zero width
40    /// and zero height.
41    fn bounding_rect(&self) -> Self::Output {
42        Rect::new(*self, *self)
43    }
44}
45
46impl<T> BoundingRect<T> for Point<T>
47where
48    T: CoordNum,
49{
50    type Output = Rect<T>;
51
52    /// Return the bounding rectangle for a `Point`. It will have zero width
53    /// and zero height.
54    fn bounding_rect(&self) -> Self::Output {
55        Rect::new(self.0, self.0)
56    }
57}
58
59impl<T> BoundingRect<T> for MultiPoint<T>
60where
61    T: CoordNum,
62{
63    type Output = Option<Rect<T>>;
64
65    ///
66    /// Return the BoundingRect for a MultiPoint
67    fn bounding_rect(&self) -> Self::Output {
68        get_bounding_rect(self.0.iter().map(|p| p.0))
69    }
70}
71
72impl<T> BoundingRect<T> for Line<T>
73where
74    T: CoordNum,
75{
76    type Output = Rect<T>;
77
78    fn bounding_rect(&self) -> Self::Output {
79        Rect::new(self.start, self.end)
80    }
81}
82
83impl<T> BoundingRect<T> for LineString<T>
84where
85    T: CoordNum,
86{
87    type Output = Option<Rect<T>>;
88
89    ///
90    /// Return the BoundingRect for a LineString
91    fn bounding_rect(&self) -> Self::Output {
92        line_string_bounding_rect(self)
93    }
94}
95
96impl<T> BoundingRect<T> for MultiLineString<T>
97where
98    T: CoordNum,
99{
100    type Output = Option<Rect<T>>;
101
102    ///
103    /// Return the BoundingRect for a MultiLineString
104    fn bounding_rect(&self) -> Self::Output {
105        get_bounding_rect(self.iter().flat_map(|line| line.0.iter().cloned()))
106    }
107}
108
109impl<T> BoundingRect<T> for Polygon<T>
110where
111    T: CoordNum,
112{
113    type Output = Option<Rect<T>>;
114
115    ///
116    /// Return the BoundingRect for a Polygon
117    fn bounding_rect(&self) -> Self::Output {
118        let line = self.exterior();
119        get_bounding_rect(line.0.iter().cloned())
120    }
121}
122
123impl<T> BoundingRect<T> for MultiPolygon<T>
124where
125    T: CoordNum,
126{
127    type Output = Option<Rect<T>>;
128
129    ///
130    /// Return the BoundingRect for a MultiPolygon
131    fn bounding_rect(&self) -> Self::Output {
132        get_bounding_rect(
133            self.iter()
134                .flat_map(|poly| poly.exterior().0.iter().cloned()),
135        )
136    }
137}
138
139impl<T> BoundingRect<T> for Triangle<T>
140where
141    T: CoordNum,
142{
143    type Output = Rect<T>;
144
145    fn bounding_rect(&self) -> Self::Output {
146        get_bounding_rect(self.to_array().iter().cloned()).unwrap()
147    }
148}
149
150impl<T> BoundingRect<T> for Rect<T>
151where
152    T: CoordNum,
153{
154    type Output = Rect<T>;
155
156    fn bounding_rect(&self) -> Self::Output {
157        *self
158    }
159}
160
161impl<T> BoundingRect<T> for Geometry<T>
162where
163    T: CoordNum,
164{
165    type Output = Option<Rect<T>>;
166
167    crate::geometry_delegate_impl! {
168       fn bounding_rect(&self) -> Self::Output;
169    }
170}
171
172impl<T> BoundingRect<T> for GeometryCow<'_, T>
173where
174    T: CoordNum,
175{
176    type Output = Option<Rect<T>>;
177
178    crate::geometry_cow_delegate_impl! {
179       fn bounding_rect(&self) -> Self::Output;
180    }
181}
182
183impl<T> BoundingRect<T> for GeometryCollection<T>
184where
185    T: CoordNum,
186{
187    type Output = Option<Rect<T>>;
188
189    fn bounding_rect(&self) -> Self::Output {
190        self.iter().fold(None, |acc, next| {
191            let next_bounding_rect = next.bounding_rect();
192
193            match (acc, next_bounding_rect) {
194                (None, None) => None,
195                (Some(r), None) | (None, Some(r)) => Some(r),
196                (Some(r1), Some(r2)) => Some(bounding_rect_merge(r1, r2)),
197            }
198        })
199    }
200}
201
202// Return a new rectangle that encompasses the provided rectangles
203fn bounding_rect_merge<T: CoordNum>(a: Rect<T>, b: Rect<T>) -> Rect<T> {
204    Rect::new(
205        coord! {
206            x: partial_min(a.min().x, b.min().x),
207            y: partial_min(a.min().y, b.min().y),
208        },
209        coord! {
210            x: partial_max(a.max().x, b.max().x),
211            y: partial_max(a.max().y, b.max().y),
212        },
213    )
214}
215
216#[cfg(test)]
217mod test {
218    use super::bounding_rect_merge;
219    use crate::line_string;
220    use crate::BoundingRect;
221    use crate::{
222        coord, point, polygon, Geometry, GeometryCollection, Line, LineString, MultiLineString,
223        MultiPoint, MultiPolygon, Polygon, Rect,
224    };
225
226    #[test]
227    fn empty_linestring_test() {
228        let linestring: LineString<f32> = line_string![];
229        let bounding_rect = linestring.bounding_rect();
230        assert!(bounding_rect.is_none());
231    }
232    #[test]
233    fn linestring_one_point_test() {
234        let linestring = line_string![(x: 40.02f64, y: 116.34)];
235        let bounding_rect = Rect::new(
236            coord! {
237                x: 40.02f64,
238                y: 116.34,
239            },
240            coord! {
241                x: 40.02,
242                y: 116.34,
243            },
244        );
245        assert_eq!(bounding_rect, linestring.bounding_rect().unwrap());
246    }
247    #[test]
248    fn linestring_test() {
249        let linestring = line_string![
250            (x: 1., y: 1.),
251            (x: 2., y: -2.),
252            (x: -3., y: -3.),
253            (x: -4., y: 4.)
254        ];
255        let bounding_rect = Rect::new(coord! { x: -4., y: -3. }, coord! { x: 2., y: 4. });
256        assert_eq!(bounding_rect, linestring.bounding_rect().unwrap());
257    }
258    #[test]
259    fn multilinestring_test() {
260        let multiline = MultiLineString::new(vec![
261            line_string![(x: 1., y: 1.), (x: -40., y: 1.)],
262            line_string![(x: 1., y: 1.), (x: 50., y: 1.)],
263            line_string![(x: 1., y: 1.), (x: 1., y: -60.)],
264            line_string![(x: 1., y: 1.), (x: 1., y: 70.)],
265        ]);
266        let bounding_rect = Rect::new(coord! { x: -40., y: -60. }, coord! { x: 50., y: 70. });
267        assert_eq!(bounding_rect, multiline.bounding_rect().unwrap());
268    }
269    #[test]
270    fn multipoint_test() {
271        let multipoint = MultiPoint::from(vec![(1., 1.), (2., -2.), (-3., -3.), (-4., 4.)]);
272        let bounding_rect = Rect::new(coord! { x: -4., y: -3. }, coord! { x: 2., y: 4. });
273        assert_eq!(bounding_rect, multipoint.bounding_rect().unwrap());
274    }
275    #[test]
276    fn polygon_test() {
277        let linestring = line_string![
278            (x: 0., y: 0.),
279            (x: 5., y: 0.),
280            (x: 5., y: 6.),
281            (x: 0., y: 6.),
282            (x: 0., y: 0.),
283        ];
284        let line_bounding_rect = linestring.bounding_rect().unwrap();
285        let poly = Polygon::new(linestring, Vec::new());
286        assert_eq!(line_bounding_rect, poly.bounding_rect().unwrap());
287    }
288    #[test]
289    fn multipolygon_test() {
290        let mpoly = MultiPolygon::new(vec![
291            polygon![(x: 0., y: 0.), (x: 50., y: 0.), (x: 0., y: -70.), (x: 0., y: 0.)],
292            polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 0., y: 80.), (x: 0., y: 0.)],
293            polygon![(x: 0., y: 0.), (x: -60., y: 0.), (x: 0., y: 6.), (x: 0., y: 0.)],
294        ]);
295        let bounding_rect = Rect::new(coord! { x: -60., y: -70. }, coord! { x: 50., y: 80. });
296        assert_eq!(bounding_rect, mpoly.bounding_rect().unwrap());
297    }
298    #[test]
299    fn line_test() {
300        let line1 = Line::new(coord! { x: 0., y: 1. }, coord! { x: 2., y: 3. });
301        let line2 = Line::new(coord! { x: 2., y: 3. }, coord! { x: 0., y: 1. });
302        assert_eq!(
303            line1.bounding_rect(),
304            Rect::new(coord! { x: 0., y: 1. }, coord! { x: 2., y: 3. },)
305        );
306        assert_eq!(
307            line2.bounding_rect(),
308            Rect::new(coord! { x: 0., y: 1. }, coord! { x: 2., y: 3. },)
309        );
310    }
311
312    #[test]
313    fn bounding_rect_merge_test() {
314        assert_eq!(
315            bounding_rect_merge(
316                Rect::new(coord! { x: 0., y: 0. }, coord! { x: 1., y: 1. }),
317                Rect::new(coord! { x: 1., y: 1. }, coord! { x: 2., y: 2. }),
318            ),
319            Rect::new(coord! { x: 0., y: 0. }, coord! { x: 2., y: 2. }),
320        );
321    }
322
323    #[test]
324    fn point_bounding_rect_test() {
325        assert_eq!(
326            Rect::new(coord! { x: 1., y: 2. }, coord! { x: 1., y: 2. }),
327            point! { x: 1., y: 2. }.bounding_rect(),
328        );
329    }
330
331    #[test]
332    fn geometry_collection_bounding_rect_test() {
333        assert_eq!(
334            Some(Rect::new(coord! { x: 0., y: 0. }, coord! { x: 1., y: 2. })),
335            GeometryCollection::new_from(vec![
336                Geometry::Point(point! { x: 0., y: 0. }),
337                Geometry::Point(point! { x: 1., y: 2. }),
338            ])
339            .bounding_rect(),
340        );
341    }
342}