1use crate::error::{AlgorithmError, Result};
48use crate::vector::pool::{PoolGuard, get_pooled_polygon};
49use oxigdal_core::vector::{Coordinate, LineString, Polygon};
50
51#[cfg(feature = "std")]
52use std::vec::Vec;
53
54const EPSILON: f64 = 1e-10;
56
57pub fn difference_polygon(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
87 use oxigdal_core::OxiGdalError;
88
89 if poly1.exterior.coords.len() < 4 {
91 return Err(OxiGdalError::invalid_parameter_builder(
92 "poly1",
93 format!("exterior must have at least 4 coordinates, got {}", poly1.exterior.coords.len()),
94 )
95 .with_parameter("coordinate_count", poly1.exterior.coords.len().to_string())
96 .with_parameter("min_count", "4")
97 .with_operation("difference_polygon")
98 .with_suggestion("A valid polygon requires at least 4 coordinates (first and last must be identical to close the ring)")
99 .build()
100 .into());
101 }
102
103 if poly2.exterior.coords.len() < 4 {
104 return Err(OxiGdalError::invalid_parameter_builder(
105 "poly2",
106 format!("exterior must have at least 4 coordinates, got {}", poly2.exterior.coords.len()),
107 )
108 .with_parameter("coordinate_count", poly2.exterior.coords.len().to_string())
109 .with_parameter("min_count", "4")
110 .with_operation("difference_polygon")
111 .with_suggestion("A valid polygon requires at least 4 coordinates (first and last must be identical to close the ring)")
112 .build()
113 .into());
114 }
115
116 crate::vector::clipping::clip_polygons(
118 poly1,
119 poly2,
120 crate::vector::clipping::ClipOperation::Difference,
121 )
122}
123
124fn point_in_ring(point: &Coordinate, ring: &[Coordinate]) -> bool {
128 crate::vector::clipping::point_in_ring(point, ring)
129}
130
131#[allow(dead_code)]
137fn compute_ring_centroid(coords: &[Coordinate]) -> Coordinate {
138 crate::vector::clipping::compute_ring_centroid(coords)
139}
140
141pub fn difference_polygons(base: &[Polygon], subtract: &[Polygon]) -> Result<Vec<Polygon>> {
158 if base.is_empty() {
159 return Ok(vec![]);
160 }
161
162 if subtract.is_empty() {
163 return Ok(base.to_vec());
164 }
165
166 for (i, poly) in base.iter().enumerate() {
168 if poly.exterior.coords.len() < 4 {
169 return Err(AlgorithmError::InsufficientData {
170 operation: "difference_polygons",
171 message: format!(
172 "base polygon {} exterior must have at least 4 coordinates",
173 i
174 ),
175 });
176 }
177 }
178
179 for (i, poly) in subtract.iter().enumerate() {
180 if poly.exterior.coords.len() < 4 {
181 return Err(AlgorithmError::InsufficientData {
182 operation: "difference_polygons",
183 message: format!(
184 "subtract polygon {} exterior must have at least 4 coordinates",
185 i
186 ),
187 });
188 }
189 }
190
191 let mut result = base.to_vec();
193
194 for sub_poly in subtract {
195 let mut new_result = Vec::new();
196
197 for base_poly in &result {
198 let diff = difference_polygon(base_poly, sub_poly)?;
199 new_result.extend(diff);
200 }
201
202 result = new_result;
203 }
204
205 Ok(result)
206}
207
208pub fn symmetric_difference(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
226 let diff1 = difference_polygon(poly1, poly2)?;
228
229 let diff2 = difference_polygon(poly2, poly1)?;
231
232 let mut result = diff1;
234 result.extend(diff2);
235
236 Ok(result)
237}
238
239pub fn clip_to_box(
270 polygon: &Polygon,
271 min_x: f64,
272 min_y: f64,
273 max_x: f64,
274 max_y: f64,
275) -> Result<Vec<Polygon>> {
276 if polygon.exterior.coords.len() < 4 {
277 return Err(AlgorithmError::InsufficientData {
278 operation: "clip_to_box",
279 message: "polygon exterior must have at least 4 coordinates".to_string(),
280 });
281 }
282
283 if min_x >= max_x || min_y >= max_y {
284 return Err(AlgorithmError::InvalidParameter {
285 parameter: "bounding box",
286 message: "invalid bounding box dimensions".to_string(),
287 });
288 }
289
290 if let Some((poly_min_x, poly_min_y, poly_max_x, poly_max_y)) = polygon.bounds() {
292 if poly_max_x < min_x || poly_min_x > max_x || poly_max_y < min_y || poly_min_y > max_y {
293 return Ok(vec![]);
295 }
296
297 if poly_min_x >= min_x && poly_max_x <= max_x && poly_min_y >= min_y && poly_max_y <= max_y
298 {
299 return Ok(vec![polygon.clone()]);
301 }
302 }
303
304 let mut clipped_coords = polygon.exterior.coords.clone();
306
307 clipped_coords = clip_against_edge(&clipped_coords, min_x, true, false)?; clipped_coords = clip_against_edge(&clipped_coords, max_x, true, true)?; clipped_coords = clip_against_edge(&clipped_coords, min_y, false, false)?; clipped_coords = clip_against_edge(&clipped_coords, max_y, false, true)?; if clipped_coords.len() < 4 {
314 return Ok(vec![]);
316 }
317
318 if let (Some(first), Some(last)) = (clipped_coords.first(), clipped_coords.last()) {
320 if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
321 clipped_coords.push(*first);
322 }
323 }
324
325 let clipped_exterior = LineString::new(clipped_coords).map_err(AlgorithmError::Core)?;
326
327 let clipped_interiors = clip_interior_rings_to_box(
329 &polygon.interiors,
330 min_x,
331 min_y,
332 max_x,
333 max_y,
334 &clipped_exterior,
335 )?;
336
337 let result = Polygon::new(clipped_exterior, clipped_interiors).map_err(AlgorithmError::Core)?;
338
339 Ok(vec![result])
340}
341
342fn clip_interior_rings_to_box(
362 interiors: &[LineString],
363 min_x: f64,
364 min_y: f64,
365 max_x: f64,
366 max_y: f64,
367 clipped_exterior: &LineString,
368) -> Result<Vec<LineString>> {
369 let mut result = Vec::new();
370
371 for hole in interiors {
372 if hole.coords.len() < 4 {
373 continue;
374 }
375
376 let hole_bounds = hole.bounds();
378 if let Some((hole_min_x, hole_min_y, hole_max_x, hole_max_y)) = hole_bounds {
379 if hole_max_x < min_x || hole_min_x > max_x || hole_max_y < min_y || hole_min_y > max_y
381 {
382 continue;
384 }
385
386 if hole_min_x >= min_x
388 && hole_max_x <= max_x
389 && hole_min_y >= min_y
390 && hole_max_y <= max_y
391 {
392 if is_ring_inside_ring(&hole.coords, &clipped_exterior.coords) {
394 result.push(hole.clone());
395 }
396 continue;
397 }
398 }
399
400 let clipped_hole = clip_ring_to_box(&hole.coords, min_x, min_y, max_x, max_y)?;
402
403 if clipped_hole.len() >= 4 {
404 if is_ring_inside_ring(&clipped_hole, &clipped_exterior.coords) {
406 let mut closed_hole = clipped_hole;
408 if let (Some(first), Some(last)) = (closed_hole.first(), closed_hole.last()) {
409 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
410 closed_hole.push(*first);
411 }
412 }
413
414 if closed_hole.len() >= 4 {
415 if let Ok(hole_ring) = LineString::new(closed_hole) {
416 result.push(hole_ring);
417 }
418 }
419 }
420 }
421 }
422
423 Ok(result)
424}
425
426fn clip_ring_to_box(
440 coords: &[Coordinate],
441 min_x: f64,
442 min_y: f64,
443 max_x: f64,
444 max_y: f64,
445) -> Result<Vec<Coordinate>> {
446 let mut clipped = coords.to_vec();
447
448 clipped = clip_against_edge(&clipped, min_x, true, false)?; clipped = clip_against_edge(&clipped, max_x, true, true)?; clipped = clip_against_edge(&clipped, min_y, false, false)?; clipped = clip_against_edge(&clipped, max_y, false, true)?; Ok(clipped)
455}
456
457fn is_ring_inside_ring(inner: &[Coordinate], outer: &[Coordinate]) -> bool {
477 if inner.len() < 3 || outer.len() < 3 {
478 return false;
479 }
480
481 for i in 0..(inner.len() - 1) {
485 for j in 0..(outer.len() - 1) {
486 if segments_intersect(&inner[i], &inner[i + 1], &outer[j], &outer[j + 1]) {
487 return false;
488 }
489 }
490 }
491
492 inner.iter().any(|v| point_in_ring(v, outer))
496}
497
498pub fn validate_polygon_topology(polygon: &Polygon) -> Result<bool> {
518 if polygon.exterior.coords.len() < 4 {
520 return Ok(false);
521 }
522
523 if let (Some(first), Some(last)) = (
525 polygon.exterior.coords.first(),
526 polygon.exterior.coords.last(),
527 ) {
528 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
529 return Ok(false);
530 }
531 }
532
533 for hole in &polygon.interiors {
535 if hole.coords.len() < 4 {
537 return Ok(false);
538 }
539
540 if let (Some(first), Some(last)) = (hole.coords.first(), hole.coords.last()) {
542 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
543 return Ok(false);
544 }
545 }
546
547 if !is_ring_inside_ring(&hole.coords, &polygon.exterior.coords) {
549 return Ok(false);
550 }
551 }
552
553 for i in 0..polygon.interiors.len() {
555 for j in (i + 1)..polygon.interiors.len() {
556 if rings_overlap(&polygon.interiors[i].coords, &polygon.interiors[j].coords)? {
557 return Ok(false);
558 }
559 }
560 }
561
562 Ok(true)
563}
564
565fn rings_overlap(ring1: &[Coordinate], ring2: &[Coordinate]) -> Result<bool> {
570 for coord in ring1 {
572 if point_in_ring(coord, ring2) {
573 return Ok(true);
574 }
575 }
576
577 for coord in ring2 {
578 if point_in_ring(coord, ring1) {
579 return Ok(true);
580 }
581 }
582
583 if ring1.len() < 2 || ring2.len() < 2 {
586 return Ok(false);
587 }
588
589 for i in 0..(ring1.len() - 1) {
590 for j in 0..(ring2.len() - 1) {
591 if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
592 return Ok(true);
593 }
594 }
595 }
596
597 Ok(false)
598}
599
600fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
602 let d1x = p2.x - p1.x;
603 let d1y = p2.y - p1.y;
604 let d2x = p4.x - p3.x;
605 let d2y = p4.y - p3.y;
606
607 let cross = d1x * d2y - d1y * d2x;
608
609 if cross.abs() < EPSILON {
610 return false;
612 }
613
614 let dx = p3.x - p1.x;
615 let dy = p3.y - p1.y;
616
617 let t = (dx * d2y - dy * d2x) / cross;
618 let u = (dx * d1y - dy * d1x) / cross;
619
620 t > EPSILON && t < (1.0 - EPSILON) && u > EPSILON && u < (1.0 - EPSILON)
622}
623
624pub fn merge_adjacent_holes(holes: &[LineString]) -> Result<Vec<LineString>> {
637 if holes.is_empty() {
638 return Ok(vec![]);
639 }
640
641 if holes.len() == 1 {
642 return Ok(holes.to_vec());
643 }
644
645 let mut result = Vec::new();
648 let mut merged = vec![false; holes.len()];
649
650 for i in 0..holes.len() {
651 if merged[i] {
652 continue;
653 }
654
655 let mut current_hole = holes[i].coords.clone();
656
657 for j in (i + 1)..holes.len() {
658 if merged[j] {
659 continue;
660 }
661
662 if rings_overlap(¤t_hole, &holes[j].coords)? {
663 if compute_ring_area(&holes[j].coords).abs()
666 > compute_ring_area(¤t_hole).abs()
667 {
668 current_hole = holes[j].coords.clone();
669 }
670 merged[j] = true;
671 }
672 }
673
674 if current_hole.len() >= 4 {
675 if let Ok(ring) = LineString::new(current_hole) {
676 result.push(ring);
677 }
678 }
679 }
680
681 Ok(result)
682}
683
684fn clip_against_edge(
686 coords: &[Coordinate],
687 edge_value: f64,
688 is_vertical: bool,
689 is_max: bool,
690) -> Result<Vec<Coordinate>> {
691 if coords.is_empty() {
692 return Ok(vec![]);
693 }
694
695 let mut result = Vec::new();
696
697 for i in 0..coords.len() {
698 let current = &coords[i];
699 let next = &coords[(i + 1) % coords.len()];
700
701 let current_inside = is_inside(current, edge_value, is_vertical, is_max);
702 let next_inside = is_inside(next, edge_value, is_vertical, is_max);
703
704 if current_inside {
705 result.push(*current);
706 }
707
708 if current_inside != next_inside {
709 if let Some(intersection) = compute_intersection(current, next, edge_value, is_vertical)
711 {
712 result.push(intersection);
713 }
714 }
715 }
716
717 Ok(result)
718}
719
720fn is_inside(point: &Coordinate, edge_value: f64, is_vertical: bool, is_max: bool) -> bool {
722 let value = if is_vertical { point.x } else { point.y };
723
724 if is_max {
725 value <= edge_value
726 } else {
727 value >= edge_value
728 }
729}
730
731fn compute_intersection(
733 p1: &Coordinate,
734 p2: &Coordinate,
735 edge_value: f64,
736 is_vertical: bool,
737) -> Option<Coordinate> {
738 if is_vertical {
739 let dx = p2.x - p1.x;
741 if dx.abs() < f64::EPSILON {
742 return None; }
744
745 let t = (edge_value - p1.x) / dx;
746 if (0.0..=1.0).contains(&t) {
747 let y = p1.y + t * (p2.y - p1.y);
748 Some(Coordinate::new_2d(edge_value, y))
749 } else {
750 None
751 }
752 } else {
753 let dy = p2.y - p1.y;
755 if dy.abs() < f64::EPSILON {
756 return None; }
758
759 let t = (edge_value - p1.y) / dy;
760 if (0.0..=1.0).contains(&t) {
761 let x = p1.x + t * (p2.x - p1.x);
762 Some(Coordinate::new_2d(x, edge_value))
763 } else {
764 None
765 }
766 }
767}
768
769pub fn erase_small_holes(polygon: &Polygon, min_area: f64) -> Result<Polygon> {
787 if polygon.exterior.coords.len() < 4 {
788 return Err(AlgorithmError::InsufficientData {
789 operation: "erase_small_holes",
790 message: "polygon exterior must have at least 4 coordinates".to_string(),
791 });
792 }
793
794 if min_area < 0.0 {
795 return Err(AlgorithmError::InvalidParameter {
796 parameter: "min_area",
797 message: "min_area must be non-negative".to_string(),
798 });
799 }
800
801 let mut kept_holes = Vec::new();
802
803 for hole in &polygon.interiors {
804 let area = compute_ring_area(&hole.coords).abs();
805 if area >= min_area {
806 kept_holes.push(hole.clone());
807 }
808 }
809
810 Polygon::new(polygon.exterior.clone(), kept_holes).map_err(AlgorithmError::Core)
811}
812
813fn compute_ring_area(coords: &[Coordinate]) -> f64 {
815 if coords.len() < 3 {
816 return 0.0;
817 }
818
819 let mut area = 0.0;
820 let n = coords.len();
821
822 for i in 0..n {
823 let j = (i + 1) % n;
824 area += coords[i].x * coords[j].y;
825 area -= coords[j].x * coords[i].y;
826 }
827
828 area / 2.0
829}
830
831pub fn difference_polygon_pooled(
880 subject: &Polygon,
881 clip: &Polygon,
882) -> Result<PoolGuard<'static, Polygon>> {
883 let results = difference_polygon(subject, clip)?;
884
885 if let Some(result) = results.first() {
887 let mut poly = get_pooled_polygon();
888 poly.exterior = result.exterior.clone();
889 poly.interiors = result.interiors.clone();
890 Ok(poly)
891 } else {
892 Err(AlgorithmError::InsufficientData {
893 operation: "difference_polygon_pooled",
894 message: "difference resulted in no polygons".to_string(),
895 })
896 }
897}
898
899pub fn difference_polygons_pooled(
937 base: &[Polygon],
938 subtract: &[Polygon],
939) -> Result<PoolGuard<'static, Polygon>> {
940 let results = difference_polygons(base, subtract)?;
941
942 if let Some(result) = results.first() {
944 let mut poly = get_pooled_polygon();
945 poly.exterior = result.exterior.clone();
946 poly.interiors = result.interiors.clone();
947 Ok(poly)
948 } else {
949 Err(AlgorithmError::InsufficientData {
950 operation: "difference_polygons_pooled",
951 message: "difference resulted in no polygons".to_string(),
952 })
953 }
954}
955
956pub fn symmetric_difference_pooled(
1001 poly1: &Polygon,
1002 poly2: &Polygon,
1003) -> Result<Vec<PoolGuard<'static, Polygon>>> {
1004 let results = symmetric_difference(poly1, poly2)?;
1005
1006 let mut pooled_results = Vec::new();
1008 for result in results {
1009 let mut poly = get_pooled_polygon();
1010 poly.exterior = result.exterior;
1011 poly.interiors = result.interiors;
1012 pooled_results.push(poly);
1013 }
1014
1015 Ok(pooled_results)
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use super::*;
1021
1022 fn create_square(x: f64, y: f64, size: f64) -> Result<Polygon> {
1023 let coords = vec![
1024 Coordinate::new_2d(x, y),
1025 Coordinate::new_2d(x + size, y),
1026 Coordinate::new_2d(x + size, y + size),
1027 Coordinate::new_2d(x, y + size),
1028 Coordinate::new_2d(x, y),
1029 ];
1030 let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
1031 Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
1032 }
1033
1034 #[test]
1035 fn test_difference_polygon_disjoint() {
1036 let poly1 = create_square(0.0, 0.0, 5.0);
1037 let poly2 = create_square(10.0, 10.0, 5.0);
1038
1039 assert!(poly1.is_ok() && poly2.is_ok());
1040 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1041 let result = difference_polygon(&p1, &p2);
1042 assert!(result.is_ok());
1043 if let Ok(polys) = result {
1044 assert_eq!(polys.len(), 1); }
1046 }
1047 }
1048
1049 #[test]
1050 fn test_difference_polygon_contained() {
1051 let poly1 = create_square(0.0, 0.0, 10.0);
1052 let poly2 = create_square(2.0, 2.0, 3.0);
1053
1054 assert!(poly1.is_ok() && poly2.is_ok());
1055 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1056 let result = difference_polygon(&p1, &p2);
1057 assert!(result.is_ok());
1058 if let Ok(polys) = result {
1059 assert_eq!(polys.len(), 1);
1061 assert_eq!(polys[0].interiors.len(), 1);
1062 }
1063 }
1064 }
1065
1066 #[test]
1067 fn test_difference_polygon_completely_subtracted() {
1068 let poly1 = create_square(2.0, 2.0, 3.0);
1069 let poly2 = create_square(0.0, 0.0, 10.0);
1070
1071 assert!(poly1.is_ok() && poly2.is_ok());
1072 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1073 let result = difference_polygon(&p1, &p2);
1074 assert!(result.is_ok());
1075 if let Ok(polys) = result {
1076 assert_eq!(polys.len(), 0);
1078 }
1079 }
1080 }
1081
1082 #[test]
1083 fn test_difference_polygons_multiple() {
1084 let base = vec![create_square(0.0, 0.0, 10.0).ok()];
1085 let subtract = vec![
1086 create_square(2.0, 2.0, 2.0).ok(),
1087 create_square(6.0, 6.0, 2.0).ok(),
1088 ];
1089
1090 let base_polys: Vec<_> = base.into_iter().flatten().collect();
1091 let subtract_polys: Vec<_> = subtract.into_iter().flatten().collect();
1092
1093 let result = difference_polygons(&base_polys, &subtract_polys);
1094 assert!(result.is_ok());
1095 }
1096
1097 #[test]
1098 fn test_symmetric_difference() {
1099 let poly1 = create_square(0.0, 0.0, 5.0);
1100 let poly2 = create_square(3.0, 0.0, 5.0);
1101
1102 assert!(poly1.is_ok() && poly2.is_ok());
1103 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1104 let result = symmetric_difference(&p1, &p2);
1105 assert!(result.is_ok());
1106 if let Ok(polys) = result {
1108 assert!(!polys.is_empty());
1109 }
1110 }
1111 }
1112
1113 #[test]
1114 fn test_clip_to_box_inside() {
1115 let poly = create_square(2.0, 2.0, 3.0);
1116 assert!(poly.is_ok());
1117
1118 if let Ok(p) = poly {
1119 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1120 assert!(result.is_ok());
1121 if let Ok(polys) = result {
1122 assert_eq!(polys.len(), 1);
1124 }
1125 }
1126 }
1127
1128 #[test]
1129 fn test_clip_to_box_outside() {
1130 let poly = create_square(15.0, 15.0, 3.0);
1131 assert!(poly.is_ok());
1132
1133 if let Ok(p) = poly {
1134 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1135 assert!(result.is_ok());
1136 if let Ok(polys) = result {
1137 assert_eq!(polys.len(), 0);
1139 }
1140 }
1141 }
1142
1143 #[test]
1144 fn test_clip_to_box_partial() {
1145 let poly = create_square(5.0, 5.0, 10.0);
1146 assert!(poly.is_ok());
1147
1148 if let Ok(p) = poly {
1149 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1150 assert!(result.is_ok());
1151 if let Ok(polys) = result {
1152 assert_eq!(polys.len(), 1);
1154 }
1155 }
1156 }
1157
1158 #[test]
1159 fn test_erase_small_holes() {
1160 let exterior_coords = vec![
1161 Coordinate::new_2d(0.0, 0.0),
1162 Coordinate::new_2d(20.0, 0.0),
1163 Coordinate::new_2d(20.0, 20.0),
1164 Coordinate::new_2d(0.0, 20.0),
1165 Coordinate::new_2d(0.0, 0.0),
1166 ];
1167
1168 let small_hole_coords = vec![
1169 Coordinate::new_2d(2.0, 2.0),
1170 Coordinate::new_2d(3.0, 2.0),
1171 Coordinate::new_2d(3.0, 3.0),
1172 Coordinate::new_2d(2.0, 3.0),
1173 Coordinate::new_2d(2.0, 2.0),
1174 ];
1175
1176 let large_hole_coords = vec![
1177 Coordinate::new_2d(10.0, 10.0),
1178 Coordinate::new_2d(15.0, 10.0),
1179 Coordinate::new_2d(15.0, 15.0),
1180 Coordinate::new_2d(10.0, 15.0),
1181 Coordinate::new_2d(10.0, 10.0),
1182 ];
1183
1184 let exterior = LineString::new(exterior_coords);
1185 let small_hole = LineString::new(small_hole_coords);
1186 let large_hole = LineString::new(large_hole_coords);
1187
1188 assert!(exterior.is_ok() && small_hole.is_ok() && large_hole.is_ok());
1189
1190 if let (Ok(ext), Ok(sh), Ok(lh)) = (exterior, small_hole, large_hole) {
1191 let polygon = Polygon::new(ext, vec![sh, lh]);
1192 assert!(polygon.is_ok());
1193
1194 if let Ok(poly) = polygon {
1195 let result = erase_small_holes(&poly, 10.0);
1196 assert!(result.is_ok());
1197 if let Ok(cleaned) = result {
1198 assert_eq!(cleaned.interiors.len(), 1);
1200 }
1201 }
1202 }
1203 }
1204
1205 fn create_square_with_hole(
1207 x: f64,
1208 y: f64,
1209 size: f64,
1210 hole_x: f64,
1211 hole_y: f64,
1212 hole_size: f64,
1213 ) -> Result<Polygon> {
1214 let exterior_coords = vec![
1215 Coordinate::new_2d(x, y),
1216 Coordinate::new_2d(x + size, y),
1217 Coordinate::new_2d(x + size, y + size),
1218 Coordinate::new_2d(x, y + size),
1219 Coordinate::new_2d(x, y),
1220 ];
1221 let hole_coords = vec![
1222 Coordinate::new_2d(hole_x, hole_y),
1223 Coordinate::new_2d(hole_x + hole_size, hole_y),
1224 Coordinate::new_2d(hole_x + hole_size, hole_y + hole_size),
1225 Coordinate::new_2d(hole_x, hole_y + hole_size),
1226 Coordinate::new_2d(hole_x, hole_y),
1227 ];
1228
1229 let exterior = LineString::new(exterior_coords).map_err(AlgorithmError::Core)?;
1230 let hole = LineString::new(hole_coords).map_err(AlgorithmError::Core)?;
1231
1232 Polygon::new(exterior, vec![hole]).map_err(AlgorithmError::Core)
1233 }
1234
1235 #[test]
1238 fn test_difference_poly1_with_hole_disjoint() {
1239 let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1241 let poly2 = create_square(30.0, 30.0, 5.0);
1242
1243 assert!(poly1.is_ok() && poly2.is_ok());
1244 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1245 let result = difference_polygon(&p1, &p2);
1246 assert!(result.is_ok());
1247 if let Ok(polys) = result {
1248 assert_eq!(polys.len(), 1);
1250 assert_eq!(polys[0].interiors.len(), 1);
1251 }
1252 }
1253 }
1254
1255 #[test]
1256 fn test_difference_poly1_with_hole_contained_subtract() {
1257 let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1259 let poly2 = create_square(12.0, 12.0, 3.0);
1260
1261 assert!(poly1.is_ok() && poly2.is_ok());
1262 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1263 let result = difference_polygon(&p1, &p2);
1264 assert!(result.is_ok());
1265 if let Ok(polys) = result {
1266 assert_eq!(polys.len(), 1);
1268 assert_eq!(polys[0].interiors.len(), 2);
1269 }
1270 }
1271 }
1272
1273 #[test]
1274 fn test_difference_subtract_poly_with_hole() {
1275 let poly1 = create_square(0.0, 0.0, 20.0);
1277 let poly2 = create_square_with_hole(2.0, 2.0, 16.0, 6.0, 6.0, 4.0);
1278
1279 assert!(poly1.is_ok() && poly2.is_ok());
1280 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1281 let result = difference_polygon(&p1, &p2);
1282 assert!(result.is_ok());
1283 if let Ok(polys) = result {
1284 assert!(!polys.is_empty());
1288 }
1289 }
1290 }
1291
1292 #[test]
1293 fn test_difference_poly1_inside_hole_of_poly2() {
1294 let exterior_coords = vec![
1296 Coordinate::new_2d(0.0, 0.0),
1297 Coordinate::new_2d(30.0, 0.0),
1298 Coordinate::new_2d(30.0, 30.0),
1299 Coordinate::new_2d(0.0, 30.0),
1300 Coordinate::new_2d(0.0, 0.0),
1301 ];
1302 let hole_coords = vec![
1303 Coordinate::new_2d(5.0, 5.0),
1304 Coordinate::new_2d(25.0, 5.0),
1305 Coordinate::new_2d(25.0, 25.0),
1306 Coordinate::new_2d(5.0, 25.0),
1307 Coordinate::new_2d(5.0, 5.0),
1308 ];
1309
1310 let exterior = LineString::new(exterior_coords);
1311 let hole = LineString::new(hole_coords);
1312
1313 assert!(exterior.is_ok() && hole.is_ok());
1314 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1315 let poly2 = Polygon::new(ext, vec![h]);
1316 let poly1 = create_square(10.0, 10.0, 5.0);
1317
1318 assert!(poly1.is_ok() && poly2.is_ok());
1319 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1320 let result = difference_polygon(&p1, &p2);
1321 assert!(result.is_ok());
1322 if let Ok(polys) = result {
1323 assert_eq!(polys.len(), 1);
1325 }
1326 }
1327 }
1328 }
1329
1330 #[test]
1331 fn test_clip_to_box_with_hole_inside() {
1332 let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1334 assert!(poly.is_ok());
1335
1336 if let Ok(p) = poly {
1337 let result = clip_to_box(&p, 0.0, 0.0, 25.0, 25.0);
1338 assert!(result.is_ok());
1339 if let Ok(polys) = result {
1340 assert_eq!(polys.len(), 1);
1342 assert_eq!(polys[0].interiors.len(), 1);
1343 }
1344 }
1345 }
1346
1347 #[test]
1348 fn test_clip_to_box_hole_outside() {
1349 let exterior_coords = vec![
1351 Coordinate::new_2d(0.0, 0.0),
1352 Coordinate::new_2d(20.0, 0.0),
1353 Coordinate::new_2d(20.0, 20.0),
1354 Coordinate::new_2d(0.0, 20.0),
1355 Coordinate::new_2d(0.0, 0.0),
1356 ];
1357 let hole_coords = vec![
1358 Coordinate::new_2d(15.0, 15.0),
1359 Coordinate::new_2d(18.0, 15.0),
1360 Coordinate::new_2d(18.0, 18.0),
1361 Coordinate::new_2d(15.0, 18.0),
1362 Coordinate::new_2d(15.0, 15.0),
1363 ];
1364
1365 let exterior = LineString::new(exterior_coords);
1366 let hole = LineString::new(hole_coords);
1367
1368 assert!(exterior.is_ok() && hole.is_ok());
1369 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1370 let poly = Polygon::new(ext, vec![h]);
1371 assert!(poly.is_ok());
1372
1373 if let Ok(p) = poly {
1374 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1376 assert!(result.is_ok());
1377 if let Ok(polys) = result {
1378 assert_eq!(polys.len(), 1);
1380 assert_eq!(polys[0].interiors.len(), 0);
1381 }
1382 }
1383 }
1384 }
1385
1386 #[test]
1387 fn test_clip_to_box_hole_partial() {
1388 let exterior_coords = vec![
1390 Coordinate::new_2d(0.0, 0.0),
1391 Coordinate::new_2d(20.0, 0.0),
1392 Coordinate::new_2d(20.0, 20.0),
1393 Coordinate::new_2d(0.0, 20.0),
1394 Coordinate::new_2d(0.0, 0.0),
1395 ];
1396 let hole_coords = vec![
1397 Coordinate::new_2d(8.0, 8.0),
1398 Coordinate::new_2d(12.0, 8.0),
1399 Coordinate::new_2d(12.0, 12.0),
1400 Coordinate::new_2d(8.0, 12.0),
1401 Coordinate::new_2d(8.0, 8.0),
1402 ];
1403
1404 let exterior = LineString::new(exterior_coords);
1405 let hole = LineString::new(hole_coords);
1406
1407 assert!(exterior.is_ok() && hole.is_ok());
1408 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1409 let poly = Polygon::new(ext, vec![h]);
1410 assert!(poly.is_ok());
1411
1412 if let Ok(p) = poly {
1413 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1415 assert!(result.is_ok());
1416 }
1418 }
1419 }
1420
1421 #[test]
1422 fn test_validate_polygon_topology_valid() {
1423 let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1424 assert!(poly.is_ok());
1425
1426 if let Ok(p) = poly {
1427 let result = validate_polygon_topology(&p);
1428 assert!(result.is_ok());
1429 if let Ok(valid) = result {
1430 assert!(valid);
1431 }
1432 }
1433 }
1434
1435 #[test]
1436 fn test_validate_polygon_topology_hole_outside() {
1437 let exterior_coords = vec![
1439 Coordinate::new_2d(0.0, 0.0),
1440 Coordinate::new_2d(10.0, 0.0),
1441 Coordinate::new_2d(10.0, 10.0),
1442 Coordinate::new_2d(0.0, 10.0),
1443 Coordinate::new_2d(0.0, 0.0),
1444 ];
1445 let hole_coords = vec![
1446 Coordinate::new_2d(20.0, 20.0),
1447 Coordinate::new_2d(25.0, 20.0),
1448 Coordinate::new_2d(25.0, 25.0),
1449 Coordinate::new_2d(20.0, 25.0),
1450 Coordinate::new_2d(20.0, 20.0),
1451 ];
1452
1453 let exterior = LineString::new(exterior_coords);
1454 let hole = LineString::new(hole_coords);
1455
1456 assert!(exterior.is_ok() && hole.is_ok());
1457 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1458 let poly = Polygon {
1461 exterior: ext,
1462 interiors: vec![h],
1463 };
1464
1465 let result = validate_polygon_topology(&poly);
1466 assert!(result.is_ok());
1467 if let Ok(valid) = result {
1468 assert!(!valid);
1470 }
1471 }
1472 }
1473
1474 #[test]
1475 fn test_merge_adjacent_holes_no_overlap() {
1476 let hole1_coords = vec![
1477 Coordinate::new_2d(2.0, 2.0),
1478 Coordinate::new_2d(4.0, 2.0),
1479 Coordinate::new_2d(4.0, 4.0),
1480 Coordinate::new_2d(2.0, 4.0),
1481 Coordinate::new_2d(2.0, 2.0),
1482 ];
1483 let hole2_coords = vec![
1484 Coordinate::new_2d(6.0, 6.0),
1485 Coordinate::new_2d(8.0, 6.0),
1486 Coordinate::new_2d(8.0, 8.0),
1487 Coordinate::new_2d(6.0, 8.0),
1488 Coordinate::new_2d(6.0, 6.0),
1489 ];
1490
1491 let hole1 = LineString::new(hole1_coords);
1492 let hole2 = LineString::new(hole2_coords);
1493
1494 assert!(hole1.is_ok() && hole2.is_ok());
1495 if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
1496 let result = merge_adjacent_holes(&[h1, h2]);
1497 assert!(result.is_ok());
1498 if let Ok(merged) = result {
1499 assert_eq!(merged.len(), 2);
1501 }
1502 }
1503 }
1504
1505 #[test]
1506 fn test_merge_adjacent_holes_with_overlap() {
1507 let hole1_coords = vec![
1508 Coordinate::new_2d(2.0, 2.0),
1509 Coordinate::new_2d(6.0, 2.0),
1510 Coordinate::new_2d(6.0, 6.0),
1511 Coordinate::new_2d(2.0, 6.0),
1512 Coordinate::new_2d(2.0, 2.0),
1513 ];
1514 let hole2_coords = vec![
1515 Coordinate::new_2d(4.0, 4.0),
1516 Coordinate::new_2d(8.0, 4.0),
1517 Coordinate::new_2d(8.0, 8.0),
1518 Coordinate::new_2d(4.0, 8.0),
1519 Coordinate::new_2d(4.0, 4.0),
1520 ];
1521
1522 let hole1 = LineString::new(hole1_coords);
1523 let hole2 = LineString::new(hole2_coords);
1524
1525 assert!(hole1.is_ok() && hole2.is_ok());
1526 if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
1527 let result = merge_adjacent_holes(&[h1, h2]);
1528 assert!(result.is_ok());
1529 if let Ok(merged) = result {
1530 assert_eq!(merged.len(), 1);
1532 }
1533 }
1534 }
1535
1536 #[test]
1537 fn test_point_in_ring() {
1538 let ring = vec![
1539 Coordinate::new_2d(0.0, 0.0),
1540 Coordinate::new_2d(10.0, 0.0),
1541 Coordinate::new_2d(10.0, 10.0),
1542 Coordinate::new_2d(0.0, 10.0),
1543 Coordinate::new_2d(0.0, 0.0),
1544 ];
1545
1546 let inside = point_in_ring(&Coordinate::new_2d(5.0, 5.0), &ring);
1548 assert!(inside);
1549
1550 let outside = point_in_ring(&Coordinate::new_2d(15.0, 15.0), &ring);
1552 assert!(!outside);
1553
1554 let on_edge = point_in_ring(&Coordinate::new_2d(5.0, 0.0), &ring);
1556 let _ = on_edge;
1558 }
1559
1560 #[test]
1561 fn test_is_ring_inside_ring() {
1562 let outer = vec![
1563 Coordinate::new_2d(0.0, 0.0),
1564 Coordinate::new_2d(20.0, 0.0),
1565 Coordinate::new_2d(20.0, 20.0),
1566 Coordinate::new_2d(0.0, 20.0),
1567 Coordinate::new_2d(0.0, 0.0),
1568 ];
1569
1570 let inner = vec![
1571 Coordinate::new_2d(5.0, 5.0),
1572 Coordinate::new_2d(15.0, 5.0),
1573 Coordinate::new_2d(15.0, 15.0),
1574 Coordinate::new_2d(5.0, 15.0),
1575 Coordinate::new_2d(5.0, 5.0),
1576 ];
1577
1578 let outside = vec![
1579 Coordinate::new_2d(30.0, 30.0),
1580 Coordinate::new_2d(40.0, 30.0),
1581 Coordinate::new_2d(40.0, 40.0),
1582 Coordinate::new_2d(30.0, 40.0),
1583 Coordinate::new_2d(30.0, 30.0),
1584 ];
1585
1586 assert!(is_ring_inside_ring(&inner, &outer));
1587 assert!(!is_ring_inside_ring(&outside, &outer));
1588 }
1589
1590 #[test]
1591 fn test_is_ring_inside_ring_concave() {
1592 let outer = vec![
1595 Coordinate::new_2d(0.0, 0.0),
1596 Coordinate::new_2d(10.0, 0.0),
1597 Coordinate::new_2d(10.0, 3.0),
1598 Coordinate::new_2d(3.0, 3.0),
1599 Coordinate::new_2d(3.0, 7.0),
1600 Coordinate::new_2d(10.0, 7.0),
1601 Coordinate::new_2d(10.0, 10.0),
1602 Coordinate::new_2d(0.0, 10.0),
1603 Coordinate::new_2d(0.0, 0.0),
1604 ];
1605
1606 let inner = vec![
1612 Coordinate::new_2d(1.0, 1.0),
1613 Coordinate::new_2d(9.0, 1.0),
1614 Coordinate::new_2d(9.0, 2.0),
1615 Coordinate::new_2d(2.0, 2.0),
1616 Coordinate::new_2d(2.0, 8.0),
1617 Coordinate::new_2d(9.0, 8.0),
1618 Coordinate::new_2d(9.0, 9.0),
1619 Coordinate::new_2d(1.0, 9.0),
1620 Coordinate::new_2d(1.0, 1.0),
1621 ];
1622
1623 let centroid = compute_ring_centroid(&inner);
1626 assert!(
1627 !point_in_ring(¢roid, &outer),
1628 "test premise: inner centroid must fall in the outer's notch"
1629 );
1630
1631 assert!(
1633 is_ring_inside_ring(&inner, &outer),
1634 "concave inner ring is genuinely inside the concave outer ring"
1635 );
1636
1637 let straddling = vec![
1641 Coordinate::new_2d(1.0, 4.0),
1642 Coordinate::new_2d(6.0, 4.0), Coordinate::new_2d(6.0, 6.0),
1644 Coordinate::new_2d(1.0, 6.0), Coordinate::new_2d(1.0, 4.0),
1646 ];
1647 assert!(
1648 !is_ring_inside_ring(&straddling, &outer),
1649 "a ring crossing the outer boundary is not contained"
1650 );
1651 }
1652
1653 #[test]
1654 fn test_clip_ring_to_box() {
1655 let ring = vec![
1656 Coordinate::new_2d(0.0, 0.0),
1657 Coordinate::new_2d(20.0, 0.0),
1658 Coordinate::new_2d(20.0, 20.0),
1659 Coordinate::new_2d(0.0, 20.0),
1660 Coordinate::new_2d(0.0, 0.0),
1661 ];
1662
1663 let result = clip_ring_to_box(&ring, 5.0, 5.0, 15.0, 15.0);
1664 assert!(result.is_ok());
1665 if let Ok(clipped) = result {
1666 assert!(clipped.len() >= 4);
1668 }
1669 }
1670
1671 #[test]
1672 fn test_compute_ring_centroid() {
1673 let ring = vec![
1674 Coordinate::new_2d(0.0, 0.0),
1675 Coordinate::new_2d(10.0, 0.0),
1676 Coordinate::new_2d(10.0, 10.0),
1677 Coordinate::new_2d(0.0, 10.0),
1678 Coordinate::new_2d(0.0, 0.0),
1679 ];
1680
1681 let centroid = compute_ring_centroid(&ring);
1682 assert!((centroid.x - 4.0).abs() < 1.0); assert!((centroid.y - 4.0).abs() < 1.0);
1685 }
1686
1687 #[test]
1688 fn test_difference_preserves_unaffected_holes() {
1689 let exterior_coords = vec![
1691 Coordinate::new_2d(0.0, 0.0),
1692 Coordinate::new_2d(30.0, 0.0),
1693 Coordinate::new_2d(30.0, 30.0),
1694 Coordinate::new_2d(0.0, 30.0),
1695 Coordinate::new_2d(0.0, 0.0),
1696 ];
1697 let hole1_coords = vec![
1698 Coordinate::new_2d(2.0, 2.0),
1699 Coordinate::new_2d(5.0, 2.0),
1700 Coordinate::new_2d(5.0, 5.0),
1701 Coordinate::new_2d(2.0, 5.0),
1702 Coordinate::new_2d(2.0, 2.0),
1703 ];
1704 let hole2_coords = vec![
1705 Coordinate::new_2d(20.0, 20.0),
1706 Coordinate::new_2d(25.0, 20.0),
1707 Coordinate::new_2d(25.0, 25.0),
1708 Coordinate::new_2d(20.0, 25.0),
1709 Coordinate::new_2d(20.0, 20.0),
1710 ];
1711
1712 let exterior = LineString::new(exterior_coords);
1713 let hole1 = LineString::new(hole1_coords);
1714 let hole2 = LineString::new(hole2_coords);
1715
1716 assert!(exterior.is_ok() && hole1.is_ok() && hole2.is_ok());
1717 if let (Ok(ext), Ok(h1), Ok(h2)) = (exterior, hole1, hole2) {
1718 let poly1 = Polygon::new(ext, vec![h1, h2]);
1719 let poly2 = create_square(10.0, 10.0, 5.0);
1720
1721 assert!(poly1.is_ok() && poly2.is_ok());
1722 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1723 let result = difference_polygon(&p1, &p2);
1724 assert!(result.is_ok());
1725 if let Ok(polys) = result {
1726 assert_eq!(polys.len(), 1);
1728 assert!(polys[0].interiors.len() >= 2);
1730 }
1731 }
1732 }
1733 }
1734}