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
131fn compute_ring_centroid(coords: &[Coordinate]) -> Coordinate {
133 crate::vector::clipping::compute_ring_centroid(coords)
134}
135
136pub fn difference_polygons(base: &[Polygon], subtract: &[Polygon]) -> Result<Vec<Polygon>> {
153 if base.is_empty() {
154 return Ok(vec![]);
155 }
156
157 if subtract.is_empty() {
158 return Ok(base.to_vec());
159 }
160
161 for (i, poly) in base.iter().enumerate() {
163 if poly.exterior.coords.len() < 4 {
164 return Err(AlgorithmError::InsufficientData {
165 operation: "difference_polygons",
166 message: format!(
167 "base polygon {} exterior must have at least 4 coordinates",
168 i
169 ),
170 });
171 }
172 }
173
174 for (i, poly) in subtract.iter().enumerate() {
175 if poly.exterior.coords.len() < 4 {
176 return Err(AlgorithmError::InsufficientData {
177 operation: "difference_polygons",
178 message: format!(
179 "subtract polygon {} exterior must have at least 4 coordinates",
180 i
181 ),
182 });
183 }
184 }
185
186 let mut result = base.to_vec();
188
189 for sub_poly in subtract {
190 let mut new_result = Vec::new();
191
192 for base_poly in &result {
193 let diff = difference_polygon(base_poly, sub_poly)?;
194 new_result.extend(diff);
195 }
196
197 result = new_result;
198 }
199
200 Ok(result)
201}
202
203pub fn symmetric_difference(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
221 let diff1 = difference_polygon(poly1, poly2)?;
223
224 let diff2 = difference_polygon(poly2, poly1)?;
226
227 let mut result = diff1;
229 result.extend(diff2);
230
231 Ok(result)
232}
233
234pub fn clip_to_box(
265 polygon: &Polygon,
266 min_x: f64,
267 min_y: f64,
268 max_x: f64,
269 max_y: f64,
270) -> Result<Vec<Polygon>> {
271 if polygon.exterior.coords.len() < 4 {
272 return Err(AlgorithmError::InsufficientData {
273 operation: "clip_to_box",
274 message: "polygon exterior must have at least 4 coordinates".to_string(),
275 });
276 }
277
278 if min_x >= max_x || min_y >= max_y {
279 return Err(AlgorithmError::InvalidParameter {
280 parameter: "bounding box",
281 message: "invalid bounding box dimensions".to_string(),
282 });
283 }
284
285 if let Some((poly_min_x, poly_min_y, poly_max_x, poly_max_y)) = polygon.bounds() {
287 if poly_max_x < min_x || poly_min_x > max_x || poly_max_y < min_y || poly_min_y > max_y {
288 return Ok(vec![]);
290 }
291
292 if poly_min_x >= min_x && poly_max_x <= max_x && poly_min_y >= min_y && poly_max_y <= max_y
293 {
294 return Ok(vec![polygon.clone()]);
296 }
297 }
298
299 let mut clipped_coords = polygon.exterior.coords.clone();
301
302 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 {
309 return Ok(vec![]);
311 }
312
313 if let (Some(first), Some(last)) = (clipped_coords.first(), clipped_coords.last()) {
315 if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
316 clipped_coords.push(*first);
317 }
318 }
319
320 let clipped_exterior = LineString::new(clipped_coords).map_err(AlgorithmError::Core)?;
321
322 let clipped_interiors = clip_interior_rings_to_box(
324 &polygon.interiors,
325 min_x,
326 min_y,
327 max_x,
328 max_y,
329 &clipped_exterior,
330 )?;
331
332 let result = Polygon::new(clipped_exterior, clipped_interiors).map_err(AlgorithmError::Core)?;
333
334 Ok(vec![result])
335}
336
337fn clip_interior_rings_to_box(
357 interiors: &[LineString],
358 min_x: f64,
359 min_y: f64,
360 max_x: f64,
361 max_y: f64,
362 clipped_exterior: &LineString,
363) -> Result<Vec<LineString>> {
364 let mut result = Vec::new();
365
366 for hole in interiors {
367 if hole.coords.len() < 4 {
368 continue;
369 }
370
371 let hole_bounds = hole.bounds();
373 if let Some((hole_min_x, hole_min_y, hole_max_x, hole_max_y)) = hole_bounds {
374 if hole_max_x < min_x || hole_min_x > max_x || hole_max_y < min_y || hole_min_y > max_y
376 {
377 continue;
379 }
380
381 if hole_min_x >= min_x
383 && hole_max_x <= max_x
384 && hole_min_y >= min_y
385 && hole_max_y <= max_y
386 {
387 if is_ring_inside_ring(&hole.coords, &clipped_exterior.coords) {
389 result.push(hole.clone());
390 }
391 continue;
392 }
393 }
394
395 let clipped_hole = clip_ring_to_box(&hole.coords, min_x, min_y, max_x, max_y)?;
397
398 if clipped_hole.len() >= 4 {
399 if is_ring_inside_ring(&clipped_hole, &clipped_exterior.coords) {
401 let mut closed_hole = clipped_hole;
403 if let (Some(first), Some(last)) = (closed_hole.first(), closed_hole.last()) {
404 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
405 closed_hole.push(*first);
406 }
407 }
408
409 if closed_hole.len() >= 4 {
410 if let Ok(hole_ring) = LineString::new(closed_hole) {
411 result.push(hole_ring);
412 }
413 }
414 }
415 }
416 }
417
418 Ok(result)
419}
420
421fn clip_ring_to_box(
435 coords: &[Coordinate],
436 min_x: f64,
437 min_y: f64,
438 max_x: f64,
439 max_y: f64,
440) -> Result<Vec<Coordinate>> {
441 let mut clipped = coords.to_vec();
442
443 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)
450}
451
452fn is_ring_inside_ring(inner: &[Coordinate], outer: &[Coordinate]) -> bool {
466 if inner.is_empty() || outer.is_empty() {
467 return false;
468 }
469
470 let centroid = compute_ring_centroid(inner);
472
473 point_in_ring(¢roid, outer)
475}
476
477pub fn validate_polygon_topology(polygon: &Polygon) -> Result<bool> {
497 if polygon.exterior.coords.len() < 4 {
499 return Ok(false);
500 }
501
502 if let (Some(first), Some(last)) = (
504 polygon.exterior.coords.first(),
505 polygon.exterior.coords.last(),
506 ) {
507 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
508 return Ok(false);
509 }
510 }
511
512 for hole in &polygon.interiors {
514 if hole.coords.len() < 4 {
516 return Ok(false);
517 }
518
519 if let (Some(first), Some(last)) = (hole.coords.first(), hole.coords.last()) {
521 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
522 return Ok(false);
523 }
524 }
525
526 if !is_ring_inside_ring(&hole.coords, &polygon.exterior.coords) {
528 return Ok(false);
529 }
530 }
531
532 for i in 0..polygon.interiors.len() {
534 for j in (i + 1)..polygon.interiors.len() {
535 if rings_overlap(&polygon.interiors[i].coords, &polygon.interiors[j].coords)? {
536 return Ok(false);
537 }
538 }
539 }
540
541 Ok(true)
542}
543
544fn rings_overlap(ring1: &[Coordinate], ring2: &[Coordinate]) -> Result<bool> {
549 for coord in ring1 {
551 if point_in_ring(coord, ring2) {
552 return Ok(true);
553 }
554 }
555
556 for coord in ring2 {
557 if point_in_ring(coord, ring1) {
558 return Ok(true);
559 }
560 }
561
562 if ring1.len() < 2 || ring2.len() < 2 {
565 return Ok(false);
566 }
567
568 for i in 0..(ring1.len() - 1) {
569 for j in 0..(ring2.len() - 1) {
570 if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
571 return Ok(true);
572 }
573 }
574 }
575
576 Ok(false)
577}
578
579fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
581 let d1x = p2.x - p1.x;
582 let d1y = p2.y - p1.y;
583 let d2x = p4.x - p3.x;
584 let d2y = p4.y - p3.y;
585
586 let cross = d1x * d2y - d1y * d2x;
587
588 if cross.abs() < EPSILON {
589 return false;
591 }
592
593 let dx = p3.x - p1.x;
594 let dy = p3.y - p1.y;
595
596 let t = (dx * d2y - dy * d2x) / cross;
597 let u = (dx * d1y - dy * d1x) / cross;
598
599 t > EPSILON && t < (1.0 - EPSILON) && u > EPSILON && u < (1.0 - EPSILON)
601}
602
603pub fn merge_adjacent_holes(holes: &[LineString]) -> Result<Vec<LineString>> {
616 if holes.is_empty() {
617 return Ok(vec![]);
618 }
619
620 if holes.len() == 1 {
621 return Ok(holes.to_vec());
622 }
623
624 let mut result = Vec::new();
627 let mut merged = vec![false; holes.len()];
628
629 for i in 0..holes.len() {
630 if merged[i] {
631 continue;
632 }
633
634 let mut current_hole = holes[i].coords.clone();
635
636 for j in (i + 1)..holes.len() {
637 if merged[j] {
638 continue;
639 }
640
641 if rings_overlap(¤t_hole, &holes[j].coords)? {
642 if compute_ring_area(&holes[j].coords).abs()
645 > compute_ring_area(¤t_hole).abs()
646 {
647 current_hole = holes[j].coords.clone();
648 }
649 merged[j] = true;
650 }
651 }
652
653 if current_hole.len() >= 4 {
654 if let Ok(ring) = LineString::new(current_hole) {
655 result.push(ring);
656 }
657 }
658 }
659
660 Ok(result)
661}
662
663fn clip_against_edge(
665 coords: &[Coordinate],
666 edge_value: f64,
667 is_vertical: bool,
668 is_max: bool,
669) -> Result<Vec<Coordinate>> {
670 if coords.is_empty() {
671 return Ok(vec![]);
672 }
673
674 let mut result = Vec::new();
675
676 for i in 0..coords.len() {
677 let current = &coords[i];
678 let next = &coords[(i + 1) % coords.len()];
679
680 let current_inside = is_inside(current, edge_value, is_vertical, is_max);
681 let next_inside = is_inside(next, edge_value, is_vertical, is_max);
682
683 if current_inside {
684 result.push(*current);
685 }
686
687 if current_inside != next_inside {
688 if let Some(intersection) = compute_intersection(current, next, edge_value, is_vertical)
690 {
691 result.push(intersection);
692 }
693 }
694 }
695
696 Ok(result)
697}
698
699fn is_inside(point: &Coordinate, edge_value: f64, is_vertical: bool, is_max: bool) -> bool {
701 let value = if is_vertical { point.x } else { point.y };
702
703 if is_max {
704 value <= edge_value
705 } else {
706 value >= edge_value
707 }
708}
709
710fn compute_intersection(
712 p1: &Coordinate,
713 p2: &Coordinate,
714 edge_value: f64,
715 is_vertical: bool,
716) -> Option<Coordinate> {
717 if is_vertical {
718 let dx = p2.x - p1.x;
720 if dx.abs() < f64::EPSILON {
721 return None; }
723
724 let t = (edge_value - p1.x) / dx;
725 if (0.0..=1.0).contains(&t) {
726 let y = p1.y + t * (p2.y - p1.y);
727 Some(Coordinate::new_2d(edge_value, y))
728 } else {
729 None
730 }
731 } else {
732 let dy = p2.y - p1.y;
734 if dy.abs() < f64::EPSILON {
735 return None; }
737
738 let t = (edge_value - p1.y) / dy;
739 if (0.0..=1.0).contains(&t) {
740 let x = p1.x + t * (p2.x - p1.x);
741 Some(Coordinate::new_2d(x, edge_value))
742 } else {
743 None
744 }
745 }
746}
747
748pub fn erase_small_holes(polygon: &Polygon, min_area: f64) -> Result<Polygon> {
766 if polygon.exterior.coords.len() < 4 {
767 return Err(AlgorithmError::InsufficientData {
768 operation: "erase_small_holes",
769 message: "polygon exterior must have at least 4 coordinates".to_string(),
770 });
771 }
772
773 if min_area < 0.0 {
774 return Err(AlgorithmError::InvalidParameter {
775 parameter: "min_area",
776 message: "min_area must be non-negative".to_string(),
777 });
778 }
779
780 let mut kept_holes = Vec::new();
781
782 for hole in &polygon.interiors {
783 let area = compute_ring_area(&hole.coords).abs();
784 if area >= min_area {
785 kept_holes.push(hole.clone());
786 }
787 }
788
789 Polygon::new(polygon.exterior.clone(), kept_holes).map_err(AlgorithmError::Core)
790}
791
792fn compute_ring_area(coords: &[Coordinate]) -> f64 {
794 if coords.len() < 3 {
795 return 0.0;
796 }
797
798 let mut area = 0.0;
799 let n = coords.len();
800
801 for i in 0..n {
802 let j = (i + 1) % n;
803 area += coords[i].x * coords[j].y;
804 area -= coords[j].x * coords[i].y;
805 }
806
807 area / 2.0
808}
809
810pub fn difference_polygon_pooled(
859 subject: &Polygon,
860 clip: &Polygon,
861) -> Result<PoolGuard<'static, Polygon>> {
862 let results = difference_polygon(subject, clip)?;
863
864 if let Some(result) = results.first() {
866 let mut poly = get_pooled_polygon();
867 poly.exterior = result.exterior.clone();
868 poly.interiors = result.interiors.clone();
869 Ok(poly)
870 } else {
871 Err(AlgorithmError::InsufficientData {
872 operation: "difference_polygon_pooled",
873 message: "difference resulted in no polygons".to_string(),
874 })
875 }
876}
877
878pub fn difference_polygons_pooled(
916 base: &[Polygon],
917 subtract: &[Polygon],
918) -> Result<PoolGuard<'static, Polygon>> {
919 let results = difference_polygons(base, subtract)?;
920
921 if let Some(result) = results.first() {
923 let mut poly = get_pooled_polygon();
924 poly.exterior = result.exterior.clone();
925 poly.interiors = result.interiors.clone();
926 Ok(poly)
927 } else {
928 Err(AlgorithmError::InsufficientData {
929 operation: "difference_polygons_pooled",
930 message: "difference resulted in no polygons".to_string(),
931 })
932 }
933}
934
935pub fn symmetric_difference_pooled(
980 poly1: &Polygon,
981 poly2: &Polygon,
982) -> Result<Vec<PoolGuard<'static, Polygon>>> {
983 let results = symmetric_difference(poly1, poly2)?;
984
985 let mut pooled_results = Vec::new();
987 for result in results {
988 let mut poly = get_pooled_polygon();
989 poly.exterior = result.exterior;
990 poly.interiors = result.interiors;
991 pooled_results.push(poly);
992 }
993
994 Ok(pooled_results)
995}
996
997#[cfg(test)]
998mod tests {
999 use super::*;
1000
1001 fn create_square(x: f64, y: f64, size: f64) -> Result<Polygon> {
1002 let coords = vec![
1003 Coordinate::new_2d(x, y),
1004 Coordinate::new_2d(x + size, y),
1005 Coordinate::new_2d(x + size, y + size),
1006 Coordinate::new_2d(x, y + size),
1007 Coordinate::new_2d(x, y),
1008 ];
1009 let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
1010 Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
1011 }
1012
1013 #[test]
1014 fn test_difference_polygon_disjoint() {
1015 let poly1 = create_square(0.0, 0.0, 5.0);
1016 let poly2 = create_square(10.0, 10.0, 5.0);
1017
1018 assert!(poly1.is_ok() && poly2.is_ok());
1019 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1020 let result = difference_polygon(&p1, &p2);
1021 assert!(result.is_ok());
1022 if let Ok(polys) = result {
1023 assert_eq!(polys.len(), 1); }
1025 }
1026 }
1027
1028 #[test]
1029 fn test_difference_polygon_contained() {
1030 let poly1 = create_square(0.0, 0.0, 10.0);
1031 let poly2 = create_square(2.0, 2.0, 3.0);
1032
1033 assert!(poly1.is_ok() && poly2.is_ok());
1034 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1035 let result = difference_polygon(&p1, &p2);
1036 assert!(result.is_ok());
1037 if let Ok(polys) = result {
1038 assert_eq!(polys.len(), 1);
1040 assert_eq!(polys[0].interiors.len(), 1);
1041 }
1042 }
1043 }
1044
1045 #[test]
1046 fn test_difference_polygon_completely_subtracted() {
1047 let poly1 = create_square(2.0, 2.0, 3.0);
1048 let poly2 = create_square(0.0, 0.0, 10.0);
1049
1050 assert!(poly1.is_ok() && poly2.is_ok());
1051 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1052 let result = difference_polygon(&p1, &p2);
1053 assert!(result.is_ok());
1054 if let Ok(polys) = result {
1055 assert_eq!(polys.len(), 0);
1057 }
1058 }
1059 }
1060
1061 #[test]
1062 fn test_difference_polygons_multiple() {
1063 let base = vec![create_square(0.0, 0.0, 10.0).ok()];
1064 let subtract = vec![
1065 create_square(2.0, 2.0, 2.0).ok(),
1066 create_square(6.0, 6.0, 2.0).ok(),
1067 ];
1068
1069 let base_polys: Vec<_> = base.into_iter().flatten().collect();
1070 let subtract_polys: Vec<_> = subtract.into_iter().flatten().collect();
1071
1072 let result = difference_polygons(&base_polys, &subtract_polys);
1073 assert!(result.is_ok());
1074 }
1075
1076 #[test]
1077 fn test_symmetric_difference() {
1078 let poly1 = create_square(0.0, 0.0, 5.0);
1079 let poly2 = create_square(3.0, 0.0, 5.0);
1080
1081 assert!(poly1.is_ok() && poly2.is_ok());
1082 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1083 let result = symmetric_difference(&p1, &p2);
1084 assert!(result.is_ok());
1085 if let Ok(polys) = result {
1087 assert!(!polys.is_empty());
1088 }
1089 }
1090 }
1091
1092 #[test]
1093 fn test_clip_to_box_inside() {
1094 let poly = create_square(2.0, 2.0, 3.0);
1095 assert!(poly.is_ok());
1096
1097 if let Ok(p) = poly {
1098 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1099 assert!(result.is_ok());
1100 if let Ok(polys) = result {
1101 assert_eq!(polys.len(), 1);
1103 }
1104 }
1105 }
1106
1107 #[test]
1108 fn test_clip_to_box_outside() {
1109 let poly = create_square(15.0, 15.0, 3.0);
1110 assert!(poly.is_ok());
1111
1112 if let Ok(p) = poly {
1113 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1114 assert!(result.is_ok());
1115 if let Ok(polys) = result {
1116 assert_eq!(polys.len(), 0);
1118 }
1119 }
1120 }
1121
1122 #[test]
1123 fn test_clip_to_box_partial() {
1124 let poly = create_square(5.0, 5.0, 10.0);
1125 assert!(poly.is_ok());
1126
1127 if let Ok(p) = poly {
1128 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1129 assert!(result.is_ok());
1130 if let Ok(polys) = result {
1131 assert_eq!(polys.len(), 1);
1133 }
1134 }
1135 }
1136
1137 #[test]
1138 fn test_erase_small_holes() {
1139 let exterior_coords = vec![
1140 Coordinate::new_2d(0.0, 0.0),
1141 Coordinate::new_2d(20.0, 0.0),
1142 Coordinate::new_2d(20.0, 20.0),
1143 Coordinate::new_2d(0.0, 20.0),
1144 Coordinate::new_2d(0.0, 0.0),
1145 ];
1146
1147 let small_hole_coords = vec![
1148 Coordinate::new_2d(2.0, 2.0),
1149 Coordinate::new_2d(3.0, 2.0),
1150 Coordinate::new_2d(3.0, 3.0),
1151 Coordinate::new_2d(2.0, 3.0),
1152 Coordinate::new_2d(2.0, 2.0),
1153 ];
1154
1155 let large_hole_coords = vec![
1156 Coordinate::new_2d(10.0, 10.0),
1157 Coordinate::new_2d(15.0, 10.0),
1158 Coordinate::new_2d(15.0, 15.0),
1159 Coordinate::new_2d(10.0, 15.0),
1160 Coordinate::new_2d(10.0, 10.0),
1161 ];
1162
1163 let exterior = LineString::new(exterior_coords);
1164 let small_hole = LineString::new(small_hole_coords);
1165 let large_hole = LineString::new(large_hole_coords);
1166
1167 assert!(exterior.is_ok() && small_hole.is_ok() && large_hole.is_ok());
1168
1169 if let (Ok(ext), Ok(sh), Ok(lh)) = (exterior, small_hole, large_hole) {
1170 let polygon = Polygon::new(ext, vec![sh, lh]);
1171 assert!(polygon.is_ok());
1172
1173 if let Ok(poly) = polygon {
1174 let result = erase_small_holes(&poly, 10.0);
1175 assert!(result.is_ok());
1176 if let Ok(cleaned) = result {
1177 assert_eq!(cleaned.interiors.len(), 1);
1179 }
1180 }
1181 }
1182 }
1183
1184 fn create_square_with_hole(
1186 x: f64,
1187 y: f64,
1188 size: f64,
1189 hole_x: f64,
1190 hole_y: f64,
1191 hole_size: f64,
1192 ) -> Result<Polygon> {
1193 let exterior_coords = vec![
1194 Coordinate::new_2d(x, y),
1195 Coordinate::new_2d(x + size, y),
1196 Coordinate::new_2d(x + size, y + size),
1197 Coordinate::new_2d(x, y + size),
1198 Coordinate::new_2d(x, y),
1199 ];
1200 let hole_coords = vec![
1201 Coordinate::new_2d(hole_x, hole_y),
1202 Coordinate::new_2d(hole_x + hole_size, hole_y),
1203 Coordinate::new_2d(hole_x + hole_size, hole_y + hole_size),
1204 Coordinate::new_2d(hole_x, hole_y + hole_size),
1205 Coordinate::new_2d(hole_x, hole_y),
1206 ];
1207
1208 let exterior = LineString::new(exterior_coords).map_err(AlgorithmError::Core)?;
1209 let hole = LineString::new(hole_coords).map_err(AlgorithmError::Core)?;
1210
1211 Polygon::new(exterior, vec![hole]).map_err(AlgorithmError::Core)
1212 }
1213
1214 #[test]
1217 fn test_difference_poly1_with_hole_disjoint() {
1218 let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1220 let poly2 = create_square(30.0, 30.0, 5.0);
1221
1222 assert!(poly1.is_ok() && poly2.is_ok());
1223 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1224 let result = difference_polygon(&p1, &p2);
1225 assert!(result.is_ok());
1226 if let Ok(polys) = result {
1227 assert_eq!(polys.len(), 1);
1229 assert_eq!(polys[0].interiors.len(), 1);
1230 }
1231 }
1232 }
1233
1234 #[test]
1235 fn test_difference_poly1_with_hole_contained_subtract() {
1236 let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1238 let poly2 = create_square(12.0, 12.0, 3.0);
1239
1240 assert!(poly1.is_ok() && poly2.is_ok());
1241 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1242 let result = difference_polygon(&p1, &p2);
1243 assert!(result.is_ok());
1244 if let Ok(polys) = result {
1245 assert_eq!(polys.len(), 1);
1247 assert_eq!(polys[0].interiors.len(), 2);
1248 }
1249 }
1250 }
1251
1252 #[test]
1253 fn test_difference_subtract_poly_with_hole() {
1254 let poly1 = create_square(0.0, 0.0, 20.0);
1256 let poly2 = create_square_with_hole(2.0, 2.0, 16.0, 6.0, 6.0, 4.0);
1257
1258 assert!(poly1.is_ok() && poly2.is_ok());
1259 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1260 let result = difference_polygon(&p1, &p2);
1261 assert!(result.is_ok());
1262 if let Ok(polys) = result {
1263 assert!(!polys.is_empty());
1267 }
1268 }
1269 }
1270
1271 #[test]
1272 fn test_difference_poly1_inside_hole_of_poly2() {
1273 let exterior_coords = vec![
1275 Coordinate::new_2d(0.0, 0.0),
1276 Coordinate::new_2d(30.0, 0.0),
1277 Coordinate::new_2d(30.0, 30.0),
1278 Coordinate::new_2d(0.0, 30.0),
1279 Coordinate::new_2d(0.0, 0.0),
1280 ];
1281 let hole_coords = vec![
1282 Coordinate::new_2d(5.0, 5.0),
1283 Coordinate::new_2d(25.0, 5.0),
1284 Coordinate::new_2d(25.0, 25.0),
1285 Coordinate::new_2d(5.0, 25.0),
1286 Coordinate::new_2d(5.0, 5.0),
1287 ];
1288
1289 let exterior = LineString::new(exterior_coords);
1290 let hole = LineString::new(hole_coords);
1291
1292 assert!(exterior.is_ok() && hole.is_ok());
1293 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1294 let poly2 = Polygon::new(ext, vec![h]);
1295 let poly1 = create_square(10.0, 10.0, 5.0);
1296
1297 assert!(poly1.is_ok() && poly2.is_ok());
1298 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1299 let result = difference_polygon(&p1, &p2);
1300 assert!(result.is_ok());
1301 if let Ok(polys) = result {
1302 assert_eq!(polys.len(), 1);
1304 }
1305 }
1306 }
1307 }
1308
1309 #[test]
1310 fn test_clip_to_box_with_hole_inside() {
1311 let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1313 assert!(poly.is_ok());
1314
1315 if let Ok(p) = poly {
1316 let result = clip_to_box(&p, 0.0, 0.0, 25.0, 25.0);
1317 assert!(result.is_ok());
1318 if let Ok(polys) = result {
1319 assert_eq!(polys.len(), 1);
1321 assert_eq!(polys[0].interiors.len(), 1);
1322 }
1323 }
1324 }
1325
1326 #[test]
1327 fn test_clip_to_box_hole_outside() {
1328 let exterior_coords = vec![
1330 Coordinate::new_2d(0.0, 0.0),
1331 Coordinate::new_2d(20.0, 0.0),
1332 Coordinate::new_2d(20.0, 20.0),
1333 Coordinate::new_2d(0.0, 20.0),
1334 Coordinate::new_2d(0.0, 0.0),
1335 ];
1336 let hole_coords = vec![
1337 Coordinate::new_2d(15.0, 15.0),
1338 Coordinate::new_2d(18.0, 15.0),
1339 Coordinate::new_2d(18.0, 18.0),
1340 Coordinate::new_2d(15.0, 18.0),
1341 Coordinate::new_2d(15.0, 15.0),
1342 ];
1343
1344 let exterior = LineString::new(exterior_coords);
1345 let hole = LineString::new(hole_coords);
1346
1347 assert!(exterior.is_ok() && hole.is_ok());
1348 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1349 let poly = Polygon::new(ext, vec![h]);
1350 assert!(poly.is_ok());
1351
1352 if let Ok(p) = poly {
1353 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1355 assert!(result.is_ok());
1356 if let Ok(polys) = result {
1357 assert_eq!(polys.len(), 1);
1359 assert_eq!(polys[0].interiors.len(), 0);
1360 }
1361 }
1362 }
1363 }
1364
1365 #[test]
1366 fn test_clip_to_box_hole_partial() {
1367 let exterior_coords = vec![
1369 Coordinate::new_2d(0.0, 0.0),
1370 Coordinate::new_2d(20.0, 0.0),
1371 Coordinate::new_2d(20.0, 20.0),
1372 Coordinate::new_2d(0.0, 20.0),
1373 Coordinate::new_2d(0.0, 0.0),
1374 ];
1375 let hole_coords = vec![
1376 Coordinate::new_2d(8.0, 8.0),
1377 Coordinate::new_2d(12.0, 8.0),
1378 Coordinate::new_2d(12.0, 12.0),
1379 Coordinate::new_2d(8.0, 12.0),
1380 Coordinate::new_2d(8.0, 8.0),
1381 ];
1382
1383 let exterior = LineString::new(exterior_coords);
1384 let hole = LineString::new(hole_coords);
1385
1386 assert!(exterior.is_ok() && hole.is_ok());
1387 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1388 let poly = Polygon::new(ext, vec![h]);
1389 assert!(poly.is_ok());
1390
1391 if let Ok(p) = poly {
1392 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1394 assert!(result.is_ok());
1395 }
1397 }
1398 }
1399
1400 #[test]
1401 fn test_validate_polygon_topology_valid() {
1402 let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1403 assert!(poly.is_ok());
1404
1405 if let Ok(p) = poly {
1406 let result = validate_polygon_topology(&p);
1407 assert!(result.is_ok());
1408 if let Ok(valid) = result {
1409 assert!(valid);
1410 }
1411 }
1412 }
1413
1414 #[test]
1415 fn test_validate_polygon_topology_hole_outside() {
1416 let exterior_coords = vec![
1418 Coordinate::new_2d(0.0, 0.0),
1419 Coordinate::new_2d(10.0, 0.0),
1420 Coordinate::new_2d(10.0, 10.0),
1421 Coordinate::new_2d(0.0, 10.0),
1422 Coordinate::new_2d(0.0, 0.0),
1423 ];
1424 let hole_coords = vec![
1425 Coordinate::new_2d(20.0, 20.0),
1426 Coordinate::new_2d(25.0, 20.0),
1427 Coordinate::new_2d(25.0, 25.0),
1428 Coordinate::new_2d(20.0, 25.0),
1429 Coordinate::new_2d(20.0, 20.0),
1430 ];
1431
1432 let exterior = LineString::new(exterior_coords);
1433 let hole = LineString::new(hole_coords);
1434
1435 assert!(exterior.is_ok() && hole.is_ok());
1436 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1437 let poly = Polygon {
1440 exterior: ext,
1441 interiors: vec![h],
1442 };
1443
1444 let result = validate_polygon_topology(&poly);
1445 assert!(result.is_ok());
1446 if let Ok(valid) = result {
1447 assert!(!valid);
1449 }
1450 }
1451 }
1452
1453 #[test]
1454 fn test_merge_adjacent_holes_no_overlap() {
1455 let hole1_coords = vec![
1456 Coordinate::new_2d(2.0, 2.0),
1457 Coordinate::new_2d(4.0, 2.0),
1458 Coordinate::new_2d(4.0, 4.0),
1459 Coordinate::new_2d(2.0, 4.0),
1460 Coordinate::new_2d(2.0, 2.0),
1461 ];
1462 let hole2_coords = vec![
1463 Coordinate::new_2d(6.0, 6.0),
1464 Coordinate::new_2d(8.0, 6.0),
1465 Coordinate::new_2d(8.0, 8.0),
1466 Coordinate::new_2d(6.0, 8.0),
1467 Coordinate::new_2d(6.0, 6.0),
1468 ];
1469
1470 let hole1 = LineString::new(hole1_coords);
1471 let hole2 = LineString::new(hole2_coords);
1472
1473 assert!(hole1.is_ok() && hole2.is_ok());
1474 if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
1475 let result = merge_adjacent_holes(&[h1, h2]);
1476 assert!(result.is_ok());
1477 if let Ok(merged) = result {
1478 assert_eq!(merged.len(), 2);
1480 }
1481 }
1482 }
1483
1484 #[test]
1485 fn test_merge_adjacent_holes_with_overlap() {
1486 let hole1_coords = vec![
1487 Coordinate::new_2d(2.0, 2.0),
1488 Coordinate::new_2d(6.0, 2.0),
1489 Coordinate::new_2d(6.0, 6.0),
1490 Coordinate::new_2d(2.0, 6.0),
1491 Coordinate::new_2d(2.0, 2.0),
1492 ];
1493 let hole2_coords = vec![
1494 Coordinate::new_2d(4.0, 4.0),
1495 Coordinate::new_2d(8.0, 4.0),
1496 Coordinate::new_2d(8.0, 8.0),
1497 Coordinate::new_2d(4.0, 8.0),
1498 Coordinate::new_2d(4.0, 4.0),
1499 ];
1500
1501 let hole1 = LineString::new(hole1_coords);
1502 let hole2 = LineString::new(hole2_coords);
1503
1504 assert!(hole1.is_ok() && hole2.is_ok());
1505 if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
1506 let result = merge_adjacent_holes(&[h1, h2]);
1507 assert!(result.is_ok());
1508 if let Ok(merged) = result {
1509 assert_eq!(merged.len(), 1);
1511 }
1512 }
1513 }
1514
1515 #[test]
1516 fn test_point_in_ring() {
1517 let ring = vec![
1518 Coordinate::new_2d(0.0, 0.0),
1519 Coordinate::new_2d(10.0, 0.0),
1520 Coordinate::new_2d(10.0, 10.0),
1521 Coordinate::new_2d(0.0, 10.0),
1522 Coordinate::new_2d(0.0, 0.0),
1523 ];
1524
1525 let inside = point_in_ring(&Coordinate::new_2d(5.0, 5.0), &ring);
1527 assert!(inside);
1528
1529 let outside = point_in_ring(&Coordinate::new_2d(15.0, 15.0), &ring);
1531 assert!(!outside);
1532
1533 let on_edge = point_in_ring(&Coordinate::new_2d(5.0, 0.0), &ring);
1535 let _ = on_edge;
1537 }
1538
1539 #[test]
1540 fn test_is_ring_inside_ring() {
1541 let outer = vec![
1542 Coordinate::new_2d(0.0, 0.0),
1543 Coordinate::new_2d(20.0, 0.0),
1544 Coordinate::new_2d(20.0, 20.0),
1545 Coordinate::new_2d(0.0, 20.0),
1546 Coordinate::new_2d(0.0, 0.0),
1547 ];
1548
1549 let inner = vec![
1550 Coordinate::new_2d(5.0, 5.0),
1551 Coordinate::new_2d(15.0, 5.0),
1552 Coordinate::new_2d(15.0, 15.0),
1553 Coordinate::new_2d(5.0, 15.0),
1554 Coordinate::new_2d(5.0, 5.0),
1555 ];
1556
1557 let outside = vec![
1558 Coordinate::new_2d(30.0, 30.0),
1559 Coordinate::new_2d(40.0, 30.0),
1560 Coordinate::new_2d(40.0, 40.0),
1561 Coordinate::new_2d(30.0, 40.0),
1562 Coordinate::new_2d(30.0, 30.0),
1563 ];
1564
1565 assert!(is_ring_inside_ring(&inner, &outer));
1566 assert!(!is_ring_inside_ring(&outside, &outer));
1567 }
1568
1569 #[test]
1570 fn test_clip_ring_to_box() {
1571 let ring = vec![
1572 Coordinate::new_2d(0.0, 0.0),
1573 Coordinate::new_2d(20.0, 0.0),
1574 Coordinate::new_2d(20.0, 20.0),
1575 Coordinate::new_2d(0.0, 20.0),
1576 Coordinate::new_2d(0.0, 0.0),
1577 ];
1578
1579 let result = clip_ring_to_box(&ring, 5.0, 5.0, 15.0, 15.0);
1580 assert!(result.is_ok());
1581 if let Ok(clipped) = result {
1582 assert!(clipped.len() >= 4);
1584 }
1585 }
1586
1587 #[test]
1588 fn test_compute_ring_centroid() {
1589 let ring = vec![
1590 Coordinate::new_2d(0.0, 0.0),
1591 Coordinate::new_2d(10.0, 0.0),
1592 Coordinate::new_2d(10.0, 10.0),
1593 Coordinate::new_2d(0.0, 10.0),
1594 Coordinate::new_2d(0.0, 0.0),
1595 ];
1596
1597 let centroid = compute_ring_centroid(&ring);
1598 assert!((centroid.x - 4.0).abs() < 1.0); assert!((centroid.y - 4.0).abs() < 1.0);
1601 }
1602
1603 #[test]
1604 fn test_difference_preserves_unaffected_holes() {
1605 let exterior_coords = vec![
1607 Coordinate::new_2d(0.0, 0.0),
1608 Coordinate::new_2d(30.0, 0.0),
1609 Coordinate::new_2d(30.0, 30.0),
1610 Coordinate::new_2d(0.0, 30.0),
1611 Coordinate::new_2d(0.0, 0.0),
1612 ];
1613 let hole1_coords = vec![
1614 Coordinate::new_2d(2.0, 2.0),
1615 Coordinate::new_2d(5.0, 2.0),
1616 Coordinate::new_2d(5.0, 5.0),
1617 Coordinate::new_2d(2.0, 5.0),
1618 Coordinate::new_2d(2.0, 2.0),
1619 ];
1620 let hole2_coords = vec![
1621 Coordinate::new_2d(20.0, 20.0),
1622 Coordinate::new_2d(25.0, 20.0),
1623 Coordinate::new_2d(25.0, 25.0),
1624 Coordinate::new_2d(20.0, 25.0),
1625 Coordinate::new_2d(20.0, 20.0),
1626 ];
1627
1628 let exterior = LineString::new(exterior_coords);
1629 let hole1 = LineString::new(hole1_coords);
1630 let hole2 = LineString::new(hole2_coords);
1631
1632 assert!(exterior.is_ok() && hole1.is_ok() && hole2.is_ok());
1633 if let (Ok(ext), Ok(h1), Ok(h2)) = (exterior, hole1, hole2) {
1634 let poly1 = Polygon::new(ext, vec![h1, h2]);
1635 let poly2 = create_square(10.0, 10.0, 5.0);
1636
1637 assert!(poly1.is_ok() && poly2.is_ok());
1638 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1639 let result = difference_polygon(&p1, &p2);
1640 assert!(result.is_ok());
1641 if let Ok(polys) = result {
1642 assert_eq!(polys.len(), 1);
1644 assert!(polys[0].interiors.len() >= 2);
1646 }
1647 }
1648 }
1649 }
1650}