1use crate::error::{AlgorithmError, Result};
52use crate::vector::contains::{
53 point_in_polygon_or_boundary, point_on_polygon_boundary, point_strictly_inside_polygon,
54};
55use crate::vector::intersection::SegmentIntersection;
56use crate::vector::intersection::intersect_segment_segment;
57use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
58
59use core::fmt;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum Dimension {
71 Empty,
73 Point,
75 Line,
77 Area,
79 DontCare,
81}
82
83impl Dimension {
84 #[must_use]
86 pub fn to_char(self) -> char {
87 match self {
88 Self::Empty => 'F',
89 Self::Point => '0',
90 Self::Line => '1',
91 Self::Area => '2',
92 Self::DontCare => '*',
93 }
94 }
95
96 pub fn from_char(c: char) -> Result<Self> {
98 match c {
99 'F' | 'f' => Ok(Self::Empty),
100 '0' => Ok(Self::Point),
101 '1' => Ok(Self::Line),
102 '2' => Ok(Self::Area),
103 '*' => Ok(Self::DontCare),
104 'T' | 't' => Ok(Self::DontCare), _ => Err(AlgorithmError::InvalidInput(format!(
106 "invalid DE-9IM character: '{c}'"
107 ))),
108 }
109 }
110
111 fn matches_pattern(self, pat: char) -> bool {
122 match pat {
123 '*' => true,
124 'T' | 't' => self != Self::Empty,
125 'F' | 'f' => self == Self::Empty,
126 '0' => self == Self::Point,
127 '1' => self == Self::Line,
128 '2' => self == Self::Area,
129 _ => false,
130 }
131 }
132}
133
134impl fmt::Display for Dimension {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 write!(f, "{}", self.to_char())
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
155pub struct De9im {
156 cells: [Dimension; 9],
157}
158
159impl De9im {
160 pub const II: usize = 0;
163 pub const IB: usize = 1;
165 pub const IE: usize = 2;
167 pub const BI: usize = 3;
169 pub const BB: usize = 4;
171 pub const BE: usize = 5;
173 pub const EI: usize = 6;
175 pub const EB: usize = 7;
177 pub const EE: usize = 8;
179
180 #[must_use]
182 pub const fn new(cells: [Dimension; 9]) -> Self {
183 Self { cells }
184 }
185
186 pub fn from_str(s: &str) -> Result<Self> {
188 let chars: Vec<char> = s.chars().collect();
189 if chars.len() != 9 {
190 return Err(AlgorithmError::InvalidInput(format!(
191 "DE-9IM string must be exactly 9 characters, got {}",
192 chars.len()
193 )));
194 }
195 let mut cells = [Dimension::Empty; 9];
196 for (i, &ch) in chars.iter().enumerate() {
197 cells[i] = Dimension::from_char(ch)?;
198 }
199 Ok(Self { cells })
200 }
201
202 #[must_use]
204 pub fn get(&self, index: usize) -> Dimension {
205 if index < 9 {
206 self.cells[index]
207 } else {
208 Dimension::Empty
209 }
210 }
211
212 pub fn set(&mut self, index: usize, dim: Dimension) {
214 if index < 9 {
215 self.cells[index] = dim;
216 }
217 }
218
219 #[must_use]
221 pub fn to_string_repr(&self) -> String {
222 self.cells.iter().map(|d| d.to_char()).collect()
223 }
224
225 #[must_use]
227 pub fn transpose(&self) -> Self {
228 Self::new([
229 self.cells[Self::II],
230 self.cells[Self::BI],
231 self.cells[Self::EI],
232 self.cells[Self::IB],
233 self.cells[Self::BB],
234 self.cells[Self::EB],
235 self.cells[Self::IE],
236 self.cells[Self::BE],
237 self.cells[Self::EE],
238 ])
239 }
240
241 #[must_use]
250 pub fn matches(&self, pattern: &str) -> bool {
251 let chars: Vec<char> = pattern.chars().collect();
252 if chars.len() != 9 {
253 return false;
254 }
255 self.cells
256 .iter()
257 .zip(chars.iter())
258 .all(|(dim, &pat)| dim.matches_pattern(pat))
259 }
260
261 #[must_use]
270 pub fn is_equals(&self) -> bool {
271 self.matches("T*F**FFF*")
272 }
273
274 #[must_use]
278 pub fn is_disjoint(&self) -> bool {
279 self.matches("FF*FF****")
280 }
281
282 #[must_use]
284 pub fn is_intersects(&self) -> bool {
285 !self.is_disjoint()
286 }
287
288 #[must_use]
292 pub fn is_touches(&self) -> bool {
293 self.matches("FT*******") || self.matches("F**T*****") || self.matches("F***T****")
294 }
295
296 #[must_use]
303 pub fn is_crosses(&self, dim_a: u8, dim_b: u8) -> bool {
304 if dim_a < dim_b {
305 self.matches("T*T******")
307 } else if dim_a > dim_b {
308 self.transpose().matches("T*T******")
310 } else if dim_a == 1 && dim_b == 1 {
311 self.matches("0********")
313 } else {
314 false
316 }
317 }
318
319 #[must_use]
324 pub fn is_within(&self) -> bool {
325 self.matches("T*F**F***")
326 }
327
328 #[must_use]
332 pub fn is_contains(&self) -> bool {
333 self.matches("T*****FF*")
334 }
335
336 #[must_use]
343 pub fn is_overlaps(&self, dim_a: u8, dim_b: u8) -> bool {
344 if dim_a != dim_b {
345 return false;
346 }
347 if dim_a == 1 {
348 self.matches("1*T***T**")
349 } else {
350 self.matches("T*T***T**")
351 }
352 }
353
354 #[must_use]
358 pub fn is_covers(&self) -> bool {
359 self.matches("T*****FF*")
360 || self.matches("*T****FF*")
361 || self.matches("***T**FF*")
362 || self.matches("****T*FF*")
363 }
364
365 #[must_use]
369 pub fn is_covered_by(&self) -> bool {
370 self.matches("T*F**F***")
371 || self.matches("*TF**F***")
372 || self.matches("**FT*F***")
373 || self.matches("**F*TF***")
374 }
375}
376
377impl fmt::Display for De9im {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 write!(f, "{}", self.to_string_repr())
380 }
381}
382
383pub fn relate_polygons(a: &Polygon, b: &Polygon) -> Result<De9im> {
396 validate_polygon(a, "relate_polygons", "polygon a")?;
397 validate_polygon(b, "relate_polygons", "polygon b")?;
398
399 let mut matrix = [Dimension::Empty; 9];
400
401 matrix[De9im::EE] = Dimension::Area;
403
404 let mut a_bdry_in_b_interior = false;
406 let mut a_bdry_on_b_boundary = false;
407 let mut a_bdry_in_b_exterior = false;
408
409 classify_boundary_against_polygon(
410 a,
411 b,
412 &mut a_bdry_in_b_interior,
413 &mut a_bdry_on_b_boundary,
414 &mut a_bdry_in_b_exterior,
415 );
416
417 let mut b_bdry_in_a_interior = false;
418 let mut b_bdry_on_a_boundary = false;
419 let mut b_bdry_in_a_exterior = false;
420
421 classify_boundary_against_polygon(
422 b,
423 a,
424 &mut b_bdry_in_a_interior,
425 &mut b_bdry_on_a_boundary,
426 &mut b_bdry_in_a_exterior,
427 );
428
429 let mut a_int_in_b_interior = false;
433 let mut a_int_on_b_boundary = false;
434 let mut a_int_in_b_exterior = false;
435 let mut b_int_in_a_interior = false;
436 let mut b_int_on_a_boundary = false;
437 let mut b_int_in_a_exterior = false;
438
439 let a_samples = interior_sample_points(a);
441 for pt in &a_samples {
442 classify_point_against_polygon(
443 pt,
444 b,
445 &mut a_int_in_b_interior,
446 &mut a_int_on_b_boundary,
447 &mut a_int_in_b_exterior,
448 );
449 }
450
451 let b_samples = interior_sample_points(b);
453 for pt in &b_samples {
454 classify_point_against_polygon(
455 pt,
456 a,
457 &mut b_int_in_a_interior,
458 &mut b_int_on_a_boundary,
459 &mut b_int_in_a_exterior,
460 );
461 }
462
463 let cross_samples = generate_crossing_interior_samples(a, b);
467 for pt in &cross_samples {
468 if point_strictly_inside_polygon(pt, a) && point_strictly_inside_polygon(pt, b) {
470 a_int_in_b_interior = true;
471 b_int_in_a_interior = true;
472 }
473 }
474
475 let bb_dim = compute_boundary_boundary_dim(a, b);
477
478 if a_int_in_b_interior || b_int_in_a_interior {
482 matrix[De9im::II] = Dimension::Area;
483 }
484
485 if b_bdry_in_a_interior || a_int_on_b_boundary {
488 matrix[De9im::IB] = Dimension::Line;
489 }
490
491 if a_int_in_b_exterior {
493 matrix[De9im::IE] = Dimension::Area;
494 }
495
496 if a_bdry_in_b_interior {
498 matrix[De9im::BI] = Dimension::Line;
499 }
500
501 if a_bdry_on_b_boundary || b_bdry_on_a_boundary || bb_dim != Dimension::Empty {
503 matrix[De9im::BB] = bb_dim;
504 }
505
506 if a_bdry_in_b_exterior {
508 matrix[De9im::BE] = Dimension::Line;
509 }
510
511 if b_int_in_a_exterior {
513 matrix[De9im::EI] = Dimension::Area;
514 }
515
516 if b_bdry_in_a_exterior {
518 matrix[De9im::EB] = Dimension::Line;
519 }
520
521 Ok(De9im::new(matrix))
522}
523
524pub fn relate_point_polygon(pt: &Point, poly: &Polygon) -> Result<De9im> {
530 validate_polygon(poly, "relate_point_polygon", "polygon")?;
531
532 let coord = &pt.coord;
533 let mut matrix = [Dimension::Empty; 9];
534
535 matrix[De9im::EE] = Dimension::Area;
537 if point_on_polygon_boundary(coord, poly) {
545 matrix[De9im::II] = Dimension::Empty; matrix[De9im::IB] = Dimension::Point; matrix[De9im::IE] = Dimension::Empty; matrix[De9im::EI] = Dimension::Area; matrix[De9im::EB] = Dimension::Line; } else if point_strictly_inside_polygon(coord, poly) {
553 matrix[De9im::II] = Dimension::Point; matrix[De9im::IB] = Dimension::Empty;
556 matrix[De9im::IE] = Dimension::Empty;
557 matrix[De9im::EI] = Dimension::Area;
558 matrix[De9im::EB] = Dimension::Line;
559 } else {
560 matrix[De9im::II] = Dimension::Empty;
562 matrix[De9im::IB] = Dimension::Empty;
563 matrix[De9im::IE] = Dimension::Point;
564 matrix[De9im::EI] = Dimension::Area;
565 matrix[De9im::EB] = Dimension::Line;
566 }
567
568 Ok(De9im::new(matrix))
569}
570
571pub fn relate_line_polygon(line: &LineString, poly: &Polygon) -> Result<De9im> {
581 validate_polygon(poly, "relate_line_polygon", "polygon")?;
582 if line.coords.len() < 2 {
583 return Err(AlgorithmError::InsufficientData {
584 operation: "relate_line_polygon",
585 message: "line must have at least 2 coordinates".to_string(),
586 });
587 }
588
589 let mut matrix = [Dimension::Empty; 9];
590 matrix[De9im::EE] = Dimension::Area;
591
592 let mut line_has_interior_in_poly_interior = false;
594 let mut line_has_interior_on_poly_boundary = false;
595 let mut line_has_interior_in_poly_exterior = false;
596
597 for coord in &line.coords {
599 if point_on_polygon_boundary(coord, poly) {
600 line_has_interior_on_poly_boundary = true;
601 } else if point_strictly_inside_polygon(coord, poly) {
602 line_has_interior_in_poly_interior = true;
603 } else {
604 line_has_interior_in_poly_exterior = true;
605 }
606 }
607
608 for i in 0..line.coords.len().saturating_sub(1) {
610 let mid = midpoint(&line.coords[i], &line.coords[i + 1]);
611 if point_on_polygon_boundary(&mid, poly) {
612 line_has_interior_on_poly_boundary = true;
613 } else if point_strictly_inside_polygon(&mid, poly) {
614 line_has_interior_in_poly_interior = true;
615 } else {
616 line_has_interior_in_poly_exterior = true;
617 }
618 }
619
620 let line_is_closed = line.coords.len() >= 3
622 && coords_equal(&line.coords[0], &line.coords[line.coords.len() - 1]);
623
624 let mut line_boundary_in_poly_interior = false;
625 let mut line_boundary_on_poly_boundary = false;
626 let mut line_boundary_in_poly_exterior = false;
627
628 if !line_is_closed && line.coords.len() >= 2 {
629 let endpoints = [&line.coords[0], &line.coords[line.coords.len() - 1]];
630 for ep in &endpoints {
631 if point_on_polygon_boundary(ep, poly) {
632 line_boundary_on_poly_boundary = true;
633 } else if point_strictly_inside_polygon(ep, poly) {
634 line_boundary_in_poly_interior = true;
635 } else {
636 line_boundary_in_poly_exterior = true;
637 }
638 }
639 }
640
641 let boundary_intersections = count_boundary_line_intersections(line, poly);
643
644 if line_has_interior_in_poly_interior {
646 matrix[De9im::II] = Dimension::Line;
647 }
648
649 if line_has_interior_on_poly_boundary || boundary_intersections > 0 {
651 if has_segment_on_polygon_boundary(line, poly) {
652 matrix[De9im::IB] = Dimension::Line;
653 } else {
654 matrix[De9im::IB] = Dimension::Point;
655 }
656 }
657
658 if line_has_interior_in_poly_exterior {
660 matrix[De9im::IE] = Dimension::Line;
661 }
662
663 if line_boundary_in_poly_interior {
665 matrix[De9im::BI] = Dimension::Point;
666 }
667
668 if line_boundary_on_poly_boundary {
670 matrix[De9im::BB] = Dimension::Point;
671 }
672
673 if line_boundary_in_poly_exterior {
675 matrix[De9im::BE] = Dimension::Point;
676 }
677
678 matrix[De9im::EI] = Dimension::Area;
680
681 matrix[De9im::EB] = Dimension::Line;
683
684 Ok(De9im::new(matrix))
685}
686
687pub fn relate(a: &Polygon, b: &Polygon) -> Result<De9im> {
693 relate_polygons(a, b)
694}
695
696pub trait EqualsPredicate {
702 fn equals_topo(&self, other: &Self) -> Result<bool>;
704}
705
706pub trait CoversPredicate {
708 fn covers(&self, other: &Self) -> Result<bool>;
710}
711
712pub trait CoveredByPredicate {
714 fn covered_by(&self, other: &Self) -> Result<bool>;
716}
717
718impl EqualsPredicate for Polygon {
721 fn equals_topo(&self, other: &Self) -> Result<bool> {
722 let m = relate_polygons(self, other)?;
723 Ok(m.is_equals())
724 }
725}
726
727impl CoversPredicate for Polygon {
728 fn covers(&self, other: &Self) -> Result<bool> {
729 let m = relate_polygons(self, other)?;
730 Ok(m.is_covers())
731 }
732}
733
734impl CoveredByPredicate for Polygon {
735 fn covered_by(&self, other: &Self) -> Result<bool> {
736 let m = relate_polygons(self, other)?;
737 Ok(m.is_covered_by())
738 }
739}
740
741impl EqualsPredicate for Point {
744 fn equals_topo(&self, other: &Self) -> Result<bool> {
745 Ok(coords_equal(&self.coord, &other.coord))
746 }
747}
748
749impl CoversPredicate for Point {
750 fn covers(&self, other: &Self) -> Result<bool> {
751 Ok(coords_equal(&self.coord, &other.coord))
753 }
754}
755
756impl CoveredByPredicate for Point {
757 fn covered_by(&self, other: &Self) -> Result<bool> {
758 Ok(coords_equal(&self.coord, &other.coord))
759 }
760}
761
762fn validate_polygon(poly: &Polygon, operation: &'static str, name: &str) -> Result<()> {
768 if poly.exterior.coords.len() < 4 {
769 return Err(AlgorithmError::InsufficientData {
770 operation,
771 message: format!("{name} exterior must have at least 4 coordinates"),
772 });
773 }
774 Ok(())
775}
776
777fn coords_equal(a: &Coordinate, b: &Coordinate) -> bool {
779 (a.x - b.x).abs() < f64::EPSILON && (a.y - b.y).abs() < f64::EPSILON
780}
781
782fn midpoint(a: &Coordinate, b: &Coordinate) -> Coordinate {
784 Coordinate::new_2d((a.x + b.x) * 0.5, (a.y + b.y) * 0.5)
785}
786
787fn classify_point_against_polygon(
789 pt: &Coordinate,
790 poly: &Polygon,
791 in_interior: &mut bool,
792 on_boundary: &mut bool,
793 in_exterior: &mut bool,
794) {
795 if point_on_polygon_boundary(pt, poly) {
796 *on_boundary = true;
797 } else if point_strictly_inside_polygon(pt, poly) {
798 *in_interior = true;
799 } else {
800 *in_exterior = true;
801 }
802}
803
804fn classify_boundary_against_polygon(
806 source: &Polygon,
807 target: &Polygon,
808 in_interior: &mut bool,
809 on_boundary: &mut bool,
810 in_exterior: &mut bool,
811) {
812 for coord in &source.exterior.coords {
814 classify_point_against_polygon(coord, target, in_interior, on_boundary, in_exterior);
815 }
816 for i in 0..source.exterior.coords.len().saturating_sub(1) {
818 let mid = midpoint(&source.exterior.coords[i], &source.exterior.coords[i + 1]);
819 classify_point_against_polygon(&mid, target, in_interior, on_boundary, in_exterior);
820 }
821}
822
823fn generate_crossing_interior_samples(a: &Polygon, b: &Polygon) -> Vec<Coordinate> {
831 let mut samples = Vec::new();
832 let a_coords = &a.exterior.coords;
833 let b_coords = &b.exterior.coords;
834 let eps = 1e-6;
835
836 for i in 0..a_coords.len().saturating_sub(1) {
837 for j in 0..b_coords.len().saturating_sub(1) {
838 if let SegmentIntersection::Point(pt) = intersect_segment_segment(
839 &a_coords[i],
840 &a_coords[i + 1],
841 &b_coords[j],
842 &b_coords[j + 1],
843 ) {
844 let a_dx = a_coords[i + 1].x - a_coords[i].x;
846 let a_dy = a_coords[i + 1].y - a_coords[i].y;
847 let b_dx = b_coords[j + 1].x - b_coords[j].x;
848 let b_dy = b_coords[j + 1].y - b_coords[j].y;
849
850 let normals = [(a_dy, -a_dx), (-a_dy, a_dx), (b_dy, -b_dx), (-b_dy, b_dx)];
852
853 for &(nx_a, ny_a) in &normals[..2] {
855 for &(nx_b, ny_b) in &normals[2..] {
856 let nx = nx_a + nx_b;
857 let ny = ny_a + ny_b;
858 let len = (nx * nx + ny * ny).sqrt();
859 if len < f64::EPSILON {
860 continue;
861 }
862 let candidate =
863 Coordinate::new_2d(pt.x + eps * nx / len, pt.y + eps * ny / len);
864 if point_strictly_inside_polygon(&candidate, a)
865 && point_strictly_inside_polygon(&candidate, b)
866 {
867 samples.push(candidate);
868 }
869 }
870 }
871 }
872 }
873 if samples.len() >= 4 {
874 break;
875 }
876 }
877 samples
878}
879
880fn interior_sample_points(poly: &Polygon) -> Vec<Coordinate> {
886 let mut samples = Vec::new();
887 let n = poly.exterior.coords.len();
888 if n < 4 {
889 return samples;
890 }
891
892 let (mut cx, mut cy) = (0.0_f64, 0.0_f64);
894 let vertex_count = n - 1;
896 for coord in &poly.exterior.coords[..vertex_count] {
897 cx += coord.x;
898 cy += coord.y;
899 }
900 if vertex_count > 0 {
901 cx /= vertex_count as f64;
902 cy /= vertex_count as f64;
903 }
904 let centroid = Coordinate::new_2d(cx, cy);
905
906 if point_strictly_inside_polygon(¢roid, poly) {
907 samples.push(centroid);
908 }
909
910 for i in 0..vertex_count {
912 let mid = midpoint(&poly.exterior.coords[i], &poly.exterior.coords[i + 1]);
913 let pulled = Coordinate::new_2d(mid.x + (cx - mid.x) * 0.1, mid.y + (cy - mid.y) * 0.1);
915 if point_strictly_inside_polygon(&pulled, poly) {
916 samples.push(pulled);
917 if samples.len() >= 5 {
918 break;
919 }
920 }
921 }
922
923 if samples.is_empty() {
925 if let Some((min_x, min_y, max_x, max_y)) = poly.bounds() {
926 let step_x = (max_x - min_x) / 5.0;
927 let step_y = (max_y - min_y) / 5.0;
928 'outer: for ix in 1..5 {
929 for iy in 1..5 {
930 let pt = Coordinate::new_2d(
931 min_x + step_x * (ix as f64),
932 min_y + step_y * (iy as f64),
933 );
934 if point_strictly_inside_polygon(&pt, poly) {
935 samples.push(pt);
936 if samples.len() >= 3 {
937 break 'outer;
938 }
939 }
940 }
941 }
942 }
943 }
944
945 samples
946}
947
948fn compute_boundary_boundary_dim(a: &Polygon, b: &Polygon) -> Dimension {
954 let a_coords = &a.exterior.coords;
955 let b_coords = &b.exterior.coords;
956
957 let mut has_point_intersection = false;
958 let mut has_line_intersection = false;
959
960 for i in 0..a_coords.len().saturating_sub(1) {
961 for j in 0..b_coords.len().saturating_sub(1) {
962 match intersect_segment_segment(
963 &a_coords[i],
964 &a_coords[i + 1],
965 &b_coords[j],
966 &b_coords[j + 1],
967 ) {
968 SegmentIntersection::Point(_) => {
969 has_point_intersection = true;
970 }
971 SegmentIntersection::Overlap(_, _) => {
972 has_line_intersection = true;
973 }
974 SegmentIntersection::None => {}
975 }
976 }
977 }
978
979 if has_line_intersection {
980 Dimension::Line
981 } else if has_point_intersection {
982 Dimension::Point
983 } else {
984 Dimension::Empty
985 }
986}
987
988fn count_boundary_line_intersections(line: &LineString, poly: &Polygon) -> usize {
990 let mut count = 0;
991 let ring = &poly.exterior.coords;
992 for i in 0..line.coords.len().saturating_sub(1) {
993 for j in 0..ring.len().saturating_sub(1) {
994 match intersect_segment_segment(
995 &line.coords[i],
996 &line.coords[i + 1],
997 &ring[j],
998 &ring[j + 1],
999 ) {
1000 SegmentIntersection::Point(_) | SegmentIntersection::Overlap(_, _) => {
1001 count += 1;
1002 }
1003 SegmentIntersection::None => {}
1004 }
1005 }
1006 }
1007 count
1008}
1009
1010fn has_segment_on_polygon_boundary(line: &LineString, poly: &Polygon) -> bool {
1012 let ring = &poly.exterior.coords;
1013 for i in 0..line.coords.len().saturating_sub(1) {
1014 for j in 0..ring.len().saturating_sub(1) {
1015 if let SegmentIntersection::Overlap(_, _) = intersect_segment_segment(
1016 &line.coords[i],
1017 &line.coords[i + 1],
1018 &ring[j],
1019 &ring[j + 1],
1020 ) {
1021 return true;
1022 }
1023 }
1024 }
1025 false
1026}
1027
1028#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use crate::error::AlgorithmError;
1036
1037 type TestResult = core::result::Result<(), Box<dyn std::error::Error>>;
1038
1039 fn make_rect(x0: f64, y0: f64, x1: f64, y1: f64) -> Result<Polygon> {
1041 let coords = vec![
1042 Coordinate::new_2d(x0, y0),
1043 Coordinate::new_2d(x1, y0),
1044 Coordinate::new_2d(x1, y1),
1045 Coordinate::new_2d(x0, y1),
1046 Coordinate::new_2d(x0, y0),
1047 ];
1048 let ext = LineString::new(coords).map_err(AlgorithmError::Core)?;
1049 Polygon::new(ext, vec![]).map_err(AlgorithmError::Core)
1050 }
1051
1052 #[test]
1057 fn test_dimension_to_char() {
1058 assert_eq!(Dimension::Empty.to_char(), 'F');
1059 assert_eq!(Dimension::Point.to_char(), '0');
1060 assert_eq!(Dimension::Line.to_char(), '1');
1061 assert_eq!(Dimension::Area.to_char(), '2');
1062 assert_eq!(Dimension::DontCare.to_char(), '*');
1063 }
1064
1065 #[test]
1066 fn test_dimension_from_char() -> TestResult {
1067 assert_eq!(Dimension::from_char('F')?, Dimension::Empty);
1068 assert_eq!(Dimension::from_char('0')?, Dimension::Point);
1069 assert_eq!(Dimension::from_char('1')?, Dimension::Line);
1070 assert_eq!(Dimension::from_char('2')?, Dimension::Area);
1071 assert_eq!(Dimension::from_char('*')?, Dimension::DontCare);
1072 assert!(Dimension::from_char('X').is_err());
1073 Ok(())
1074 }
1075
1076 #[test]
1077 fn test_de9im_from_str() -> TestResult {
1078 let m = De9im::from_str("212101212")?;
1079 assert_eq!(m.get(De9im::II), Dimension::Area);
1080 assert_eq!(m.get(De9im::IB), Dimension::Line);
1081 assert_eq!(m.get(De9im::IE), Dimension::Area);
1082 assert_eq!(m.get(De9im::BI), Dimension::Line);
1083 assert_eq!(m.get(De9im::BB), Dimension::Point);
1084 assert_eq!(m.get(De9im::BE), Dimension::Line);
1085 assert_eq!(m.get(De9im::EI), Dimension::Area);
1086 assert_eq!(m.get(De9im::EB), Dimension::Line);
1087 assert_eq!(m.get(De9im::EE), Dimension::Area);
1088 Ok(())
1089 }
1090
1091 #[test]
1092 fn test_de9im_display() -> TestResult {
1093 let m = De9im::from_str("212101212")?;
1094 assert_eq!(format!("{m}"), "212101212");
1095 Ok(())
1096 }
1097
1098 #[test]
1099 fn test_de9im_matches_basic() -> TestResult {
1100 let m = De9im::from_str("212101212")?;
1101 assert!(m.matches("2*2***2*2")); assert!(m.matches("T*T***T**")); assert!(!m.matches("FF*FF****")); Ok(())
1105 }
1106
1107 #[test]
1108 fn test_de9im_transpose() -> TestResult {
1109 let m = De9im::from_str("212101212")?;
1110 let t = m.transpose();
1111 assert_eq!(t.get(De9im::II), Dimension::Area); assert_eq!(t.get(De9im::IB), Dimension::Line); assert_eq!(t.get(De9im::IE), Dimension::Area); assert_eq!(t.get(De9im::BI), Dimension::Line); assert_eq!(t.get(De9im::BB), Dimension::Point); assert_eq!(t.get(De9im::BE), Dimension::Line); assert_eq!(t.get(De9im::EI), Dimension::Area); assert_eq!(t.get(De9im::EB), Dimension::Line); assert_eq!(t.get(De9im::EE), Dimension::Area); Ok(())
1123 }
1124
1125 #[test]
1126 fn test_de9im_from_str_invalid_length() {
1127 assert!(De9im::from_str("212").is_err());
1128 assert!(De9im::from_str("2121012121").is_err());
1129 }
1130
1131 #[test]
1132 fn test_de9im_get_out_of_bounds() {
1133 let m = De9im::new([Dimension::Empty; 9]);
1134 assert_eq!(m.get(99), Dimension::Empty);
1135 }
1136
1137 #[test]
1142 fn test_is_equals_synthetic() -> TestResult {
1143 let m = De9im::from_str("2FFF1FFF2")?;
1145 assert!(m.is_equals());
1146 let m2 = De9im::from_str("212101212")?;
1148 assert!(!m2.is_equals());
1149 Ok(())
1150 }
1151
1152 #[test]
1153 fn test_is_disjoint_synthetic() -> TestResult {
1154 let m = De9im::from_str("FF2FF1212")?;
1155 assert!(m.is_disjoint());
1156 assert!(!m.is_intersects());
1157
1158 let m2 = De9im::from_str("212101212")?;
1159 assert!(!m2.is_disjoint());
1160 assert!(m2.is_intersects());
1161 Ok(())
1162 }
1163
1164 #[test]
1165 fn test_is_touches_synthetic() -> TestResult {
1166 let m = De9im::from_str("F11FF0212")?;
1168 assert!(m.is_touches());
1169
1170 let m2 = De9im::from_str("212101212")?;
1171 assert!(!m2.is_touches());
1172 Ok(())
1173 }
1174
1175 #[test]
1176 fn test_is_crosses_synthetic() -> TestResult {
1177 let m = De9im::from_str("1020F1102")?;
1179 assert!(m.is_crosses(1, 2));
1180 assert!(!m.is_crosses(2, 2));
1182
1183 let m2 = De9im::from_str("0FFFFFFFF")?;
1185 assert!(m2.is_crosses(1, 1));
1186 Ok(())
1187 }
1188
1189 #[test]
1190 fn test_is_within_synthetic() -> TestResult {
1191 let m = De9im::from_str("2FF1FF212")?;
1193 assert!(m.is_within());
1194 assert!(!m.is_contains()); Ok(())
1196 }
1197
1198 #[test]
1199 fn test_is_contains_synthetic() -> TestResult {
1200 let m = De9im::from_str("212101FF2")?;
1202 assert!(m.is_contains());
1203 Ok(())
1204 }
1205
1206 #[test]
1207 fn test_is_overlaps_synthetic() -> TestResult {
1208 let m = De9im::from_str("212101212")?;
1210 assert!(m.is_overlaps(2, 2));
1211 assert!(!m.is_overlaps(1, 2));
1213
1214 let m2 = De9im::from_str("1FT1FFT1F")?;
1216 assert!(m2.is_overlaps(1, 1));
1217 Ok(())
1218 }
1219
1220 #[test]
1221 fn test_is_covers_synthetic() -> TestResult {
1222 let m = De9im::from_str("2FF1FFFF2")?;
1224 assert!(m.is_covers());
1225 Ok(())
1226 }
1227
1228 #[test]
1229 fn test_is_covered_by_synthetic() -> TestResult {
1230 let m = De9im::from_str("2FF0FF212")?;
1232 assert!(m.is_covered_by());
1233 Ok(())
1234 }
1235
1236 #[test]
1241 fn test_relate_disjoint_squares() -> TestResult {
1242 let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
1243 let b = make_rect(10.0, 10.0, 14.0, 14.0)?;
1244 let m = relate_polygons(&a, &b)?;
1245 assert!(m.is_disjoint(), "disjoint squares: matrix = {m}");
1246 assert!(!m.is_intersects());
1247 assert_eq!(m.get(De9im::II), Dimension::Empty);
1249 assert_eq!(m.get(De9im::EE), Dimension::Area);
1250 Ok(())
1251 }
1252
1253 #[test]
1254 fn test_relate_overlapping_squares() -> TestResult {
1255 let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
1256 let b = make_rect(2.0, 2.0, 6.0, 6.0)?;
1257 let m = relate_polygons(&a, &b)?;
1258 assert!(m.is_intersects(), "overlapping squares: matrix = {m}");
1259 assert!(!m.is_disjoint());
1260 assert!(m.is_overlaps(2, 2), "overlapping squares: matrix = {m}");
1261 assert!(!m.is_contains());
1262 assert!(!m.is_within());
1263 assert_eq!(
1265 m.get(De9im::II),
1266 Dimension::Area,
1267 "II = {}",
1268 m.get(De9im::II)
1269 );
1270 assert_eq!(
1272 m.get(De9im::IE),
1273 Dimension::Area,
1274 "IE = {}",
1275 m.get(De9im::IE)
1276 );
1277 assert_eq!(
1279 m.get(De9im::EI),
1280 Dimension::Area,
1281 "EI = {}",
1282 m.get(De9im::EI)
1283 );
1284 Ok(())
1285 }
1286
1287 #[test]
1288 fn test_relate_contained_square() -> TestResult {
1289 let a = make_rect(0.0, 0.0, 10.0, 10.0)?;
1291 let b = make_rect(2.0, 2.0, 8.0, 8.0)?;
1292 let m = relate_polygons(&a, &b)?;
1293 assert!(m.is_contains(), "a contains b: matrix = {m}");
1294 let mt = m.transpose();
1296 assert!(mt.is_within(), "b within a: transposed matrix = {mt}");
1297 Ok(())
1298 }
1299
1300 #[test]
1301 fn test_relate_touching_squares() -> TestResult {
1302 let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
1304 let b = make_rect(4.0, 0.0, 8.0, 4.0)?;
1305 let m = relate_polygons(&a, &b)?;
1306 assert!(m.is_touches(), "touching squares: matrix = {m}");
1307 assert!(!m.is_disjoint());
1308 assert!(!m.is_overlaps(2, 2));
1309 Ok(())
1310 }
1311
1312 #[test]
1313 fn test_relate_identical_polygons() -> TestResult {
1314 let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
1315 let b = make_rect(0.0, 0.0, 4.0, 4.0)?;
1316 let m = relate_polygons(&a, &b)?;
1317 assert!(m.is_equals(), "identical polygons: matrix = {m}");
1318 assert!(!m.is_disjoint());
1319 Ok(())
1320 }
1321
1322 #[test]
1327 fn test_relate_point_inside_polygon() -> TestResult {
1328 let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
1329 let pt = Point::new(5.0, 5.0);
1330 let m = relate_point_polygon(&pt, &poly)?;
1331 assert!(m.is_within(), "point inside polygon: matrix = {m}");
1332 Ok(())
1333 }
1334
1335 #[test]
1336 fn test_relate_point_on_boundary() -> TestResult {
1337 let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
1338 let pt = Point::new(0.0, 5.0);
1339 let m = relate_point_polygon(&pt, &poly)?;
1340 assert!(m.is_touches(), "point on boundary: matrix = {m}");
1342 Ok(())
1343 }
1344
1345 #[test]
1346 fn test_relate_point_outside_polygon() -> TestResult {
1347 let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
1348 let pt = Point::new(20.0, 20.0);
1349 let m = relate_point_polygon(&pt, &poly)?;
1350 assert!(m.is_disjoint(), "point outside polygon: matrix = {m}");
1351 Ok(())
1352 }
1353
1354 #[test]
1359 fn test_relate_line_crossing_polygon() -> TestResult {
1360 let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
1362 let line_coords = vec![Coordinate::new_2d(-5.0, 5.0), Coordinate::new_2d(15.0, 5.0)];
1363 let line = LineString::new(line_coords).map_err(AlgorithmError::Core)?;
1364 let m = relate_line_polygon(&line, &poly)?;
1365 assert!(m.is_crosses(1, 2), "line crossing polygon: matrix = {m}");
1367 Ok(())
1368 }
1369
1370 #[test]
1371 fn test_relate_line_inside_polygon() -> TestResult {
1372 let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
1373 let line_coords = vec![Coordinate::new_2d(2.0, 5.0), Coordinate::new_2d(8.0, 5.0)];
1374 let line = LineString::new(line_coords).map_err(AlgorithmError::Core)?;
1375 let m = relate_line_polygon(&line, &poly)?;
1376 assert!(m.is_within(), "line inside polygon: matrix = {m}");
1377 Ok(())
1378 }
1379
1380 #[test]
1381 fn test_relate_line_outside_polygon() -> TestResult {
1382 let poly = make_rect(0.0, 0.0, 10.0, 10.0)?;
1383 let line_coords = vec![
1384 Coordinate::new_2d(20.0, 20.0),
1385 Coordinate::new_2d(30.0, 30.0),
1386 ];
1387 let line = LineString::new(line_coords).map_err(AlgorithmError::Core)?;
1388 let m = relate_line_polygon(&line, &poly)?;
1389 assert!(m.is_disjoint(), "line outside polygon: matrix = {m}");
1390 Ok(())
1391 }
1392
1393 #[test]
1398 fn test_equals_predicate_polygon() -> TestResult {
1399 let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
1400 let b = make_rect(0.0, 0.0, 4.0, 4.0)?;
1401 assert!(a.equals_topo(&b)?);
1402 let c = make_rect(1.0, 1.0, 5.0, 5.0)?;
1403 assert!(!a.equals_topo(&c)?);
1404 Ok(())
1405 }
1406
1407 #[test]
1408 fn test_covers_predicate_polygon() -> TestResult {
1409 let a = make_rect(0.0, 0.0, 10.0, 10.0)?;
1410 let b = make_rect(2.0, 2.0, 8.0, 8.0)?;
1411 assert!(a.covers(&b)?);
1412 assert!(!b.covers(&a)?);
1413 Ok(())
1414 }
1415
1416 #[test]
1417 fn test_covered_by_predicate_polygon() -> TestResult {
1418 let a = make_rect(2.0, 2.0, 8.0, 8.0)?;
1419 let b = make_rect(0.0, 0.0, 10.0, 10.0)?;
1420 assert!(a.covered_by(&b)?);
1421 assert!(!b.covered_by(&a)?);
1422 Ok(())
1423 }
1424
1425 #[test]
1426 fn test_equals_predicate_point() -> TestResult {
1427 let a = Point::new(1.0, 2.0);
1428 let b = Point::new(1.0, 2.0);
1429 let c = Point::new(3.0, 4.0);
1430 assert!(a.equals_topo(&b)?);
1431 assert!(!a.equals_topo(&c)?);
1432 Ok(())
1433 }
1434
1435 #[test]
1440 fn test_pattern_matching_roundtrip() -> TestResult {
1441 let patterns = [
1442 "T*F**FFF*", "FF*FF****", "FT*******", "T*T******", "T*F**F***", "T*****FF*", "T*T***T**", ];
1450 for pat in &patterns {
1451 let m = De9im::from_str(pat)?;
1452 assert!(
1453 m.matches(pat),
1454 "pattern {pat} should match itself, matrix = {m}"
1455 );
1456 }
1457 Ok(())
1458 }
1459
1460 #[test]
1465 fn test_polygon_polygon_crosses_always_false_via_de9im() -> TestResult {
1466 let a = make_rect(0.0, 0.0, 4.0, 4.0)?;
1468 let b = make_rect(2.0, 2.0, 6.0, 6.0)?;
1469 let m = relate_polygons(&a, &b)?;
1470 assert!(
1471 !m.is_crosses(2, 2),
1472 "polygon/polygon crosses must always be false per OGC"
1473 );
1474 Ok(())
1475 }
1476
1477 #[test]
1482 fn test_relate_invalid_polygon() {
1483 let coords = vec![
1485 Coordinate::new_2d(0.0, 0.0),
1486 Coordinate::new_2d(1.0, 0.0),
1487 Coordinate::new_2d(0.0, 0.0),
1488 ];
1489 let ext = LineString::new(coords);
1490 if let Ok(e) = ext {
1492 if let Ok(bad_poly) = Polygon::new(e, vec![]) {
1493 let good = make_rect(0.0, 0.0, 10.0, 10.0);
1494 if let Ok(g) = good {
1495 let result = relate_polygons(&bad_poly, &g);
1496 assert!(result.is_err());
1497 }
1498 }
1499 }
1500 }
1501
1502 #[test]
1503 fn test_relate_line_polygon_invalid_line() {
1504 let poly_result = make_rect(0.0, 0.0, 10.0, 10.0);
1505 if let Ok(poly) = poly_result {
1506 let coords = vec![Coordinate::new_2d(5.0, 5.0)];
1508 let line_result = LineString::new(coords);
1509 assert!(line_result.is_err());
1511 }
1512 }
1513
1514 #[test]
1515 fn test_dimension_display() {
1516 assert_eq!(format!("{}", Dimension::Empty), "F");
1517 assert_eq!(format!("{}", Dimension::Point), "0");
1518 assert_eq!(format!("{}", Dimension::Line), "1");
1519 assert_eq!(format!("{}", Dimension::Area), "2");
1520 }
1521}