1use crate::error::{AlgorithmError, Result};
48use crate::vector::intersection::{intersect_linestrings, point_in_polygon};
49use crate::vector::pool::{PoolGuard, get_pooled_polygon};
50use oxigdal_core::vector::{Coordinate, LineString, Polygon};
51
52#[cfg(feature = "std")]
53use std::vec::Vec;
54
55const EPSILON: f64 = 1e-10;
57
58pub fn difference_polygon(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
88 use oxigdal_core::OxiGdalError;
89
90 if poly1.exterior.coords.len() < 4 {
92 return Err(OxiGdalError::invalid_parameter_builder(
93 "poly1",
94 format!("exterior must have at least 4 coordinates, got {}", poly1.exterior.coords.len()),
95 )
96 .with_parameter("coordinate_count", poly1.exterior.coords.len().to_string())
97 .with_parameter("min_count", "4")
98 .with_operation("difference_polygon")
99 .with_suggestion("A valid polygon requires at least 4 coordinates (first and last must be identical to close the ring)")
100 .build()
101 .into());
102 }
103
104 if poly2.exterior.coords.len() < 4 {
105 return Err(OxiGdalError::invalid_parameter_builder(
106 "poly2",
107 format!("exterior must have at least 4 coordinates, got {}", poly2.exterior.coords.len()),
108 )
109 .with_parameter("coordinate_count", poly2.exterior.coords.len().to_string())
110 .with_parameter("min_count", "4")
111 .with_operation("difference_polygon")
112 .with_suggestion("A valid polygon requires at least 4 coordinates (first and last must be identical to close the ring)")
113 .build()
114 .into());
115 }
116
117 let bounds1 = poly1.bounds();
119 let bounds2 = poly2.bounds();
120
121 if let (Some((min_x1, min_y1, max_x1, max_y1)), Some((min_x2, min_y2, max_x2, max_y2))) =
122 (bounds1, bounds2)
123 {
124 if max_x1 < min_x2 || max_x2 < min_x1 || max_y1 < min_y2 || max_y2 < min_y1 {
126 return Ok(vec![poly1.clone()]);
128 }
129 }
130
131 let intersection_points = intersect_linestrings(&poly1.exterior, &poly2.exterior)?;
133
134 if intersection_points.is_empty() {
135 if point_in_polygon(&poly1.exterior.coords[0], poly2)? {
139 let in_hole = is_point_in_any_hole(&poly1.exterior.coords[0], poly2)?;
142 if !in_hole {
143 return Ok(vec![]);
145 }
146 return Ok(vec![poly1.clone()]);
148 }
149
150 if point_in_polygon(&poly2.exterior.coords[0], poly1)? {
152 let in_hole = is_point_in_any_hole(&poly2.exterior.coords[0], poly1)?;
155 if in_hole {
156 return Ok(vec![poly1.clone()]);
158 }
159
160 let mut interiors = filter_unaffected_holes(&poly1.interiors, poly2)?;
163
164 interiors.push(poly2.exterior.clone());
166
167 let mut result_polygons = Vec::new();
171
172 let result =
174 Polygon::new(poly1.exterior.clone(), interiors).map_err(AlgorithmError::Core)?;
175 result_polygons.push(result);
176
177 for hole in &poly2.interiors {
180 if hole.coords.len() >= 4 {
181 if !hole.coords.is_empty()
183 && point_in_polygon(&hole.coords[0], poly1)?
184 && !is_point_in_any_hole(&hole.coords[0], poly1)?
185 {
186 let hole_poly =
187 Polygon::new(hole.clone(), vec![]).map_err(AlgorithmError::Core)?;
188 result_polygons.push(hole_poly);
189 }
190 }
191 }
192
193 return Ok(result_polygons);
194 }
195
196 return Ok(vec![poly1.clone()]);
198 }
199
200 compute_overlapping_difference(poly1, poly2, &intersection_points)
202}
203
204fn is_point_in_any_hole(point: &Coordinate, polygon: &Polygon) -> Result<bool> {
206 for hole in &polygon.interiors {
207 if point_in_ring(point, &hole.coords) {
208 return Ok(true);
209 }
210 }
211 Ok(false)
212}
213
214fn filter_unaffected_holes(holes: &[LineString], subtract: &Polygon) -> Result<Vec<LineString>> {
216 let mut result = Vec::new();
217
218 for hole in holes {
219 if hole.coords.is_empty() {
220 continue;
221 }
222
223 let intersections = intersect_linestrings(hole, &subtract.exterior)?;
225
226 if intersections.is_empty() {
227 if point_in_ring(&hole.coords[0], &subtract.exterior.coords) {
229 continue;
231 }
232 result.push(hole.clone());
234 } else {
235 let center = compute_ring_centroid(&hole.coords);
239 if !point_in_ring(¢er, &subtract.exterior.coords) {
240 result.push(hole.clone());
241 }
242 }
243 }
244
245 Ok(result)
246}
247
248fn compute_overlapping_difference(
250 poly1: &Polygon,
251 poly2: &Polygon,
252 _intersection_points: &[Coordinate],
253) -> Result<Vec<Polygon>> {
254 let mut result_coords = Vec::new();
261 let coords1 = &poly1.exterior.coords;
262
263 for coord in coords1 {
265 if !point_in_ring(coord, &poly2.exterior.coords) {
266 result_coords.push(*coord);
268 } else if is_point_in_any_hole(coord, poly2).unwrap_or(false) {
269 result_coords.push(*coord);
271 }
272 }
273
274 if result_coords.len() >= 4 {
276 if let (Some(first), Some(last)) = (result_coords.first(), result_coords.last()) {
278 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
279 result_coords.push(*first);
280 }
281 }
282
283 let interiors = filter_unaffected_holes(&poly1.interiors, poly2)?;
285
286 if result_coords.len() >= 4 {
287 let exterior = LineString::new(result_coords).map_err(AlgorithmError::Core)?;
288 let result = Polygon::new(exterior, interiors).map_err(AlgorithmError::Core)?;
289 return Ok(vec![result]);
290 }
291 }
292
293 Ok(vec![poly1.clone()])
296}
297
298fn compute_ring_centroid(coords: &[Coordinate]) -> Coordinate {
300 if coords.is_empty() {
301 return Coordinate::new_2d(0.0, 0.0);
302 }
303
304 let mut sum_x = 0.0;
305 let mut sum_y = 0.0;
306 let n = coords.len();
307
308 for coord in coords {
309 sum_x += coord.x;
310 sum_y += coord.y;
311 }
312
313 Coordinate::new_2d(sum_x / n as f64, sum_y / n as f64)
314}
315
316fn point_in_ring(point: &Coordinate, ring: &[Coordinate]) -> bool {
318 let mut inside = false;
319 let n = ring.len();
320
321 if n < 3 {
322 return false;
323 }
324
325 let mut j = n - 1;
326 for i in 0..n {
327 let xi = ring[i].x;
328 let yi = ring[i].y;
329 let xj = ring[j].x;
330 let yj = ring[j].y;
331
332 let intersect = ((yi > point.y) != (yj > point.y))
333 && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
334
335 if intersect {
336 inside = !inside;
337 }
338
339 j = i;
340 }
341
342 inside
343}
344
345pub fn difference_polygons(base: &[Polygon], subtract: &[Polygon]) -> Result<Vec<Polygon>> {
362 if base.is_empty() {
363 return Ok(vec![]);
364 }
365
366 if subtract.is_empty() {
367 return Ok(base.to_vec());
368 }
369
370 for (i, poly) in base.iter().enumerate() {
372 if poly.exterior.coords.len() < 4 {
373 return Err(AlgorithmError::InsufficientData {
374 operation: "difference_polygons",
375 message: format!(
376 "base polygon {} exterior must have at least 4 coordinates",
377 i
378 ),
379 });
380 }
381 }
382
383 for (i, poly) in subtract.iter().enumerate() {
384 if poly.exterior.coords.len() < 4 {
385 return Err(AlgorithmError::InsufficientData {
386 operation: "difference_polygons",
387 message: format!(
388 "subtract polygon {} exterior must have at least 4 coordinates",
389 i
390 ),
391 });
392 }
393 }
394
395 let mut result = base.to_vec();
397
398 for sub_poly in subtract {
399 let mut new_result = Vec::new();
400
401 for base_poly in &result {
402 let diff = difference_polygon(base_poly, sub_poly)?;
403 new_result.extend(diff);
404 }
405
406 result = new_result;
407 }
408
409 Ok(result)
410}
411
412pub fn symmetric_difference(poly1: &Polygon, poly2: &Polygon) -> Result<Vec<Polygon>> {
430 let diff1 = difference_polygon(poly1, poly2)?;
432
433 let diff2 = difference_polygon(poly2, poly1)?;
435
436 let mut result = diff1;
438 result.extend(diff2);
439
440 Ok(result)
441}
442
443pub fn clip_to_box(
474 polygon: &Polygon,
475 min_x: f64,
476 min_y: f64,
477 max_x: f64,
478 max_y: f64,
479) -> Result<Vec<Polygon>> {
480 if polygon.exterior.coords.len() < 4 {
481 return Err(AlgorithmError::InsufficientData {
482 operation: "clip_to_box",
483 message: "polygon exterior must have at least 4 coordinates".to_string(),
484 });
485 }
486
487 if min_x >= max_x || min_y >= max_y {
488 return Err(AlgorithmError::InvalidParameter {
489 parameter: "bounding box",
490 message: "invalid bounding box dimensions".to_string(),
491 });
492 }
493
494 if let Some((poly_min_x, poly_min_y, poly_max_x, poly_max_y)) = polygon.bounds() {
496 if poly_max_x < min_x || poly_min_x > max_x || poly_max_y < min_y || poly_min_y > max_y {
497 return Ok(vec![]);
499 }
500
501 if poly_min_x >= min_x && poly_max_x <= max_x && poly_min_y >= min_y && poly_max_y <= max_y
502 {
503 return Ok(vec![polygon.clone()]);
505 }
506 }
507
508 let mut clipped_coords = polygon.exterior.coords.clone();
510
511 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 {
518 return Ok(vec![]);
520 }
521
522 if let (Some(first), Some(last)) = (clipped_coords.first(), clipped_coords.last()) {
524 if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
525 clipped_coords.push(*first);
526 }
527 }
528
529 let clipped_exterior = LineString::new(clipped_coords).map_err(AlgorithmError::Core)?;
530
531 let clipped_interiors = clip_interior_rings_to_box(
533 &polygon.interiors,
534 min_x,
535 min_y,
536 max_x,
537 max_y,
538 &clipped_exterior,
539 )?;
540
541 let result = Polygon::new(clipped_exterior, clipped_interiors).map_err(AlgorithmError::Core)?;
542
543 Ok(vec![result])
544}
545
546fn clip_interior_rings_to_box(
566 interiors: &[LineString],
567 min_x: f64,
568 min_y: f64,
569 max_x: f64,
570 max_y: f64,
571 clipped_exterior: &LineString,
572) -> Result<Vec<LineString>> {
573 let mut result = Vec::new();
574
575 for hole in interiors {
576 if hole.coords.len() < 4 {
577 continue;
578 }
579
580 let hole_bounds = hole.bounds();
582 if let Some((hole_min_x, hole_min_y, hole_max_x, hole_max_y)) = hole_bounds {
583 if hole_max_x < min_x || hole_min_x > max_x || hole_max_y < min_y || hole_min_y > max_y
585 {
586 continue;
588 }
589
590 if hole_min_x >= min_x
592 && hole_max_x <= max_x
593 && hole_min_y >= min_y
594 && hole_max_y <= max_y
595 {
596 if is_ring_inside_ring(&hole.coords, &clipped_exterior.coords) {
598 result.push(hole.clone());
599 }
600 continue;
601 }
602 }
603
604 let clipped_hole = clip_ring_to_box(&hole.coords, min_x, min_y, max_x, max_y)?;
606
607 if clipped_hole.len() >= 4 {
608 if is_ring_inside_ring(&clipped_hole, &clipped_exterior.coords) {
610 let mut closed_hole = clipped_hole;
612 if let (Some(first), Some(last)) = (closed_hole.first(), closed_hole.last()) {
613 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
614 closed_hole.push(*first);
615 }
616 }
617
618 if closed_hole.len() >= 4 {
619 if let Ok(hole_ring) = LineString::new(closed_hole) {
620 result.push(hole_ring);
621 }
622 }
623 }
624 }
625 }
626
627 Ok(result)
628}
629
630fn clip_ring_to_box(
644 coords: &[Coordinate],
645 min_x: f64,
646 min_y: f64,
647 max_x: f64,
648 max_y: f64,
649) -> Result<Vec<Coordinate>> {
650 let mut clipped = coords.to_vec();
651
652 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)
659}
660
661fn is_ring_inside_ring(inner: &[Coordinate], outer: &[Coordinate]) -> bool {
675 if inner.is_empty() || outer.is_empty() {
676 return false;
677 }
678
679 let centroid = compute_ring_centroid(inner);
681
682 point_in_ring(¢roid, outer)
684}
685
686pub fn validate_polygon_topology(polygon: &Polygon) -> Result<bool> {
706 if polygon.exterior.coords.len() < 4 {
708 return Ok(false);
709 }
710
711 if let (Some(first), Some(last)) = (
713 polygon.exterior.coords.first(),
714 polygon.exterior.coords.last(),
715 ) {
716 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
717 return Ok(false);
718 }
719 }
720
721 for hole in &polygon.interiors {
723 if hole.coords.len() < 4 {
725 return Ok(false);
726 }
727
728 if let (Some(first), Some(last)) = (hole.coords.first(), hole.coords.last()) {
730 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
731 return Ok(false);
732 }
733 }
734
735 if !is_ring_inside_ring(&hole.coords, &polygon.exterior.coords) {
737 return Ok(false);
738 }
739 }
740
741 for i in 0..polygon.interiors.len() {
743 for j in (i + 1)..polygon.interiors.len() {
744 if rings_overlap(&polygon.interiors[i].coords, &polygon.interiors[j].coords)? {
745 return Ok(false);
746 }
747 }
748 }
749
750 Ok(true)
751}
752
753fn rings_overlap(ring1: &[Coordinate], ring2: &[Coordinate]) -> Result<bool> {
758 for coord in ring1 {
760 if point_in_ring(coord, ring2) {
761 return Ok(true);
762 }
763 }
764
765 for coord in ring2 {
766 if point_in_ring(coord, ring1) {
767 return Ok(true);
768 }
769 }
770
771 if ring1.len() < 2 || ring2.len() < 2 {
774 return Ok(false);
775 }
776
777 for i in 0..(ring1.len() - 1) {
778 for j in 0..(ring2.len() - 1) {
779 if segments_intersect(&ring1[i], &ring1[i + 1], &ring2[j], &ring2[j + 1]) {
780 return Ok(true);
781 }
782 }
783 }
784
785 Ok(false)
786}
787
788fn segments_intersect(p1: &Coordinate, p2: &Coordinate, p3: &Coordinate, p4: &Coordinate) -> bool {
790 let d1x = p2.x - p1.x;
791 let d1y = p2.y - p1.y;
792 let d2x = p4.x - p3.x;
793 let d2y = p4.y - p3.y;
794
795 let cross = d1x * d2y - d1y * d2x;
796
797 if cross.abs() < EPSILON {
798 return false;
800 }
801
802 let dx = p3.x - p1.x;
803 let dy = p3.y - p1.y;
804
805 let t = (dx * d2y - dy * d2x) / cross;
806 let u = (dx * d1y - dy * d1x) / cross;
807
808 t > EPSILON && t < (1.0 - EPSILON) && u > EPSILON && u < (1.0 - EPSILON)
810}
811
812pub fn merge_adjacent_holes(holes: &[LineString]) -> Result<Vec<LineString>> {
825 if holes.is_empty() {
826 return Ok(vec![]);
827 }
828
829 if holes.len() == 1 {
830 return Ok(holes.to_vec());
831 }
832
833 let mut result = Vec::new();
836 let mut merged = vec![false; holes.len()];
837
838 for i in 0..holes.len() {
839 if merged[i] {
840 continue;
841 }
842
843 let mut current_hole = holes[i].coords.clone();
844
845 for j in (i + 1)..holes.len() {
846 if merged[j] {
847 continue;
848 }
849
850 if rings_overlap(¤t_hole, &holes[j].coords)? {
851 if compute_ring_area(&holes[j].coords).abs()
854 > compute_ring_area(¤t_hole).abs()
855 {
856 current_hole = holes[j].coords.clone();
857 }
858 merged[j] = true;
859 }
860 }
861
862 if current_hole.len() >= 4 {
863 if let Ok(ring) = LineString::new(current_hole) {
864 result.push(ring);
865 }
866 }
867 }
868
869 Ok(result)
870}
871
872fn clip_against_edge(
874 coords: &[Coordinate],
875 edge_value: f64,
876 is_vertical: bool,
877 is_max: bool,
878) -> Result<Vec<Coordinate>> {
879 if coords.is_empty() {
880 return Ok(vec![]);
881 }
882
883 let mut result = Vec::new();
884
885 for i in 0..coords.len() {
886 let current = &coords[i];
887 let next = &coords[(i + 1) % coords.len()];
888
889 let current_inside = is_inside(current, edge_value, is_vertical, is_max);
890 let next_inside = is_inside(next, edge_value, is_vertical, is_max);
891
892 if current_inside {
893 result.push(*current);
894 }
895
896 if current_inside != next_inside {
897 if let Some(intersection) = compute_intersection(current, next, edge_value, is_vertical)
899 {
900 result.push(intersection);
901 }
902 }
903 }
904
905 Ok(result)
906}
907
908fn is_inside(point: &Coordinate, edge_value: f64, is_vertical: bool, is_max: bool) -> bool {
910 let value = if is_vertical { point.x } else { point.y };
911
912 if is_max {
913 value <= edge_value
914 } else {
915 value >= edge_value
916 }
917}
918
919fn compute_intersection(
921 p1: &Coordinate,
922 p2: &Coordinate,
923 edge_value: f64,
924 is_vertical: bool,
925) -> Option<Coordinate> {
926 if is_vertical {
927 let dx = p2.x - p1.x;
929 if dx.abs() < f64::EPSILON {
930 return None; }
932
933 let t = (edge_value - p1.x) / dx;
934 if (0.0..=1.0).contains(&t) {
935 let y = p1.y + t * (p2.y - p1.y);
936 Some(Coordinate::new_2d(edge_value, y))
937 } else {
938 None
939 }
940 } else {
941 let dy = p2.y - p1.y;
943 if dy.abs() < f64::EPSILON {
944 return None; }
946
947 let t = (edge_value - p1.y) / dy;
948 if (0.0..=1.0).contains(&t) {
949 let x = p1.x + t * (p2.x - p1.x);
950 Some(Coordinate::new_2d(x, edge_value))
951 } else {
952 None
953 }
954 }
955}
956
957pub fn erase_small_holes(polygon: &Polygon, min_area: f64) -> Result<Polygon> {
975 if polygon.exterior.coords.len() < 4 {
976 return Err(AlgorithmError::InsufficientData {
977 operation: "erase_small_holes",
978 message: "polygon exterior must have at least 4 coordinates".to_string(),
979 });
980 }
981
982 if min_area < 0.0 {
983 return Err(AlgorithmError::InvalidParameter {
984 parameter: "min_area",
985 message: "min_area must be non-negative".to_string(),
986 });
987 }
988
989 let mut kept_holes = Vec::new();
990
991 for hole in &polygon.interiors {
992 let area = compute_ring_area(&hole.coords).abs();
993 if area >= min_area {
994 kept_holes.push(hole.clone());
995 }
996 }
997
998 Polygon::new(polygon.exterior.clone(), kept_holes).map_err(AlgorithmError::Core)
999}
1000
1001fn compute_ring_area(coords: &[Coordinate]) -> f64 {
1003 if coords.len() < 3 {
1004 return 0.0;
1005 }
1006
1007 let mut area = 0.0;
1008 let n = coords.len();
1009
1010 for i in 0..n {
1011 let j = (i + 1) % n;
1012 area += coords[i].x * coords[j].y;
1013 area -= coords[j].x * coords[i].y;
1014 }
1015
1016 area / 2.0
1017}
1018
1019pub fn difference_polygon_pooled(
1068 subject: &Polygon,
1069 clip: &Polygon,
1070) -> Result<PoolGuard<'static, Polygon>> {
1071 let results = difference_polygon(subject, clip)?;
1072
1073 if let Some(result) = results.first() {
1075 let mut poly = get_pooled_polygon();
1076 poly.exterior = result.exterior.clone();
1077 poly.interiors = result.interiors.clone();
1078 Ok(poly)
1079 } else {
1080 Err(AlgorithmError::InsufficientData {
1081 operation: "difference_polygon_pooled",
1082 message: "difference resulted in no polygons".to_string(),
1083 })
1084 }
1085}
1086
1087pub fn difference_polygons_pooled(
1125 base: &[Polygon],
1126 subtract: &[Polygon],
1127) -> Result<PoolGuard<'static, Polygon>> {
1128 let results = difference_polygons(base, subtract)?;
1129
1130 if let Some(result) = results.first() {
1132 let mut poly = get_pooled_polygon();
1133 poly.exterior = result.exterior.clone();
1134 poly.interiors = result.interiors.clone();
1135 Ok(poly)
1136 } else {
1137 Err(AlgorithmError::InsufficientData {
1138 operation: "difference_polygons_pooled",
1139 message: "difference resulted in no polygons".to_string(),
1140 })
1141 }
1142}
1143
1144pub fn symmetric_difference_pooled(
1189 poly1: &Polygon,
1190 poly2: &Polygon,
1191) -> Result<Vec<PoolGuard<'static, Polygon>>> {
1192 let results = symmetric_difference(poly1, poly2)?;
1193
1194 let mut pooled_results = Vec::new();
1196 for result in results {
1197 let mut poly = get_pooled_polygon();
1198 poly.exterior = result.exterior;
1199 poly.interiors = result.interiors;
1200 pooled_results.push(poly);
1201 }
1202
1203 Ok(pooled_results)
1204}
1205
1206#[cfg(test)]
1207mod tests {
1208 use super::*;
1209
1210 fn create_square(x: f64, y: f64, size: f64) -> Result<Polygon> {
1211 let coords = vec![
1212 Coordinate::new_2d(x, y),
1213 Coordinate::new_2d(x + size, y),
1214 Coordinate::new_2d(x + size, y + size),
1215 Coordinate::new_2d(x, y + size),
1216 Coordinate::new_2d(x, y),
1217 ];
1218 let exterior = LineString::new(coords).map_err(|e| AlgorithmError::Core(e))?;
1219 Polygon::new(exterior, vec![]).map_err(|e| AlgorithmError::Core(e))
1220 }
1221
1222 #[test]
1223 fn test_difference_polygon_disjoint() {
1224 let poly1 = create_square(0.0, 0.0, 5.0);
1225 let poly2 = create_square(10.0, 10.0, 5.0);
1226
1227 assert!(poly1.is_ok() && poly2.is_ok());
1228 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1229 let result = difference_polygon(&p1, &p2);
1230 assert!(result.is_ok());
1231 if let Ok(polys) = result {
1232 assert_eq!(polys.len(), 1); }
1234 }
1235 }
1236
1237 #[test]
1238 fn test_difference_polygon_contained() {
1239 let poly1 = create_square(0.0, 0.0, 10.0);
1240 let poly2 = create_square(2.0, 2.0, 3.0);
1241
1242 assert!(poly1.is_ok() && poly2.is_ok());
1243 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1244 let result = difference_polygon(&p1, &p2);
1245 assert!(result.is_ok());
1246 if let Ok(polys) = result {
1247 assert_eq!(polys.len(), 1);
1249 assert_eq!(polys[0].interiors.len(), 1);
1250 }
1251 }
1252 }
1253
1254 #[test]
1255 fn test_difference_polygon_completely_subtracted() {
1256 let poly1 = create_square(2.0, 2.0, 3.0);
1257 let poly2 = create_square(0.0, 0.0, 10.0);
1258
1259 assert!(poly1.is_ok() && poly2.is_ok());
1260 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1261 let result = difference_polygon(&p1, &p2);
1262 assert!(result.is_ok());
1263 if let Ok(polys) = result {
1264 assert_eq!(polys.len(), 0);
1266 }
1267 }
1268 }
1269
1270 #[test]
1271 fn test_difference_polygons_multiple() {
1272 let base = vec![create_square(0.0, 0.0, 10.0).ok()];
1273 let subtract = vec![
1274 create_square(2.0, 2.0, 2.0).ok(),
1275 create_square(6.0, 6.0, 2.0).ok(),
1276 ];
1277
1278 let base_polys: Vec<_> = base.into_iter().flatten().collect();
1279 let subtract_polys: Vec<_> = subtract.into_iter().flatten().collect();
1280
1281 let result = difference_polygons(&base_polys, &subtract_polys);
1282 assert!(result.is_ok());
1283 }
1284
1285 #[test]
1286 fn test_symmetric_difference() {
1287 let poly1 = create_square(0.0, 0.0, 5.0);
1288 let poly2 = create_square(3.0, 0.0, 5.0);
1289
1290 assert!(poly1.is_ok() && poly2.is_ok());
1291 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1292 let result = symmetric_difference(&p1, &p2);
1293 assert!(result.is_ok());
1294 if let Ok(polys) = result {
1296 assert!(!polys.is_empty());
1297 }
1298 }
1299 }
1300
1301 #[test]
1302 fn test_clip_to_box_inside() {
1303 let poly = create_square(2.0, 2.0, 3.0);
1304 assert!(poly.is_ok());
1305
1306 if let Ok(p) = poly {
1307 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1308 assert!(result.is_ok());
1309 if let Ok(polys) = result {
1310 assert_eq!(polys.len(), 1);
1312 }
1313 }
1314 }
1315
1316 #[test]
1317 fn test_clip_to_box_outside() {
1318 let poly = create_square(15.0, 15.0, 3.0);
1319 assert!(poly.is_ok());
1320
1321 if let Ok(p) = poly {
1322 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1323 assert!(result.is_ok());
1324 if let Ok(polys) = result {
1325 assert_eq!(polys.len(), 0);
1327 }
1328 }
1329 }
1330
1331 #[test]
1332 fn test_clip_to_box_partial() {
1333 let poly = create_square(5.0, 5.0, 10.0);
1334 assert!(poly.is_ok());
1335
1336 if let Ok(p) = poly {
1337 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1338 assert!(result.is_ok());
1339 if let Ok(polys) = result {
1340 assert_eq!(polys.len(), 1);
1342 }
1343 }
1344 }
1345
1346 #[test]
1347 fn test_erase_small_holes() {
1348 let exterior_coords = vec![
1349 Coordinate::new_2d(0.0, 0.0),
1350 Coordinate::new_2d(20.0, 0.0),
1351 Coordinate::new_2d(20.0, 20.0),
1352 Coordinate::new_2d(0.0, 20.0),
1353 Coordinate::new_2d(0.0, 0.0),
1354 ];
1355
1356 let small_hole_coords = vec![
1357 Coordinate::new_2d(2.0, 2.0),
1358 Coordinate::new_2d(3.0, 2.0),
1359 Coordinate::new_2d(3.0, 3.0),
1360 Coordinate::new_2d(2.0, 3.0),
1361 Coordinate::new_2d(2.0, 2.0),
1362 ];
1363
1364 let large_hole_coords = vec![
1365 Coordinate::new_2d(10.0, 10.0),
1366 Coordinate::new_2d(15.0, 10.0),
1367 Coordinate::new_2d(15.0, 15.0),
1368 Coordinate::new_2d(10.0, 15.0),
1369 Coordinate::new_2d(10.0, 10.0),
1370 ];
1371
1372 let exterior = LineString::new(exterior_coords);
1373 let small_hole = LineString::new(small_hole_coords);
1374 let large_hole = LineString::new(large_hole_coords);
1375
1376 assert!(exterior.is_ok() && small_hole.is_ok() && large_hole.is_ok());
1377
1378 if let (Ok(ext), Ok(sh), Ok(lh)) = (exterior, small_hole, large_hole) {
1379 let polygon = Polygon::new(ext, vec![sh, lh]);
1380 assert!(polygon.is_ok());
1381
1382 if let Ok(poly) = polygon {
1383 let result = erase_small_holes(&poly, 10.0);
1384 assert!(result.is_ok());
1385 if let Ok(cleaned) = result {
1386 assert_eq!(cleaned.interiors.len(), 1);
1388 }
1389 }
1390 }
1391 }
1392
1393 fn create_square_with_hole(
1395 x: f64,
1396 y: f64,
1397 size: f64,
1398 hole_x: f64,
1399 hole_y: f64,
1400 hole_size: f64,
1401 ) -> Result<Polygon> {
1402 let exterior_coords = vec![
1403 Coordinate::new_2d(x, y),
1404 Coordinate::new_2d(x + size, y),
1405 Coordinate::new_2d(x + size, y + size),
1406 Coordinate::new_2d(x, y + size),
1407 Coordinate::new_2d(x, y),
1408 ];
1409 let hole_coords = vec![
1410 Coordinate::new_2d(hole_x, hole_y),
1411 Coordinate::new_2d(hole_x + hole_size, hole_y),
1412 Coordinate::new_2d(hole_x + hole_size, hole_y + hole_size),
1413 Coordinate::new_2d(hole_x, hole_y + hole_size),
1414 Coordinate::new_2d(hole_x, hole_y),
1415 ];
1416
1417 let exterior = LineString::new(exterior_coords).map_err(AlgorithmError::Core)?;
1418 let hole = LineString::new(hole_coords).map_err(AlgorithmError::Core)?;
1419
1420 Polygon::new(exterior, vec![hole]).map_err(AlgorithmError::Core)
1421 }
1422
1423 #[test]
1426 fn test_difference_poly1_with_hole_disjoint() {
1427 let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1429 let poly2 = create_square(30.0, 30.0, 5.0);
1430
1431 assert!(poly1.is_ok() && poly2.is_ok());
1432 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1433 let result = difference_polygon(&p1, &p2);
1434 assert!(result.is_ok());
1435 if let Ok(polys) = result {
1436 assert_eq!(polys.len(), 1);
1438 assert_eq!(polys[0].interiors.len(), 1);
1439 }
1440 }
1441 }
1442
1443 #[test]
1444 fn test_difference_poly1_with_hole_contained_subtract() {
1445 let poly1 = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1447 let poly2 = create_square(12.0, 12.0, 3.0);
1448
1449 assert!(poly1.is_ok() && poly2.is_ok());
1450 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1451 let result = difference_polygon(&p1, &p2);
1452 assert!(result.is_ok());
1453 if let Ok(polys) = result {
1454 assert_eq!(polys.len(), 1);
1456 assert_eq!(polys[0].interiors.len(), 2);
1457 }
1458 }
1459 }
1460
1461 #[test]
1462 fn test_difference_subtract_poly_with_hole() {
1463 let poly1 = create_square(0.0, 0.0, 20.0);
1465 let poly2 = create_square_with_hole(2.0, 2.0, 16.0, 6.0, 6.0, 4.0);
1466
1467 assert!(poly1.is_ok() && poly2.is_ok());
1468 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1469 let result = difference_polygon(&p1, &p2);
1470 assert!(result.is_ok());
1471 if let Ok(polys) = result {
1472 assert!(!polys.is_empty());
1476 }
1477 }
1478 }
1479
1480 #[test]
1481 fn test_difference_poly1_inside_hole_of_poly2() {
1482 let exterior_coords = vec![
1484 Coordinate::new_2d(0.0, 0.0),
1485 Coordinate::new_2d(30.0, 0.0),
1486 Coordinate::new_2d(30.0, 30.0),
1487 Coordinate::new_2d(0.0, 30.0),
1488 Coordinate::new_2d(0.0, 0.0),
1489 ];
1490 let hole_coords = vec![
1491 Coordinate::new_2d(5.0, 5.0),
1492 Coordinate::new_2d(25.0, 5.0),
1493 Coordinate::new_2d(25.0, 25.0),
1494 Coordinate::new_2d(5.0, 25.0),
1495 Coordinate::new_2d(5.0, 5.0),
1496 ];
1497
1498 let exterior = LineString::new(exterior_coords);
1499 let hole = LineString::new(hole_coords);
1500
1501 assert!(exterior.is_ok() && hole.is_ok());
1502 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1503 let poly2 = Polygon::new(ext, vec![h]);
1504 let poly1 = create_square(10.0, 10.0, 5.0);
1505
1506 assert!(poly1.is_ok() && poly2.is_ok());
1507 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1508 let result = difference_polygon(&p1, &p2);
1509 assert!(result.is_ok());
1510 if let Ok(polys) = result {
1511 assert_eq!(polys.len(), 1);
1513 }
1514 }
1515 }
1516 }
1517
1518 #[test]
1519 fn test_clip_to_box_with_hole_inside() {
1520 let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1522 assert!(poly.is_ok());
1523
1524 if let Ok(p) = poly {
1525 let result = clip_to_box(&p, 0.0, 0.0, 25.0, 25.0);
1526 assert!(result.is_ok());
1527 if let Ok(polys) = result {
1528 assert_eq!(polys.len(), 1);
1530 assert_eq!(polys[0].interiors.len(), 1);
1531 }
1532 }
1533 }
1534
1535 #[test]
1536 fn test_clip_to_box_hole_outside() {
1537 let exterior_coords = vec![
1539 Coordinate::new_2d(0.0, 0.0),
1540 Coordinate::new_2d(20.0, 0.0),
1541 Coordinate::new_2d(20.0, 20.0),
1542 Coordinate::new_2d(0.0, 20.0),
1543 Coordinate::new_2d(0.0, 0.0),
1544 ];
1545 let hole_coords = vec![
1546 Coordinate::new_2d(15.0, 15.0),
1547 Coordinate::new_2d(18.0, 15.0),
1548 Coordinate::new_2d(18.0, 18.0),
1549 Coordinate::new_2d(15.0, 18.0),
1550 Coordinate::new_2d(15.0, 15.0),
1551 ];
1552
1553 let exterior = LineString::new(exterior_coords);
1554 let hole = LineString::new(hole_coords);
1555
1556 assert!(exterior.is_ok() && hole.is_ok());
1557 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1558 let poly = Polygon::new(ext, vec![h]);
1559 assert!(poly.is_ok());
1560
1561 if let Ok(p) = poly {
1562 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1564 assert!(result.is_ok());
1565 if let Ok(polys) = result {
1566 assert_eq!(polys.len(), 1);
1568 assert_eq!(polys[0].interiors.len(), 0);
1569 }
1570 }
1571 }
1572 }
1573
1574 #[test]
1575 fn test_clip_to_box_hole_partial() {
1576 let exterior_coords = vec![
1578 Coordinate::new_2d(0.0, 0.0),
1579 Coordinate::new_2d(20.0, 0.0),
1580 Coordinate::new_2d(20.0, 20.0),
1581 Coordinate::new_2d(0.0, 20.0),
1582 Coordinate::new_2d(0.0, 0.0),
1583 ];
1584 let hole_coords = vec![
1585 Coordinate::new_2d(8.0, 8.0),
1586 Coordinate::new_2d(12.0, 8.0),
1587 Coordinate::new_2d(12.0, 12.0),
1588 Coordinate::new_2d(8.0, 12.0),
1589 Coordinate::new_2d(8.0, 8.0),
1590 ];
1591
1592 let exterior = LineString::new(exterior_coords);
1593 let hole = LineString::new(hole_coords);
1594
1595 assert!(exterior.is_ok() && hole.is_ok());
1596 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1597 let poly = Polygon::new(ext, vec![h]);
1598 assert!(poly.is_ok());
1599
1600 if let Ok(p) = poly {
1601 let result = clip_to_box(&p, 0.0, 0.0, 10.0, 10.0);
1603 assert!(result.is_ok());
1604 }
1606 }
1607 }
1608
1609 #[test]
1610 fn test_validate_polygon_topology_valid() {
1611 let poly = create_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0);
1612 assert!(poly.is_ok());
1613
1614 if let Ok(p) = poly {
1615 let result = validate_polygon_topology(&p);
1616 assert!(result.is_ok());
1617 if let Ok(valid) = result {
1618 assert!(valid);
1619 }
1620 }
1621 }
1622
1623 #[test]
1624 fn test_validate_polygon_topology_hole_outside() {
1625 let exterior_coords = vec![
1627 Coordinate::new_2d(0.0, 0.0),
1628 Coordinate::new_2d(10.0, 0.0),
1629 Coordinate::new_2d(10.0, 10.0),
1630 Coordinate::new_2d(0.0, 10.0),
1631 Coordinate::new_2d(0.0, 0.0),
1632 ];
1633 let hole_coords = vec![
1634 Coordinate::new_2d(20.0, 20.0),
1635 Coordinate::new_2d(25.0, 20.0),
1636 Coordinate::new_2d(25.0, 25.0),
1637 Coordinate::new_2d(20.0, 25.0),
1638 Coordinate::new_2d(20.0, 20.0),
1639 ];
1640
1641 let exterior = LineString::new(exterior_coords);
1642 let hole = LineString::new(hole_coords);
1643
1644 assert!(exterior.is_ok() && hole.is_ok());
1645 if let (Ok(ext), Ok(h)) = (exterior, hole) {
1646 let poly = Polygon {
1649 exterior: ext,
1650 interiors: vec![h],
1651 };
1652
1653 let result = validate_polygon_topology(&poly);
1654 assert!(result.is_ok());
1655 if let Ok(valid) = result {
1656 assert!(!valid);
1658 }
1659 }
1660 }
1661
1662 #[test]
1663 fn test_merge_adjacent_holes_no_overlap() {
1664 let hole1_coords = vec![
1665 Coordinate::new_2d(2.0, 2.0),
1666 Coordinate::new_2d(4.0, 2.0),
1667 Coordinate::new_2d(4.0, 4.0),
1668 Coordinate::new_2d(2.0, 4.0),
1669 Coordinate::new_2d(2.0, 2.0),
1670 ];
1671 let hole2_coords = vec![
1672 Coordinate::new_2d(6.0, 6.0),
1673 Coordinate::new_2d(8.0, 6.0),
1674 Coordinate::new_2d(8.0, 8.0),
1675 Coordinate::new_2d(6.0, 8.0),
1676 Coordinate::new_2d(6.0, 6.0),
1677 ];
1678
1679 let hole1 = LineString::new(hole1_coords);
1680 let hole2 = LineString::new(hole2_coords);
1681
1682 assert!(hole1.is_ok() && hole2.is_ok());
1683 if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
1684 let result = merge_adjacent_holes(&[h1, h2]);
1685 assert!(result.is_ok());
1686 if let Ok(merged) = result {
1687 assert_eq!(merged.len(), 2);
1689 }
1690 }
1691 }
1692
1693 #[test]
1694 fn test_merge_adjacent_holes_with_overlap() {
1695 let hole1_coords = vec![
1696 Coordinate::new_2d(2.0, 2.0),
1697 Coordinate::new_2d(6.0, 2.0),
1698 Coordinate::new_2d(6.0, 6.0),
1699 Coordinate::new_2d(2.0, 6.0),
1700 Coordinate::new_2d(2.0, 2.0),
1701 ];
1702 let hole2_coords = vec![
1703 Coordinate::new_2d(4.0, 4.0),
1704 Coordinate::new_2d(8.0, 4.0),
1705 Coordinate::new_2d(8.0, 8.0),
1706 Coordinate::new_2d(4.0, 8.0),
1707 Coordinate::new_2d(4.0, 4.0),
1708 ];
1709
1710 let hole1 = LineString::new(hole1_coords);
1711 let hole2 = LineString::new(hole2_coords);
1712
1713 assert!(hole1.is_ok() && hole2.is_ok());
1714 if let (Ok(h1), Ok(h2)) = (hole1, hole2) {
1715 let result = merge_adjacent_holes(&[h1, h2]);
1716 assert!(result.is_ok());
1717 if let Ok(merged) = result {
1718 assert_eq!(merged.len(), 1);
1720 }
1721 }
1722 }
1723
1724 #[test]
1725 fn test_point_in_ring() {
1726 let ring = vec![
1727 Coordinate::new_2d(0.0, 0.0),
1728 Coordinate::new_2d(10.0, 0.0),
1729 Coordinate::new_2d(10.0, 10.0),
1730 Coordinate::new_2d(0.0, 10.0),
1731 Coordinate::new_2d(0.0, 0.0),
1732 ];
1733
1734 let inside = point_in_ring(&Coordinate::new_2d(5.0, 5.0), &ring);
1736 assert!(inside);
1737
1738 let outside = point_in_ring(&Coordinate::new_2d(15.0, 15.0), &ring);
1740 assert!(!outside);
1741
1742 let on_edge = point_in_ring(&Coordinate::new_2d(5.0, 0.0), &ring);
1744 let _ = on_edge;
1746 }
1747
1748 #[test]
1749 fn test_is_ring_inside_ring() {
1750 let outer = vec![
1751 Coordinate::new_2d(0.0, 0.0),
1752 Coordinate::new_2d(20.0, 0.0),
1753 Coordinate::new_2d(20.0, 20.0),
1754 Coordinate::new_2d(0.0, 20.0),
1755 Coordinate::new_2d(0.0, 0.0),
1756 ];
1757
1758 let inner = vec![
1759 Coordinate::new_2d(5.0, 5.0),
1760 Coordinate::new_2d(15.0, 5.0),
1761 Coordinate::new_2d(15.0, 15.0),
1762 Coordinate::new_2d(5.0, 15.0),
1763 Coordinate::new_2d(5.0, 5.0),
1764 ];
1765
1766 let outside = vec![
1767 Coordinate::new_2d(30.0, 30.0),
1768 Coordinate::new_2d(40.0, 30.0),
1769 Coordinate::new_2d(40.0, 40.0),
1770 Coordinate::new_2d(30.0, 40.0),
1771 Coordinate::new_2d(30.0, 30.0),
1772 ];
1773
1774 assert!(is_ring_inside_ring(&inner, &outer));
1775 assert!(!is_ring_inside_ring(&outside, &outer));
1776 }
1777
1778 #[test]
1779 fn test_clip_ring_to_box() {
1780 let ring = vec![
1781 Coordinate::new_2d(0.0, 0.0),
1782 Coordinate::new_2d(20.0, 0.0),
1783 Coordinate::new_2d(20.0, 20.0),
1784 Coordinate::new_2d(0.0, 20.0),
1785 Coordinate::new_2d(0.0, 0.0),
1786 ];
1787
1788 let result = clip_ring_to_box(&ring, 5.0, 5.0, 15.0, 15.0);
1789 assert!(result.is_ok());
1790 if let Ok(clipped) = result {
1791 assert!(clipped.len() >= 4);
1793 }
1794 }
1795
1796 #[test]
1797 fn test_compute_ring_centroid() {
1798 let ring = vec![
1799 Coordinate::new_2d(0.0, 0.0),
1800 Coordinate::new_2d(10.0, 0.0),
1801 Coordinate::new_2d(10.0, 10.0),
1802 Coordinate::new_2d(0.0, 10.0),
1803 Coordinate::new_2d(0.0, 0.0),
1804 ];
1805
1806 let centroid = compute_ring_centroid(&ring);
1807 assert!((centroid.x - 4.0).abs() < 1.0); assert!((centroid.y - 4.0).abs() < 1.0);
1810 }
1811
1812 #[test]
1813 fn test_difference_preserves_unaffected_holes() {
1814 let exterior_coords = vec![
1816 Coordinate::new_2d(0.0, 0.0),
1817 Coordinate::new_2d(30.0, 0.0),
1818 Coordinate::new_2d(30.0, 30.0),
1819 Coordinate::new_2d(0.0, 30.0),
1820 Coordinate::new_2d(0.0, 0.0),
1821 ];
1822 let hole1_coords = vec![
1823 Coordinate::new_2d(2.0, 2.0),
1824 Coordinate::new_2d(5.0, 2.0),
1825 Coordinate::new_2d(5.0, 5.0),
1826 Coordinate::new_2d(2.0, 5.0),
1827 Coordinate::new_2d(2.0, 2.0),
1828 ];
1829 let hole2_coords = vec![
1830 Coordinate::new_2d(20.0, 20.0),
1831 Coordinate::new_2d(25.0, 20.0),
1832 Coordinate::new_2d(25.0, 25.0),
1833 Coordinate::new_2d(20.0, 25.0),
1834 Coordinate::new_2d(20.0, 20.0),
1835 ];
1836
1837 let exterior = LineString::new(exterior_coords);
1838 let hole1 = LineString::new(hole1_coords);
1839 let hole2 = LineString::new(hole2_coords);
1840
1841 assert!(exterior.is_ok() && hole1.is_ok() && hole2.is_ok());
1842 if let (Ok(ext), Ok(h1), Ok(h2)) = (exterior, hole1, hole2) {
1843 let poly1 = Polygon::new(ext, vec![h1, h2]);
1844 let poly2 = create_square(10.0, 10.0, 5.0);
1845
1846 assert!(poly1.is_ok() && poly2.is_ok());
1847 if let (Ok(p1), Ok(p2)) = (poly1, poly2) {
1848 let result = difference_polygon(&p1, &p2);
1849 assert!(result.is_ok());
1850 if let Ok(polys) = result {
1851 assert_eq!(polys.len(), 1);
1853 assert!(polys[0].interiors.len() >= 2);
1855 }
1856 }
1857 }
1858 }
1859}