1use alloc::vec::Vec;
24
25use geometry_coords::CoordinateScalar;
26use geometry_cs::{CartesianFamily, CoordinateSystem};
27use geometry_model::Segment;
28use geometry_strategy::{AreaStrategy, ShoelaceArea, WithinRing, WithinStrategy};
29use geometry_tag::{MultiPolygonTag, PolygonTag, RingTag, SameAs};
30use geometry_trait::{
31 Geometry, MultiPolygon as MultiPolygonTrait, Point, PointMut, Polygon as PolygonTrait,
32 Ring as RingTrait,
33};
34
35use crate::predicate::range_guard::coordinate_in_range;
36use crate::predicate::segment_intersection::{SegmentIntersection, segment_intersection};
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum ValidityFailure {
46 FewPoints,
49 DuplicatePoints,
52 NotClosed,
55 SelfIntersection,
58 InvalidCoordinate,
61 InteriorRingOutside,
64 CoordinateOutOfRange,
73 Spikes,
76 WrongOrientation,
82 NestedInteriorRings,
85 DisconnectedInterior,
88 IntersectingInteriors,
91 WrongTopologicalDimension,
94 WrongCornerOrder,
97 CollinearPointsOnFace,
100 NonCoplanarPointsOnFace,
103 FewPointsOnFace,
106 InconsistentOrientation,
109 InvalidIntersection,
112 DisconnectedSurface,
115}
116
117impl ValidityFailure {
118 #[must_use]
126 pub const fn message(self) -> &'static str {
127 match self {
128 Self::FewPoints => "Geometry has too few points",
129 Self::WrongTopologicalDimension => "Geometry has wrong topological dimension",
130 Self::Spikes => "Geometry has spikes",
131 Self::DuplicatePoints => "Geometry has duplicate (consecutive) points",
132 Self::NotClosed => "Geometry is defined as closed but is open",
133 Self::SelfIntersection => "Geometry has invalid self-intersections",
134 Self::WrongOrientation => "Geometry has wrong orientation",
135 Self::InteriorRingOutside => {
136 "Geometry has interior rings defined outside the outer boundary"
137 }
138 Self::NestedInteriorRings => "Geometry has nested interior rings",
139 Self::DisconnectedInterior => "Geometry has disconnected interior",
140 Self::IntersectingInteriors => "Multi-polygon has intersecting interiors",
141 Self::WrongCornerOrder => "Box has corners in wrong order",
142 Self::InvalidCoordinate => "Geometry has point(s) with invalid coordinate(s)",
143 Self::CoordinateOutOfRange => {
144 "Geometry has coordinate(s) outside the supported arithmetic range"
145 }
146 Self::CollinearPointsOnFace => "Geometry has collinear points on a face",
147 Self::NonCoplanarPointsOnFace => "Geometry has non-coplanar points on a face",
148 Self::FewPointsOnFace => "Geometry has too few points on a face",
149 Self::InconsistentOrientation => "Geometry has inconsistent surface orientation",
150 Self::InvalidIntersection => "Geometry has invalid face intersections",
151 Self::DisconnectedSurface => "Geometry has a disconnected surface",
152 }
153 }
154}
155
156impl core::fmt::Display for ValidityFailure {
157 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158 formatter.write_str(self.message())
159 }
160}
161
162#[cfg(feature = "std")]
163impl std::error::Error for ValidityFailure {}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct ValidityOptions {
173 allow_duplicates: bool,
174 allow_spikes_for_linear: bool,
175}
176
177impl ValidityOptions {
178 pub const STRICT: Self = Self::new(false, false);
180
181 pub const BOOST_DEFAULT: Self = Self::new(true, true);
184
185 #[must_use]
187 pub const fn new(allow_duplicates: bool, allow_spikes_for_linear: bool) -> Self {
188 Self {
189 allow_duplicates,
190 allow_spikes_for_linear,
191 }
192 }
193
194 #[must_use]
196 pub const fn allows_duplicates(self) -> bool {
197 self.allow_duplicates
198 }
199
200 #[must_use]
202 pub const fn allows_spikes_for_linear(self) -> bool {
203 self.allow_spikes_for_linear
204 }
205}
206
207impl Default for ValidityOptions {
208 fn default() -> Self {
209 Self::STRICT
210 }
211}
212
213#[doc(hidden)]
218pub trait ValidityStrategy<G> {
219 fn apply(&self, geometry: &G, options: ValidityOptions) -> Result<(), ValidityFailure>;
220}
221
222#[doc(hidden)]
227pub trait ValidityStrategyForKind {
228 type S: Default;
229}
230
231#[doc(hidden)]
236#[derive(Debug, Default, Clone, Copy)]
237pub struct RingValidity;
238
239#[doc(hidden)]
244#[derive(Debug, Default, Clone, Copy)]
245pub struct PolygonValidity;
246
247#[doc(hidden)]
252#[derive(Debug, Default, Clone, Copy)]
253pub struct MultiPolygonValidity;
254
255impl ValidityStrategyForKind for RingTag {
258 type S = RingValidity;
259}
260
261impl ValidityStrategyForKind for PolygonTag {
264 type S = PolygonValidity;
265}
266
267impl ValidityStrategyForKind for MultiPolygonTag {
270 type S = MultiPolygonValidity;
271}
272
273#[inline]
286#[must_use = "validity failures must be handled"]
287pub fn is_valid<G>(geometry: &G) -> Result<(), ValidityFailure>
288where
289 G: Geometry,
290 G::Kind: ValidityStrategyForKind,
291 <G::Kind as ValidityStrategyForKind>::S: ValidityStrategy<G>,
292{
293 is_valid_with(geometry, ValidityOptions::STRICT)
294}
295
296#[inline]
313#[must_use = "validity failures must be handled"]
314pub fn is_valid_with<G>(geometry: &G, options: ValidityOptions) -> Result<(), ValidityFailure>
315where
316 G: Geometry,
317 G::Kind: ValidityStrategyForKind,
318 <G::Kind as ValidityStrategyForKind>::S: ValidityStrategy<G>,
319{
320 <<G::Kind as ValidityStrategyForKind>::S as Default>::default().apply(geometry, options)
321}
322
323#[inline]
328#[must_use]
329pub fn validity_reason<G>(geometry: &G) -> &'static str
330where
331 G: Geometry,
332 G::Kind: ValidityStrategyForKind,
333 <G::Kind as ValidityStrategyForKind>::S: ValidityStrategy<G>,
334{
335 validity_reason_with(geometry, ValidityOptions::STRICT)
336}
337
338#[inline]
340#[must_use]
341pub fn validity_reason_with<G>(geometry: &G, options: ValidityOptions) -> &'static str
342where
343 G: Geometry,
344 G::Kind: ValidityStrategyForKind,
345 <G::Kind as ValidityStrategyForKind>::S: ValidityStrategy<G>,
346{
347 match is_valid_with(geometry, options) {
348 Ok(()) => "Geometry is valid",
349 Err(failure) => failure.message(),
350 }
351}
352
353impl<G, P> ValidityStrategy<G> for RingValidity
356where
357 G: RingTrait<Point = P>,
358 P: PointMut + Default + Copy,
359 P::Scalar: CoordinateScalar + Into<f64>,
360 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
361{
362 fn apply(&self, ring: &G, options: ValidityOptions) -> Result<(), ValidityFailure> {
363 is_valid_ring_with(ring, options)
364 }
365}
366
367impl<G, P> ValidityStrategy<G> for PolygonValidity
370where
371 G: PolygonTrait<Point = P>,
372 P: PointMut + Default + Copy,
373 P::Scalar: CoordinateScalar + Into<f64>,
374 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
375{
376 fn apply(&self, polygon: &G, options: ValidityOptions) -> Result<(), ValidityFailure> {
377 is_valid_polygon_with(polygon, options)
378 }
379}
380
381impl<G, P> ValidityStrategy<G> for MultiPolygonValidity
384where
385 G: MultiPolygonTrait<Point = P>,
386 P: PointMut + Default + Copy,
387 P::Scalar: CoordinateScalar + Into<f64>,
388 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
389{
390 fn apply(&self, multi_polygon: &G, options: ValidityOptions) -> Result<(), ValidityFailure> {
391 let polygons: Vec<_> = multi_polygon.polygons().collect();
392 for polygon in &polygons {
393 is_valid_polygon_with(*polygon, options)?;
394 }
395 for first in 0..polygons.len() {
396 for second in (first + 1)..polygons.len() {
397 let matrix = crate::relate::relate(polygons[first], polygons[second])
398 .map_err(|_| ValidityFailure::SelfIntersection)?;
399 if matrix.interior_interior() == crate::relate::Dimension::Area
400 || matrix.boundary_boundary() == crate::relate::Dimension::Curve
401 {
402 return Err(ValidityFailure::IntersectingInteriors);
403 }
404 }
405 }
406 Ok(())
407 }
408}
409
410#[inline]
440#[must_use = "validity failures must be handled"]
441pub fn is_valid_ring<R, P>(ring: &R) -> Result<(), ValidityFailure>
442where
443 R: RingTrait<Point = P>,
444 P: PointMut + Default + Copy,
445 P::Scalar: CoordinateScalar + Into<f64>,
446 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
447{
448 is_valid_ring_with(ring, ValidityOptions::STRICT)
449}
450
451#[inline]
457#[must_use = "validity failures must be handled"]
458pub fn is_valid_ring_with<R, P>(ring: &R, options: ValidityOptions) -> Result<(), ValidityFailure>
459where
460 R: RingTrait<Point = P>,
461 P: PointMut + Default + Copy,
462 P::Scalar: CoordinateScalar + Into<f64>,
463 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
464{
465 validate_ring(ring, false, options)
466}
467
468fn validate_ring<R, P>(
474 ring: &R,
475 is_interior: bool,
476 options: ValidityOptions,
477) -> Result<(), ValidityFailure>
478where
479 R: RingTrait<Point = P>,
480 P: PointMut + Default + Copy,
481 P::Scalar: CoordinateScalar + Into<f64>,
482 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
483{
484 let mut pts: Vec<P> = ring.points().copied().collect();
485
486 for p in &pts {
488 let x: f64 = p.get::<0>().into();
489 let y: f64 = p.get::<1>().into();
490 if !x.is_finite() || !y.is_finite() {
491 return Err(ValidityFailure::InvalidCoordinate);
492 }
493 }
494
495 if options.allows_duplicates() {
499 let mut deduplicated = Vec::with_capacity(pts.len());
500 for point in pts {
501 if !deduplicated
502 .last()
503 .is_some_and(|previous| same_point(previous, &point))
504 {
505 deduplicated.push(point);
506 }
507 }
508 pts = deduplicated;
509 }
510
511 for p in &pts {
517 if !coordinate_in_range(p) {
518 return Err(ValidityFailure::CoordinateOutOfRange);
519 }
520 }
521
522 if pts.len() < 4 {
524 return Err(ValidityFailure::FewPoints);
525 }
526
527 if !same_point(&pts[0], &pts[pts.len() - 1]) {
529 return Err(ValidityFailure::NotClosed);
530 }
531
532 if !options.allows_duplicates() && pts.windows(2).any(|pair| same_point(&pair[0], &pair[1])) {
533 return Err(ValidityFailure::DuplicatePoints);
534 }
535
536 if has_spike(&pts) {
540 return Err(ValidityFailure::Spikes);
541 }
542
543 if has_self_intersection(&pts) {
545 return Err(ValidityFailure::SelfIntersection);
546 }
547
548 let area = ShoelaceArea.area(ring);
553 let zero = <P::Scalar as CoordinateScalar>::ZERO;
554 let properly_oriented = if is_interior {
555 area < zero
556 } else {
557 area > zero
558 };
559 if !properly_oriented {
560 return Err(ValidityFailure::WrongOrientation);
561 }
562
563 Ok(())
564}
565
566#[inline]
592#[must_use = "validity failures must be handled"]
593pub fn is_valid_polygon<G, P>(polygon: &G) -> Result<(), ValidityFailure>
594where
595 G: PolygonTrait<Point = P>,
596 P: PointMut + Default + Copy,
597 P::Scalar: CoordinateScalar + Into<f64>,
598 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
599{
600 is_valid_polygon_with(polygon, ValidityOptions::STRICT)
601}
602
603#[inline]
614#[must_use = "validity failures must be handled"]
615pub fn is_valid_polygon_with<G, P>(
616 polygon: &G,
617 options: ValidityOptions,
618) -> Result<(), ValidityFailure>
619where
620 G: PolygonTrait<Point = P>,
621 P: PointMut + Default + Copy,
622 P::Scalar: CoordinateScalar + Into<f64>,
623 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
624{
625 validate_ring(polygon.exterior(), false, options)?;
626 let inners: Vec<_> = polygon.interiors().collect();
627 for inner in &inners {
628 validate_ring(*inner, true, options)?;
629 let rep = inner
630 .points()
631 .next()
632 .expect("a validated ring contains at least four points");
633 if !WithinRing.covered_by(rep, polygon.exterior()) {
634 return Err(ValidityFailure::InteriorRingOutside);
635 }
636 let interaction = ring_pair_interaction(polygon.exterior(), *inner);
637 if interaction.proper_crossing {
638 return Err(ValidityFailure::SelfIntersection);
639 }
640 if interaction.overlap || interaction.contacts.len() > 1 {
641 return Err(ValidityFailure::DisconnectedInterior);
642 }
643 }
644
645 for first in 0..inners.len() {
646 for second in (first + 1)..inners.len() {
647 let interaction = ring_pair_interaction(inners[first], inners[second]);
648 if interaction.proper_crossing || interaction.overlap {
649 return Err(ValidityFailure::SelfIntersection);
650 }
651 if interaction.contacts.len() > 1 {
652 return Err(ValidityFailure::DisconnectedInterior);
653 }
654 let nested = interaction.contacts.is_empty()
655 && (ring_first_point_within(inners[first], inners[second])
656 || ring_first_point_within(inners[second], inners[first]));
657 (!nested)
658 .then_some(())
659 .ok_or(ValidityFailure::NestedInteriorRings)?;
660 }
661 }
662 Ok(())
663}
664
665#[derive(Default)]
666struct RingPairInteraction<P> {
667 proper_crossing: bool,
668 overlap: bool,
669 contacts: Vec<P>,
670}
671
672fn ring_pair_interaction<R1, R2, P>(first: &R1, second: &R2) -> RingPairInteraction<P>
673where
674 R1: RingTrait<Point = P>,
675 R2: RingTrait<Point = P>,
676 P: PointMut + Default + Copy,
677 P::Scalar: CoordinateScalar + Into<f64>,
678{
679 let first_points: Vec<P> = first.points().copied().collect();
680 let second_points: Vec<P> = second.points().copied().collect();
681 let mut interaction = RingPairInteraction::default();
682 for first_pair in first_points.windows(2) {
683 let first_segment = Segment::new(first_pair[0], first_pair[1]);
684 for second_pair in second_points.windows(2) {
685 let second_segment = Segment::new(second_pair[0], second_pair[1]);
686 match segment_intersection(&first_segment, &second_segment) {
687 SegmentIntersection::Disjoint | SegmentIntersection::OutOfRange => {}
688 SegmentIntersection::Collinear { .. } => interaction.overlap = true,
689 SegmentIntersection::Single(point) => {
690 let endpoint_contact = first_pair.iter().any(|value| same_point(value, &point))
691 || second_pair.iter().any(|value| same_point(value, &point));
692 if !endpoint_contact {
693 interaction.proper_crossing = true;
694 }
695 if !interaction
696 .contacts
697 .iter()
698 .any(|value| same_point(value, &point))
699 {
700 interaction.contacts.push(point);
701 }
702 }
703 }
704 }
705 }
706 interaction
707}
708
709fn ring_first_point_within<R1, R2, P>(inner: &R1, outer: &R2) -> bool
710where
711 R1: RingTrait<Point = P>,
712 R2: RingTrait<Point = P>,
713 P: Point,
714 P::Scalar: CoordinateScalar,
715 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
716{
717 inner
718 .points()
719 .next()
720 .is_some_and(|point| WithinRing.within(point, outer))
721}
722
723fn has_self_intersection<P>(pts: &[P]) -> bool
727where
728 P: PointMut + Default + Copy,
729 P::Scalar: CoordinateScalar + Into<f64>,
730{
731 let n = pts.len();
732 let edges = n - 1;
734 for i in 0..edges {
735 let a = Segment::new(pts[i], pts[i + 1]);
736 for j in (i + 1)..edges {
737 if j == i + 1 {
740 continue;
741 }
742 if i == 0 && j == edges - 1 {
743 continue;
744 }
745 let b = Segment::new(pts[j], pts[j + 1]);
746 match segment_intersection::<Segment<P>, P>(&a, &b) {
747 SegmentIntersection::Disjoint | SegmentIntersection::OutOfRange => {}
748 _ => return true,
749 }
750 }
751 }
752 false
753}
754
755fn same_point<P: Point>(a: &P, b: &P) -> bool
756where
757 P::Scalar: PartialEq,
758{
759 a.get::<0>() == b.get::<0>() && a.get::<1>() == b.get::<1>()
760}
761
762fn is_spike_triple<P: Point>(a: &P, b: &P, c: &P) -> bool
769where
770 P::Scalar: CoordinateScalar,
771{
772 let ux = b.get::<0>() - a.get::<0>();
773 let uy = b.get::<1>() - a.get::<1>();
774 let vx = c.get::<0>() - b.get::<0>();
775 let vy = c.get::<1>() - b.get::<1>();
776 let zero = <P::Scalar as CoordinateScalar>::ZERO;
777 ux * vy - uy * vx == zero && ux * vx + uy * vy < zero
778}
779
780fn has_spike<P: Point + Copy>(pts: &[P]) -> bool
786where
787 P::Scalar: CoordinateScalar,
788{
789 let cycle = &pts[..pts.len() - 1];
792 let n = cycle.len(); (0..n).any(|i| is_spike_triple(&cycle[(i + n - 1) % n], &cycle[i], &cycle[(i + 1) % n]))
794}
795
796#[cfg(test)]
797mod tests {
798 use super::{ValidityFailure, is_valid_polygon, is_valid_ring};
802 use geometry_cs::Cartesian;
803 use geometry_model::{Point2D, Polygon, Ring, polygon};
804
805 type P = Point2D<f64, Cartesian>;
806
807 #[test]
808 fn valid_square_ring() {
809 let r: Ring<P> = Ring::from_vec(vec![
810 P::new(0.0, 0.0),
811 P::new(0.0, 1.0),
812 P::new(1.0, 1.0),
813 P::new(1.0, 0.0),
814 P::new(0.0, 0.0),
815 ]);
816 assert!(is_valid_ring(&r).is_ok());
817 }
818
819 #[test]
820 fn too_few_points() {
821 let r: Ring<P> = Ring::from_vec(vec![P::new(0.0, 0.0), P::new(1.0, 0.0), P::new(0.0, 0.0)]);
822 assert_eq!(is_valid_ring(&r), Err(ValidityFailure::FewPoints));
823 }
824
825 #[test]
826 fn out_of_range_self_intersection_is_not_reported_valid() {
827 let s = 2.0e14;
834 let huge_bowtie: Ring<P> = Ring::from_vec(vec![
835 P::new(0.0, 0.0),
836 P::new(s, s),
837 P::new(s, 0.0),
838 P::new(0.0, s),
839 P::new(0.0, 0.0),
840 ]);
841 assert_eq!(
842 is_valid_ring(&huge_bowtie),
843 Err(ValidityFailure::CoordinateOutOfRange)
844 );
845 let small_bowtie: Ring<P> = Ring::from_vec(vec![
847 P::new(0.0, 0.0),
848 P::new(2.0, 2.0),
849 P::new(2.0, 0.0),
850 P::new(0.0, 2.0),
851 P::new(0.0, 0.0),
852 ]);
853 assert_eq!(
854 is_valid_ring(&small_bowtie),
855 Err(ValidityFailure::SelfIntersection)
856 );
857 }
858
859 #[test]
860 fn not_closed() {
861 let r: Ring<P> = Ring::from_vec(vec![
862 P::new(0.0, 0.0),
863 P::new(1.0, 0.0),
864 P::new(1.0, 1.0),
865 P::new(0.0, 1.0),
866 ]);
867 assert_eq!(is_valid_ring(&r), Err(ValidityFailure::NotClosed));
868 }
869
870 #[test]
871 fn self_intersecting_bowtie() {
872 let r: Ring<P> = Ring::from_vec(vec![
874 P::new(0.0, 0.0),
875 P::new(2.0, 2.0),
876 P::new(2.0, 0.0),
877 P::new(0.0, 2.0),
878 P::new(0.0, 0.0),
879 ]);
880 assert_eq!(is_valid_ring(&r), Err(ValidityFailure::SelfIntersection));
881 }
882
883 #[test]
884 fn invalid_coordinate() {
885 let r: Ring<P> = Ring::from_vec(vec![
886 P::new(0.0, 0.0),
887 P::new(f64::NAN, 0.0),
888 P::new(1.0, 1.0),
889 P::new(0.0, 0.0),
890 ]);
891 assert_eq!(is_valid_ring(&r), Err(ValidityFailure::InvalidCoordinate));
892 }
893
894 #[test]
895 fn valid_polygon() {
896 let pg: Polygon<P> = polygon![[(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0), (0.0, 0.0)]];
897 assert!(is_valid_polygon(&pg).is_ok());
898 }
899
900 #[test]
901 fn valid_polygon_with_hole() {
902 let pg: Polygon<P> = polygon![
903 [
904 (0.0, 0.0),
905 (0.0, 10.0),
906 (10.0, 10.0),
907 (10.0, 0.0),
908 (0.0, 0.0)
909 ],
910 [(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]
911 ];
912 assert!(is_valid_polygon(&pg).is_ok());
913 }
914
915 #[test]
916 fn wrongly_oriented_ring_is_rejected() {
917 let r: Ring<P> = Ring::from_vec(vec![
920 P::new(0.0, 0.0),
921 P::new(2.0, 0.0),
922 P::new(2.0, 2.0),
923 P::new(0.0, 2.0),
924 P::new(0.0, 0.0),
925 ]);
926 assert_eq!(is_valid_ring(&r), Err(ValidityFailure::WrongOrientation));
927 }
928
929 #[test]
930 fn ccw_declared_ring_correctly_wound_is_ok() {
931 let r: Ring<P, false> = Ring::from_vec(vec![
935 P::new(0.0, 0.0),
936 P::new(2.0, 0.0),
937 P::new(2.0, 2.0),
938 P::new(0.0, 2.0),
939 P::new(0.0, 0.0),
940 ]);
941 assert!(is_valid_ring(&r).is_ok());
942 }
943
944 #[test]
945 fn all_collinear_ring_is_spikes() {
946 let flat: Ring<P> = Ring::from_vec(vec![
950 P::new(0.0, 0.0),
951 P::new(4.0, 0.0),
952 P::new(2.0, 0.0),
953 P::new(0.0, 0.0),
954 ]);
955 assert_eq!(is_valid_ring(&flat), Err(ValidityFailure::Spikes));
956 }
957
958 #[test]
959 fn square_with_spike_is_spikes() {
960 let r: Ring<P> = Ring::from_vec(vec![
964 P::new(0.0, 0.0),
965 P::new(0.0, 4.0),
966 P::new(4.0, 4.0),
967 P::new(4.0, 0.0),
968 P::new(2.0, 0.0),
969 P::new(2.0, -2.0),
970 P::new(2.0, 0.0),
971 P::new(0.0, 0.0),
972 ]);
973 assert_eq!(is_valid_ring(&r), Err(ValidityFailure::Spikes));
974 }
975
976 #[test]
977 fn hole_outside_exterior_is_rejected() {
978 let pg: Polygon<P> = polygon![
984 [(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0), (0.0, 0.0)],
985 [
986 (10.0, 10.0),
987 (12.0, 10.0),
988 (12.0, 12.0),
989 (10.0, 12.0),
990 (10.0, 10.0)
991 ]
992 ];
993 assert_eq!(
994 is_valid_polygon(&pg),
995 Err(ValidityFailure::InteriorRingOutside)
996 );
997 }
998
999 #[test]
1000 fn hole_touching_exterior_boundary_is_ok() {
1001 let pg: Polygon<P> = polygon![
1005 [(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0), (0.0, 0.0)],
1006 [(0.0, 2.0), (1.0, 1.0), (1.0, 3.0), (0.0, 2.0)]
1007 ];
1008 assert!(is_valid_polygon(&pg).is_ok());
1009 }
1010
1011 #[test]
1012 fn wrongly_oriented_hole_is_rejected() {
1013 let pg: Polygon<P> = polygon![
1016 [(0.0, 0.0), (0.0, 4.0), (4.0, 4.0), (4.0, 0.0), (0.0, 0.0)],
1017 [(1.0, 1.0), (1.0, 2.0), (2.0, 2.0), (2.0, 1.0), (1.0, 1.0)]
1018 ];
1019 assert_eq!(
1020 is_valid_polygon(&pg),
1021 Err(ValidityFailure::WrongOrientation)
1022 );
1023 }
1024}