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)
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))
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)
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(self.iter().flat_map(|poly| &poly.exterior().0))
133    }
134}
135
136impl<T> BoundingRect<T> for Triangle<T>
137where
138    T: CoordNum,
139{
140    type Output = Rect<T>;
141
142    fn bounding_rect(&self) -> Self::Output {
143        get_bounding_rect(self.to_array()).unwrap()
144    }
145}
146
147impl<T> BoundingRect<T> for Rect<T>
148where
149    T: CoordNum,
150{
151    type Output = Rect<T>;
152
153    fn bounding_rect(&self) -> Self::Output {
154        *self
155    }
156}
157
158impl<T> BoundingRect<T> for Geometry<T>
159where
160    T: CoordNum,
161{
162    type Output = Option<Rect<T>>;
163
164    crate::geometry_delegate_impl! {
165       fn bounding_rect(&self) -> Self::Output;
166    }
167}
168
169impl<T> BoundingRect<T> for GeometryCow<'_, T>
170where
171    T: CoordNum,
172{
173    type Output = Option<Rect<T>>;
174
175    crate::geometry_cow_delegate_impl! {
176       fn bounding_rect(&self) -> Self::Output;
177    }
178}
179
180impl<T> BoundingRect<T> for GeometryCollection<T>
181where
182    T: CoordNum,
183{
184    type Output = Option<Rect<T>>;
185
186    fn bounding_rect(&self) -> Self::Output {
187        self.iter().fold(None, |acc, next| {
188            let next_bounding_rect = next.bounding_rect();
189
190            match (acc, next_bounding_rect) {
191                (None, None) => None,
192                (Some(r), None) | (None, Some(r)) => Some(r),
193                (Some(r1), Some(r2)) => Some(bounding_rect_merge(r1, r2)),
194            }
195        })
196    }
197}
198
199// Return a new rectangle that encompasses the provided rectangles
200fn bounding_rect_merge<T: CoordNum>(a: Rect<T>, b: Rect<T>) -> Rect<T> {
201    Rect::new(
202        coord! {
203            x: partial_min(a.min().x, b.min().x),
204            y: partial_min(a.min().y, b.min().y),
205        },
206        coord! {
207            x: partial_max(a.max().x, b.max().x),
208            y: partial_max(a.max().y, b.max().y),
209        },
210    )
211}
212
213#[cfg(test)]
214mod test {
215    use super::bounding_rect_merge;
216    use crate::line_string;
217    use crate::BoundingRect;
218    use crate::{
219        coord, point, polygon, Geometry, GeometryCollection, Line, LineString, MultiLineString,
220        MultiPoint, MultiPolygon, Polygon, Rect,
221    };
222
223    #[test]
224    fn empty_linestring_test() {
225        let linestring: LineString<f32> = line_string![];
226        let bounding_rect = linestring.bounding_rect();
227        assert!(bounding_rect.is_none());
228    }
229    #[test]
230    fn linestring_one_point_test() {
231        let linestring = line_string![(x: 40.02f64, y: 116.34)];
232        let bounding_rect = Rect::new(
233            coord! {
234                x: 40.02f64,
235                y: 116.34,
236            },
237            coord! {
238                x: 40.02,
239                y: 116.34,
240            },
241        );
242        assert_eq!(bounding_rect, linestring.bounding_rect().unwrap());
243    }
244    #[test]
245    fn linestring_test() {
246        let linestring = line_string![
247            (x: 1., y: 1.),
248            (x: 2., y: -2.),
249            (x: -3., y: -3.),
250            (x: -4., y: 4.)
251        ];
252        let bounding_rect = Rect::new(coord! { x: -4., y: -3. }, coord! { x: 2., y: 4. });
253        assert_eq!(bounding_rect, linestring.bounding_rect().unwrap());
254    }
255    #[test]
256    fn multilinestring_test() {
257        let multiline = MultiLineString::new(vec![
258            line_string![(x: 1., y: 1.), (x: -40., y: 1.)],
259            line_string![(x: 1., y: 1.), (x: 50., y: 1.)],
260            line_string![(x: 1., y: 1.), (x: 1., y: -60.)],
261            line_string![(x: 1., y: 1.), (x: 1., y: 70.)],
262        ]);
263        let bounding_rect = Rect::new(coord! { x: -40., y: -60. }, coord! { x: 50., y: 70. });
264        assert_eq!(bounding_rect, multiline.bounding_rect().unwrap());
265    }
266    #[test]
267    fn multipoint_test() {
268        let multipoint = MultiPoint::from(vec![(1., 1.), (2., -2.), (-3., -3.), (-4., 4.)]);
269        let bounding_rect = Rect::new(coord! { x: -4., y: -3. }, coord! { x: 2., y: 4. });
270        assert_eq!(bounding_rect, multipoint.bounding_rect().unwrap());
271    }
272    #[test]
273    fn polygon_test() {
274        let linestring = line_string![
275            (x: 0., y: 0.),
276            (x: 5., y: 0.),
277            (x: 5., y: 6.),
278            (x: 0., y: 6.),
279            (x: 0., y: 0.),
280        ];
281        let line_bounding_rect = linestring.bounding_rect().unwrap();
282        let poly = Polygon::new(linestring, Vec::new());
283        assert_eq!(line_bounding_rect, poly.bounding_rect().unwrap());
284    }
285    #[test]
286    fn multipolygon_test() {
287        let mpoly = MultiPolygon::new(vec![
288            polygon![(x: 0., y: 0.), (x: 50., y: 0.), (x: 0., y: -70.), (x: 0., y: 0.)],
289            polygon![(x: 0., y: 0.), (x: 5., y: 0.), (x: 0., y: 80.), (x: 0., y: 0.)],
290            polygon![(x: 0., y: 0.), (x: -60., y: 0.), (x: 0., y: 6.), (x: 0., y: 0.)],
291        ]);
292        let bounding_rect = Rect::new(coord! { x: -60., y: -70. }, coord! { x: 50., y: 80. });
293        assert_eq!(bounding_rect, mpoly.bounding_rect().unwrap());
294    }
295    #[test]
296    fn line_test() {
297        let line1 = Line::new(coord! { x: 0., y: 1. }, coord! { x: 2., y: 3. });
298        let line2 = Line::new(coord! { x: 2., y: 3. }, coord! { x: 0., y: 1. });
299        assert_eq!(
300            line1.bounding_rect(),
301            Rect::new(coord! { x: 0., y: 1. }, coord! { x: 2., y: 3. },)
302        );
303        assert_eq!(
304            line2.bounding_rect(),
305            Rect::new(coord! { x: 0., y: 1. }, coord! { x: 2., y: 3. },)
306        );
307    }
308
309    #[test]
310    fn bounding_rect_merge_test() {
311        assert_eq!(
312            bounding_rect_merge(
313                Rect::new(coord! { x: 0., y: 0. }, coord! { x: 1., y: 1. }),
314                Rect::new(coord! { x: 1., y: 1. }, coord! { x: 2., y: 2. }),
315            ),
316            Rect::new(coord! { x: 0., y: 0. }, coord! { x: 2., y: 2. }),
317        );
318    }
319
320    #[test]
321    fn point_bounding_rect_test() {
322        assert_eq!(
323            Rect::new(coord! { x: 1., y: 2. }, coord! { x: 1., y: 2. }),
324            point! { x: 1., y: 2. }.bounding_rect(),
325        );
326    }
327
328    #[test]
329    fn geometry_collection_bounding_rect_test() {
330        assert_eq!(
331            Some(Rect::new(coord! { x: 0., y: 0. }, coord! { x: 1., y: 2. })),
332            GeometryCollection::new_from(vec![
333                Geometry::Point(point! { x: 0., y: 0. }),
334                Geometry::Point(point! { x: 1., y: 2. }),
335            ])
336            .bounding_rect(),
337        );
338    }
339}