geo/algorithm/
dimensions.rs1use crate::Orientation::Collinear;
2use crate::geometry::*;
3use crate::{CoordNum, GeoNum, GeometryCow};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
25pub enum Dimensions {
26 Empty,
29 ZeroDimensional,
31 OneDimensional,
33 TwoDimensional,
35}
36
37pub trait HasDimensions {
39 fn is_empty(&self) -> bool;
60
61 fn dimensions(&self) -> Dimensions;
97
98 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 Dimensions::ZeroDimensional
172 } else {
173 Dimensions::OneDimensional
174 }
175 }
176
177 fn boundary_dimensions(&self) -> Dimensions {
178 if self.start == self.end {
179 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 Dimensions::ZeroDimensional
203 }
204 }
205
206 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 return Dimensions::Empty;
241 };
242
243 let Some(second) = coords.find(|next| *next != first) else {
244 return Dimensions::ZeroDimensional;
246 };
247
248 let Some(_third) = coords.find(|next| *next != first && *next != second) else {
249 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 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 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 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 Dimensions::ZeroDimensional
391 } else if self.min().x == self.max().x || self.min().y == self.max().y {
392 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 Dimensions::ZeroDimensional
422 } else {
423 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}