geo/algorithm/
dimensions.rs

1use crate::Orientation::Collinear;
2use crate::geometry::*;
3use crate::{CoordNum, GeoNum, GeometryCow};
4
5/// Geometries can have 0, 1, or two dimensions. Or, in the case of an [`empty`](#is_empty)
6/// geometry, a special `Empty` dimensionality.
7///
8/// # Examples
9///
10/// ```
11/// use geo_types::{Point, Rect, line_string};
12/// use geo::dimensions::{HasDimensions, Dimensions};
13///
14/// let point = Point::new(0.0, 5.0);
15/// let line_string = line_string![(x: 0.0, y: 0.0), (x: 5.0, y: 5.0), (x: 0.0, y: 5.0)];
16/// let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
17/// assert_eq!(Dimensions::ZeroDimensional, point.dimensions());
18/// assert_eq!(Dimensions::OneDimensional, line_string.dimensions());
19/// assert_eq!(Dimensions::TwoDimensional, rect.dimensions());
20///
21/// assert!(point.dimensions() < line_string.dimensions());
22/// assert!(rect.dimensions() > line_string.dimensions());
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
25pub enum Dimensions {
26    /// Some geometries, like a `MultiPoint` or `GeometryCollection` may have no elements - thus no
27    /// dimensions. Note that this is distinct from being `ZeroDimensional`, like a `Point`.
28    Empty,
29    /// Dimension of a point
30    ZeroDimensional,
31    /// Dimension of a line or curve
32    OneDimensional,
33    /// Dimension of a surface
34    TwoDimensional,
35}
36
37/// Operate on the dimensionality of geometries.
38pub trait HasDimensions {
39    /// Some geometries, like a `MultiPoint`, can have zero coordinates - we call these `empty`.
40    ///
41    /// Types like `Point` and `Rect`, which have at least one coordinate by construction, can
42    /// never be considered empty.
43    /// ```
44    /// use geo_types::{Point, coord, LineString};
45    /// use geo::HasDimensions;
46    ///
47    /// let line_string = LineString::new(vec![
48    ///     coord! { x: 0., y: 0. },
49    ///     coord! { x: 10., y: 0. },
50    /// ]);
51    /// assert!(!line_string.is_empty());
52    ///
53    /// let empty_line_string: LineString = LineString::empty();
54    /// assert!(empty_line_string.is_empty());
55    ///
56    /// let point = Point::new(0.0, 0.0);
57    /// assert!(!point.is_empty());
58    /// ```
59    fn is_empty(&self) -> bool;
60
61    /// The dimensions of some geometries are fixed, e.g. a Point always has 0 dimensions. However
62    /// for others, the dimensionality depends on the specific geometry instance - for example
63    /// typical `Rect`s are 2-dimensional, but it's possible to create degenerate `Rect`s which
64    /// have either 1 or 0 dimensions.
65    ///
66    /// ## Examples
67    ///
68    /// ```
69    /// use geo_types::{GeometryCollection, Rect, Point};
70    /// use geo::dimensions::{Dimensions, HasDimensions};
71    ///
72    /// // normal rectangle
73    /// let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
74    /// assert_eq!(Dimensions::TwoDimensional, rect.dimensions());
75    ///
76    /// // "rectangle" with zero height degenerates to a line
77    /// let degenerate_line_rect = Rect::new((0.0, 10.0), (10.0, 10.0));
78    /// assert_eq!(Dimensions::OneDimensional, degenerate_line_rect.dimensions());
79    ///
80    /// // "rectangle" with zero height and zero width degenerates to a point
81    /// let degenerate_point_rect = Rect::new((10.0, 10.0), (10.0, 10.0));
82    /// assert_eq!(Dimensions::ZeroDimensional, degenerate_point_rect.dimensions());
83    ///
84    /// // collections inherit the greatest dimensionality of their elements
85    /// let geometry_collection = GeometryCollection::new_from(vec![degenerate_line_rect.into(), degenerate_point_rect.into()]);
86    /// assert_eq!(Dimensions::OneDimensional, geometry_collection.dimensions());
87    ///
88    /// let point = Point::new(10.0, 10.0);
89    /// assert_eq!(Dimensions::ZeroDimensional, point.dimensions());
90    ///
91    /// // An `Empty` dimensionality is distinct from, and less than, being 0-dimensional
92    /// let empty_collection = GeometryCollection::<f32>::empty();
93    /// assert_eq!(Dimensions::Empty, empty_collection.dimensions());
94    /// assert!(empty_collection.dimensions() < point.dimensions());
95    /// ```
96    fn dimensions(&self) -> Dimensions;
97
98    /// The dimensions of the `Geometry`'s boundary, as used by OGC-SFA.
99    ///
100    /// ## Examples
101    ///
102    /// ```
103    /// use geo_types::{GeometryCollection, Rect, Point};
104    /// use geo::dimensions::{Dimensions, HasDimensions};
105    ///
106    /// // a point has no boundary
107    /// let point = Point::new(10.0, 10.0);
108    /// assert_eq!(Dimensions::Empty, point.boundary_dimensions());
109    ///
110    /// // a typical rectangle has a *line* (one dimensional) boundary
111    /// let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
112    /// assert_eq!(Dimensions::OneDimensional, rect.boundary_dimensions());
113    ///
114    /// // a "rectangle" with zero height degenerates to a line, whose boundary is two points
115    /// let degenerate_line_rect = Rect::new((0.0, 10.0), (10.0, 10.0));
116    /// assert_eq!(Dimensions::ZeroDimensional, degenerate_line_rect.boundary_dimensions());
117    ///
118    /// // a "rectangle" with zero height and zero width degenerates to a point,
119    /// // and points have no boundary
120    /// let degenerate_point_rect = Rect::new((10.0, 10.0), (10.0, 10.0));
121    /// assert_eq!(Dimensions::Empty, degenerate_point_rect.boundary_dimensions());
122    ///
123    /// // collections inherit the greatest dimensionality of their elements
124    /// let geometry_collection = GeometryCollection::new_from(vec![degenerate_line_rect.into(), degenerate_point_rect.into()]);
125    /// assert_eq!(Dimensions::ZeroDimensional, geometry_collection.boundary_dimensions());
126    ///
127    /// let geometry_collection = GeometryCollection::<f32>::new_from(vec![]);
128    /// assert_eq!(Dimensions::Empty, geometry_collection.boundary_dimensions());
129    /// ```
130    fn boundary_dimensions(&self) -> Dimensions;
131}
132
133impl<C: GeoNum> HasDimensions for Geometry<C> {
134    crate::geometry_delegate_impl! {
135        fn is_empty(&self) -> bool;
136        fn dimensions(&self) -> Dimensions;
137        fn boundary_dimensions(&self) -> Dimensions;
138    }
139}
140
141impl<C: GeoNum> HasDimensions for GeometryCow<'_, C> {
142    crate::geometry_cow_delegate_impl! {
143        fn is_empty(&self) -> bool;
144        fn dimensions(&self) -> Dimensions;
145        fn boundary_dimensions(&self) -> Dimensions;
146    }
147}
148
149impl<C: CoordNum> HasDimensions for Point<C> {
150    fn is_empty(&self) -> bool {
151        false
152    }
153
154    fn dimensions(&self) -> Dimensions {
155        Dimensions::ZeroDimensional
156    }
157
158    fn boundary_dimensions(&self) -> Dimensions {
159        Dimensions::Empty
160    }
161}
162
163impl<C: CoordNum> HasDimensions for Line<C> {
164    fn is_empty(&self) -> bool {
165        false
166    }
167
168    fn dimensions(&self) -> Dimensions {
169        if self.start == self.end {
170            // degenerate line is a point
171            Dimensions::ZeroDimensional
172        } else {
173            Dimensions::OneDimensional
174        }
175    }
176
177    fn boundary_dimensions(&self) -> Dimensions {
178        if self.start == self.end {
179            // degenerate line is a point, which has no boundary
180            Dimensions::Empty
181        } else {
182            Dimensions::ZeroDimensional
183        }
184    }
185}
186
187impl<C: CoordNum> HasDimensions for LineString<C> {
188    fn is_empty(&self) -> bool {
189        self.0.is_empty()
190    }
191
192    fn dimensions(&self) -> Dimensions {
193        if self.0.is_empty() {
194            return Dimensions::Empty;
195        }
196
197        let first = self.0[0];
198        if self.0.iter().any(|&coord| first != coord) {
199            Dimensions::OneDimensional
200        } else {
201            // all coords are the same - i.e. a point
202            Dimensions::ZeroDimensional
203        }
204    }
205
206    /// ```
207    /// use geo_types::line_string;
208    /// use geo::dimensions::{HasDimensions, Dimensions};
209    ///
210    /// let ls = line_string![(x: 0.,  y: 0.), (x: 0., y: 1.), (x: 1., y: 1.)];
211    /// assert_eq!(Dimensions::ZeroDimensional, ls.boundary_dimensions());
212    ///
213    /// let ls = line_string![(x: 0.,  y: 0.), (x: 0., y: 1.), (x: 1., y: 1.), (x: 0., y: 0.)];
214    /// assert_eq!(Dimensions::Empty, ls.boundary_dimensions());
215    ///```
216    fn boundary_dimensions(&self) -> Dimensions {
217        if self.is_closed() {
218            return Dimensions::Empty;
219        }
220
221        match self.dimensions() {
222            Dimensions::Empty | Dimensions::ZeroDimensional => Dimensions::Empty,
223            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
224            Dimensions::TwoDimensional => unreachable!("line_string cannot be 2 dimensional"),
225        }
226    }
227}
228
229impl<C: CoordNum> HasDimensions for Polygon<C> {
230    fn is_empty(&self) -> bool {
231        self.exterior().is_empty()
232    }
233
234    fn dimensions(&self) -> Dimensions {
235        use crate::CoordsIter;
236        let mut coords = self.exterior_coords_iter();
237
238        let Some(first) = coords.next() else {
239            // No coordinates - the polygon is empty
240            return Dimensions::Empty;
241        };
242
243        let Some(second) = coords.find(|next| *next != first) else {
244            // All coordinates in the polygon are the same point
245            return Dimensions::ZeroDimensional;
246        };
247
248        let Some(_third) = coords.find(|next| *next != first && *next != second) else {
249            // There are only two distinct coordinates in the Polygon - it's collapsed to a line
250            return Dimensions::OneDimensional;
251        };
252
253        Dimensions::TwoDimensional
254    }
255
256    fn boundary_dimensions(&self) -> Dimensions {
257        match self.dimensions() {
258            Dimensions::Empty | Dimensions::ZeroDimensional => Dimensions::Empty,
259            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
260            Dimensions::TwoDimensional => Dimensions::OneDimensional,
261        }
262    }
263}
264
265impl<C: CoordNum> HasDimensions for MultiPoint<C> {
266    fn is_empty(&self) -> bool {
267        self.0.is_empty()
268    }
269
270    fn dimensions(&self) -> Dimensions {
271        if self.0.is_empty() {
272            return Dimensions::Empty;
273        }
274
275        Dimensions::ZeroDimensional
276    }
277
278    fn boundary_dimensions(&self) -> Dimensions {
279        Dimensions::Empty
280    }
281}
282
283impl<C: CoordNum> HasDimensions for MultiLineString<C> {
284    fn is_empty(&self) -> bool {
285        self.iter().all(LineString::is_empty)
286    }
287
288    fn dimensions(&self) -> Dimensions {
289        let mut max = Dimensions::Empty;
290        for line in &self.0 {
291            match line.dimensions() {
292                Dimensions::Empty => {}
293                Dimensions::ZeroDimensional => max = Dimensions::ZeroDimensional,
294                Dimensions::OneDimensional => {
295                    // return early since we know multi line string dimensionality cannot exceed
296                    // 1-d
297                    return Dimensions::OneDimensional;
298                }
299                Dimensions::TwoDimensional => unreachable!("MultiLineString cannot be 2d"),
300            }
301        }
302        max
303    }
304
305    fn boundary_dimensions(&self) -> Dimensions {
306        if self.is_closed() {
307            return Dimensions::Empty;
308        }
309
310        match self.dimensions() {
311            Dimensions::Empty | Dimensions::ZeroDimensional => Dimensions::Empty,
312            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
313            Dimensions::TwoDimensional => unreachable!("line_string cannot be 2 dimensional"),
314        }
315    }
316}
317
318impl<C: CoordNum> HasDimensions for MultiPolygon<C> {
319    fn is_empty(&self) -> bool {
320        self.iter().all(Polygon::is_empty)
321    }
322
323    fn dimensions(&self) -> Dimensions {
324        let mut max = Dimensions::Empty;
325        for geom in self {
326            let dimensions = geom.dimensions();
327            if dimensions == Dimensions::TwoDimensional {
328                // short-circuit since we know none can be larger
329                return Dimensions::TwoDimensional;
330            }
331            max = max.max(dimensions)
332        }
333        max
334    }
335
336    fn boundary_dimensions(&self) -> Dimensions {
337        match self.dimensions() {
338            Dimensions::Empty | Dimensions::ZeroDimensional => Dimensions::Empty,
339            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
340            Dimensions::TwoDimensional => Dimensions::OneDimensional,
341        }
342    }
343}
344
345impl<C: GeoNum> HasDimensions for GeometryCollection<C> {
346    fn is_empty(&self) -> bool {
347        if self.0.is_empty() {
348            true
349        } else {
350            self.iter().all(Geometry::is_empty)
351        }
352    }
353
354    fn dimensions(&self) -> Dimensions {
355        let mut max = Dimensions::Empty;
356        for geom in self {
357            let dimensions = geom.dimensions();
358            if dimensions == Dimensions::TwoDimensional {
359                // short-circuit since we know none can be larger
360                return Dimensions::TwoDimensional;
361            }
362            max = max.max(dimensions)
363        }
364        max
365    }
366
367    fn boundary_dimensions(&self) -> Dimensions {
368        let mut max = Dimensions::Empty;
369        for geom in self {
370            let d = geom.boundary_dimensions();
371
372            if d == Dimensions::OneDimensional {
373                return Dimensions::OneDimensional;
374            }
375
376            max = max.max(d);
377        }
378        max
379    }
380}
381
382impl<C: CoordNum> HasDimensions for Rect<C> {
383    fn is_empty(&self) -> bool {
384        false
385    }
386
387    fn dimensions(&self) -> Dimensions {
388        if self.min() == self.max() {
389            // degenerate rectangle is a point
390            Dimensions::ZeroDimensional
391        } else if self.min().x == self.max().x || self.min().y == self.max().y {
392            // degenerate rectangle is a line
393            Dimensions::OneDimensional
394        } else {
395            Dimensions::TwoDimensional
396        }
397    }
398
399    fn boundary_dimensions(&self) -> Dimensions {
400        match self.dimensions() {
401            Dimensions::Empty => {
402                unreachable!("even a degenerate rect should be at least 0-Dimensional")
403            }
404            Dimensions::ZeroDimensional => Dimensions::Empty,
405            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
406            Dimensions::TwoDimensional => Dimensions::OneDimensional,
407        }
408    }
409}
410
411impl<C: GeoNum> HasDimensions for Triangle<C> {
412    fn is_empty(&self) -> bool {
413        false
414    }
415
416    fn dimensions(&self) -> Dimensions {
417        use crate::Kernel;
418        if Collinear == C::Ker::orient2d(self.0, self.1, self.2) {
419            if self.0 == self.1 && self.1 == self.2 {
420                // degenerate triangle is a point
421                Dimensions::ZeroDimensional
422            } else {
423                // degenerate triangle is a line
424                Dimensions::OneDimensional
425            }
426        } else {
427            Dimensions::TwoDimensional
428        }
429    }
430
431    fn boundary_dimensions(&self) -> Dimensions {
432        match self.dimensions() {
433            Dimensions::Empty => {
434                unreachable!("even a degenerate triangle should be at least 0-dimensional")
435            }
436            Dimensions::ZeroDimensional => Dimensions::Empty,
437            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
438            Dimensions::TwoDimensional => Dimensions::OneDimensional,
439        }
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    const ONE: Coord = crate::coord!(x: 1.0, y: 1.0);
448    use crate::wkt;
449
450    #[test]
451    fn point() {
452        assert_eq!(
453            Dimensions::ZeroDimensional,
454            wkt!(POINT(1.0 1.0)).dimensions()
455        );
456    }
457
458    #[test]
459    fn line_string() {
460        assert_eq!(
461            Dimensions::OneDimensional,
462            wkt!(LINESTRING(1.0 1.0,2.0 2.0,3.0 3.0)).dimensions()
463        );
464    }
465
466    #[test]
467    fn polygon() {
468        assert_eq!(
469            Dimensions::TwoDimensional,
470            wkt!(POLYGON((1.0 1.0,2.0 2.0,3.0 3.0,1.0 1.0))).dimensions()
471        );
472    }
473
474    #[test]
475    fn multi_point() {
476        assert_eq!(
477            Dimensions::ZeroDimensional,
478            wkt!(MULTIPOINT(1.0 1.0)).dimensions()
479        );
480    }
481
482    #[test]
483    fn multi_line_string() {
484        assert_eq!(
485            Dimensions::OneDimensional,
486            wkt!(MULTILINESTRING((1.0 1.0,2.0 2.0,3.0 3.0))).dimensions()
487        );
488    }
489
490    #[test]
491    fn multi_polygon() {
492        assert_eq!(
493            Dimensions::TwoDimensional,
494            wkt!(MULTIPOLYGON(((1.0 1.0,2.0 2.0,3.0 3.0,1.0 1.0)))).dimensions()
495        );
496    }
497
498    mod empty {
499        use super::*;
500        #[test]
501        fn empty_line_string() {
502            assert_eq!(
503                Dimensions::Empty,
504                (wkt!(LINESTRING EMPTY) as LineString<f64>).dimensions()
505            );
506        }
507
508        #[test]
509        fn empty_polygon() {
510            assert_eq!(
511                Dimensions::Empty,
512                (wkt!(POLYGON EMPTY) as Polygon<f64>).dimensions()
513            );
514        }
515
516        #[test]
517        fn empty_multi_point() {
518            assert_eq!(
519                Dimensions::Empty,
520                (wkt!(MULTIPOINT EMPTY) as MultiPoint<f64>).dimensions()
521            );
522        }
523
524        #[test]
525        fn empty_multi_line_string() {
526            assert_eq!(
527                Dimensions::Empty,
528                (wkt!(MULTILINESTRING EMPTY) as MultiLineString<f64>).dimensions()
529            );
530        }
531
532        #[test]
533        fn multi_line_string_with_empty_line_string() {
534            let empty_line_string = wkt!(LINESTRING EMPTY) as LineString<f64>;
535            let multi_line_string = MultiLineString::new(vec![empty_line_string]);
536            assert_eq!(Dimensions::Empty, multi_line_string.dimensions());
537        }
538
539        #[test]
540        fn empty_multi_polygon() {
541            assert_eq!(
542                Dimensions::Empty,
543                (wkt!(MULTIPOLYGON EMPTY) as MultiPolygon<f64>).dimensions()
544            );
545        }
546
547        #[test]
548        fn multi_polygon_with_empty_polygon() {
549            let empty_polygon = (wkt!(POLYGON EMPTY) as Polygon<f64>);
550            let multi_polygon = MultiPolygon::new(vec![empty_polygon]);
551            assert_eq!(Dimensions::Empty, multi_polygon.dimensions());
552        }
553    }
554
555    mod dimensional_collapse {
556        use super::*;
557
558        #[test]
559        fn line_collapsed_to_point() {
560            assert_eq!(
561                Dimensions::ZeroDimensional,
562                Line::new(ONE, ONE).dimensions()
563            );
564        }
565
566        #[test]
567        fn line_string_collapsed_to_point() {
568            assert_eq!(
569                Dimensions::ZeroDimensional,
570                wkt!(LINESTRING(1.0 1.0)).dimensions()
571            );
572            assert_eq!(
573                Dimensions::ZeroDimensional,
574                wkt!(LINESTRING(1.0 1.0,1.0 1.0)).dimensions()
575            );
576        }
577
578        #[test]
579        fn polygon_collapsed_to_point() {
580            assert_eq!(
581                Dimensions::ZeroDimensional,
582                wkt!(POLYGON((1.0 1.0))).dimensions()
583            );
584            assert_eq!(
585                Dimensions::ZeroDimensional,
586                wkt!(POLYGON((1.0 1.0,1.0 1.0))).dimensions()
587            );
588        }
589
590        #[test]
591        fn polygon_collapsed_to_line() {
592            assert_eq!(
593                Dimensions::OneDimensional,
594                wkt!(POLYGON((1.0 1.0,2.0 2.0))).dimensions()
595            );
596        }
597
598        #[test]
599        fn multi_line_string_with_line_string_collapsed_to_point() {
600            assert_eq!(
601                Dimensions::ZeroDimensional,
602                wkt!(MULTILINESTRING((1.0 1.0))).dimensions()
603            );
604            assert_eq!(
605                Dimensions::ZeroDimensional,
606                wkt!(MULTILINESTRING((1.0 1.0,1.0 1.0))).dimensions()
607            );
608            assert_eq!(
609                Dimensions::ZeroDimensional,
610                wkt!(MULTILINESTRING((1.0 1.0),(1.0 1.0))).dimensions()
611            );
612        }
613
614        #[test]
615        fn multi_polygon_with_polygon_collapsed_to_point() {
616            assert_eq!(
617                Dimensions::ZeroDimensional,
618                wkt!(MULTIPOLYGON(((1.0 1.0)))).dimensions()
619            );
620            assert_eq!(
621                Dimensions::ZeroDimensional,
622                wkt!(MULTIPOLYGON(((1.0 1.0,1.0 1.0)))).dimensions()
623            );
624        }
625
626        #[test]
627        fn multi_polygon_with_polygon_collapsed_to_line() {
628            assert_eq!(
629                Dimensions::OneDimensional,
630                wkt!(MULTIPOLYGON(((1.0 1.0,2.0 2.0)))).dimensions()
631            );
632        }
633    }
634}