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}