geo/algorithm/
dimensions.rs

1use crate::geometry::*;
2use crate::Orientation::Collinear;
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::new(vec![]);
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>::new_from(vec![]);
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        match coords.next() {
238            None => Dimensions::Empty,
239            Some(coord_0) => {
240                if coords.all(|coord_n| coord_0 == coord_n) {
241                    // all coords are a single point
242                    Dimensions::ZeroDimensional
243                } else {
244                    Dimensions::TwoDimensional
245                }
246            }
247        }
248    }
249
250    fn boundary_dimensions(&self) -> Dimensions {
251        Dimensions::OneDimensional
252    }
253}
254
255impl<C: CoordNum> HasDimensions for MultiPoint<C> {
256    fn is_empty(&self) -> bool {
257        self.0.is_empty()
258    }
259
260    fn dimensions(&self) -> Dimensions {
261        if self.0.is_empty() {
262            return Dimensions::Empty;
263        }
264
265        Dimensions::ZeroDimensional
266    }
267
268    fn boundary_dimensions(&self) -> Dimensions {
269        Dimensions::Empty
270    }
271}
272
273impl<C: CoordNum> HasDimensions for MultiLineString<C> {
274    fn is_empty(&self) -> bool {
275        self.iter().all(LineString::is_empty)
276    }
277
278    fn dimensions(&self) -> Dimensions {
279        let mut max = Dimensions::Empty;
280        for line in &self.0 {
281            match line.dimensions() {
282                Dimensions::Empty => {}
283                Dimensions::ZeroDimensional => max = Dimensions::ZeroDimensional,
284                Dimensions::OneDimensional => {
285                    // return early since we know multi line string dimensionality cannot exceed
286                    // 1-d
287                    return Dimensions::OneDimensional;
288                }
289                Dimensions::TwoDimensional => unreachable!("MultiLineString cannot be 2d"),
290            }
291        }
292        max
293    }
294
295    fn boundary_dimensions(&self) -> Dimensions {
296        if self.is_closed() {
297            return Dimensions::Empty;
298        }
299
300        match self.dimensions() {
301            Dimensions::Empty | Dimensions::ZeroDimensional => Dimensions::Empty,
302            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
303            Dimensions::TwoDimensional => unreachable!("line_string cannot be 2 dimensional"),
304        }
305    }
306}
307
308impl<C: CoordNum> HasDimensions for MultiPolygon<C> {
309    fn is_empty(&self) -> bool {
310        self.iter().all(Polygon::is_empty)
311    }
312
313    fn dimensions(&self) -> Dimensions {
314        if self.0.is_empty() {
315            return Dimensions::Empty;
316        }
317
318        Dimensions::TwoDimensional
319    }
320
321    fn boundary_dimensions(&self) -> Dimensions {
322        if self.0.is_empty() {
323            return Dimensions::Empty;
324        }
325
326        Dimensions::OneDimensional
327    }
328}
329
330impl<C: GeoNum> HasDimensions for GeometryCollection<C> {
331    fn is_empty(&self) -> bool {
332        if self.0.is_empty() {
333            true
334        } else {
335            self.iter().all(Geometry::is_empty)
336        }
337    }
338
339    fn dimensions(&self) -> Dimensions {
340        let mut max = Dimensions::Empty;
341        for geom in self {
342            let dimensions = geom.dimensions();
343            if dimensions == Dimensions::TwoDimensional {
344                // short-circuit since we know none can be larger
345                return Dimensions::TwoDimensional;
346            }
347            max = max.max(dimensions)
348        }
349        max
350    }
351
352    fn boundary_dimensions(&self) -> Dimensions {
353        let mut max = Dimensions::Empty;
354        for geom in self {
355            let d = geom.boundary_dimensions();
356
357            if d == Dimensions::OneDimensional {
358                return Dimensions::OneDimensional;
359            }
360
361            max = max.max(d);
362        }
363        max
364    }
365}
366
367impl<C: CoordNum> HasDimensions for Rect<C> {
368    fn is_empty(&self) -> bool {
369        false
370    }
371
372    fn dimensions(&self) -> Dimensions {
373        if self.min() == self.max() {
374            // degenerate rectangle is a point
375            Dimensions::ZeroDimensional
376        } else if self.min().x == self.max().x || self.min().y == self.max().y {
377            // degenerate rectangle is a line
378            Dimensions::OneDimensional
379        } else {
380            Dimensions::TwoDimensional
381        }
382    }
383
384    fn boundary_dimensions(&self) -> Dimensions {
385        match self.dimensions() {
386            Dimensions::Empty => {
387                unreachable!("even a degenerate rect should be at least 0-Dimensional")
388            }
389            Dimensions::ZeroDimensional => Dimensions::Empty,
390            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
391            Dimensions::TwoDimensional => Dimensions::OneDimensional,
392        }
393    }
394}
395
396impl<C: crate::GeoNum> HasDimensions for Triangle<C> {
397    fn is_empty(&self) -> bool {
398        false
399    }
400
401    fn dimensions(&self) -> Dimensions {
402        use crate::Kernel;
403        if Collinear == C::Ker::orient2d(self.0, self.1, self.2) {
404            if self.0 == self.1 && self.1 == self.2 {
405                // degenerate triangle is a point
406                Dimensions::ZeroDimensional
407            } else {
408                // degenerate triangle is a line
409                Dimensions::OneDimensional
410            }
411        } else {
412            Dimensions::TwoDimensional
413        }
414    }
415
416    fn boundary_dimensions(&self) -> Dimensions {
417        match self.dimensions() {
418            Dimensions::Empty => {
419                unreachable!("even a degenerate triangle should be at least 0-dimensional")
420            }
421            Dimensions::ZeroDimensional => Dimensions::Empty,
422            Dimensions::OneDimensional => Dimensions::ZeroDimensional,
423            Dimensions::TwoDimensional => Dimensions::OneDimensional,
424        }
425    }
426}