Skip to main content

geo/algorithm/
bounding_rect.rs

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