1use crate::error::{AlgorithmError, Result};
27use oxigdal_core::vector::{Coordinate, LineString, Polygon};
28
29#[cfg(feature = "std")]
30use std::vec::Vec;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum ClipOperation {
39 Intersection,
41 Union,
43 Difference,
45 SymmetricDifference,
47}
48
49const EPSILON: f64 = 1e-10;
55
56#[derive(Debug, Clone)]
58struct ClipVertex {
59 coord: Coordinate,
61 is_intersection: bool,
63 entering: bool,
65 neighbor: Option<usize>,
67 alpha: f64,
70 next: usize,
72 visited: bool,
74}
75
76struct ClipPolygon {
78 verts: Vec<ClipVertex>,
79}
80
81impl ClipPolygon {
82 fn from_ring(coords: &[Coordinate]) -> Self {
85 let n = if coords.len() >= 2
86 && (coords[0].x - coords[coords.len() - 1].x).abs() < EPSILON
87 && (coords[0].y - coords[coords.len() - 1].y).abs() < EPSILON
88 {
89 coords.len() - 1
90 } else {
91 coords.len()
92 };
93
94 let mut verts = Vec::with_capacity(n);
95 for i in 0..n {
96 verts.push(ClipVertex {
97 coord: coords[i],
98 is_intersection: false,
99 entering: false,
100 neighbor: None,
101 alpha: 0.0,
102 next: (i + 1) % n,
103 visited: false,
104 });
105 }
106 Self { verts }
107 }
108
109 fn len(&self) -> usize {
110 self.verts.len()
111 }
112}
113
114pub fn clip_polygons(subject: &Polygon, clip: &Polygon, op: ClipOperation) -> Result<Vec<Polygon>> {
127 validate_polygon(subject, "subject")?;
128 validate_polygon(clip, "clip")?;
129
130 if op == ClipOperation::SymmetricDifference {
132 let a_minus_b = clip_polygons(subject, clip, ClipOperation::Difference)?;
133 let b_minus_a = clip_polygons(clip, subject, ClipOperation::Difference)?;
134 let mut result = a_minus_b;
135 result.extend(b_minus_a);
136 return Ok(result);
137 }
138
139 if let (Some(sb), Some(cb)) = (subject.bounds(), clip.bounds()) {
141 if sb.2 < cb.0 || cb.2 < sb.0 || sb.3 < cb.1 || cb.3 < sb.1 {
142 return Ok(disjoint_result(subject, clip, op));
143 }
144 }
145
146 let ixs = find_all_intersections(&subject.exterior.coords, &clip.exterior.coords);
148
149 let has_hole_crossings = check_hole_crossings(subject, clip);
151
152 if ixs.is_empty() && !has_hole_crossings {
153 return handle_no_intersections(subject, clip, op);
154 }
155
156 if ixs.is_empty() && has_hole_crossings {
157 return handle_hole_crossing_containment(subject, clip, op);
160 }
161
162 weiler_atherton_clip(subject, clip, op, &ixs)
164}
165
166pub fn clip_multi(
168 subjects: &[Polygon],
169 clips: &[Polygon],
170 op: ClipOperation,
171) -> Result<Vec<Polygon>> {
172 if subjects.is_empty() {
173 return match op {
174 ClipOperation::Union => Ok(clips.to_vec()),
175 _ => Ok(vec![]),
176 };
177 }
178 if clips.is_empty() {
179 return match op {
180 ClipOperation::Intersection => Ok(vec![]),
181 _ => Ok(subjects.to_vec()),
182 };
183 }
184
185 let mut result = subjects.to_vec();
186 for clip_poly in clips {
187 let mut next_result = Vec::new();
188 for subj in &result {
189 let clipped = clip_polygons(subj, clip_poly, op)?;
190 next_result.extend(clipped);
191 }
192 result = next_result;
193 if result.is_empty() && op == ClipOperation::Intersection {
194 break;
195 }
196 }
197 Ok(result)
198}
199
200fn validate_polygon(poly: &Polygon, name: &str) -> Result<()> {
205 if poly.exterior.coords.len() < 4 {
206 return Err(AlgorithmError::InsufficientData {
207 operation: "clip_polygons",
208 message: format!(
209 "{name} exterior must have at least 4 coordinates, got {}",
210 poly.exterior.coords.len()
211 ),
212 });
213 }
214 Ok(())
215}
216
217fn disjoint_result(subject: &Polygon, clip: &Polygon, op: ClipOperation) -> Vec<Polygon> {
222 match op {
223 ClipOperation::Intersection => vec![],
224 ClipOperation::Union => vec![subject.clone(), clip.clone()],
225 ClipOperation::Difference => vec![subject.clone()],
226 ClipOperation::SymmetricDifference => vec![subject.clone(), clip.clone()],
227 }
228}
229
230fn handle_no_intersections(
231 subject: &Polygon,
232 clip: &Polygon,
233 op: ClipOperation,
234) -> Result<Vec<Polygon>> {
235 if are_rings_identical(&subject.exterior.coords, &clip.exterior.coords) {
237 return match op {
238 ClipOperation::Intersection => Ok(vec![subject.clone()]),
239 ClipOperation::Union => Ok(vec![subject.clone()]),
240 ClipOperation::Difference => Ok(vec![]),
241 ClipOperation::SymmetricDifference => Ok(vec![]),
242 };
243 }
244
245 let subj_in_clip = is_ring_contained_in_polygon(&subject.exterior.coords, clip)?;
249 let clip_in_subj = is_ring_contained_in_polygon(&clip.exterior.coords, subject)?;
250
251 match op {
252 ClipOperation::Intersection => {
253 if subj_in_clip {
254 let holes = collect_overlapping_holes(&clip.interiors, &subject.exterior.coords);
257 if holes.is_empty() {
258 Ok(vec![subject.clone()])
259 } else {
260 let mut all_holes = subject.interiors.clone();
261 all_holes.extend(holes);
262 let poly = Polygon::new(subject.exterior.clone(), all_holes)
263 .map_err(AlgorithmError::Core)?;
264 Ok(vec![poly])
265 }
266 } else if clip_in_subj {
267 let holes = collect_overlapping_holes(&subject.interiors, &clip.exterior.coords);
269 if holes.is_empty() {
270 Ok(vec![clip.clone()])
271 } else {
272 let mut all_holes = clip.interiors.clone();
273 all_holes.extend(holes);
274 let poly = Polygon::new(clip.exterior.clone(), all_holes)
275 .map_err(AlgorithmError::Core)?;
276 Ok(vec![poly])
277 }
278 } else {
279 Ok(vec![])
280 }
281 }
282 ClipOperation::Union => {
283 if subj_in_clip {
284 Ok(vec![clip.clone()])
285 } else if clip_in_subj {
286 Ok(vec![subject.clone()])
287 } else {
288 Ok(vec![subject.clone(), clip.clone()])
289 }
290 }
291 ClipOperation::Difference => {
292 if subj_in_clip {
293 Ok(vec![])
295 } else if clip_in_subj {
296 build_subject_with_hole(subject, clip)
298 } else {
299 Ok(vec![subject.clone()])
300 }
301 }
302 ClipOperation::SymmetricDifference => {
303 let a = clip_polygons(subject, clip, ClipOperation::Difference)?;
305 let b = clip_polygons(clip, subject, ClipOperation::Difference)?;
306 let mut r = a;
307 r.extend(b);
308 Ok(r)
309 }
310 }
311}
312
313fn check_hole_crossings(subject: &Polygon, clip: &Polygon) -> bool {
315 for hole in &subject.interiors {
317 let ixs = find_all_intersections(&hole.coords, &clip.exterior.coords);
318 if !ixs.is_empty() {
319 return true;
320 }
321 }
322 for hole in &clip.interiors {
324 let ixs = find_all_intersections(&hole.coords, &subject.exterior.coords);
325 if !ixs.is_empty() {
326 return true;
327 }
328 }
329 false
330}
331
332fn handle_hole_crossing_containment(
336 subject: &Polygon,
337 clip: &Polygon,
338 op: ClipOperation,
339) -> Result<Vec<Polygon>> {
340 let clip_inside_subj =
343 is_ring_contained_in_polygon(&clip.exterior.coords, subject).unwrap_or(false);
344 let subj_inside_clip =
345 is_ring_contained_in_polygon(&subject.exterior.coords, clip).unwrap_or(false);
346
347 match op {
348 ClipOperation::Intersection => {
349 if clip_inside_subj {
350 let holes = collect_overlapping_holes(&subject.interiors, &clip.exterior.coords);
352 let mut all_holes = clip.interiors.clone();
353 all_holes.extend(holes);
354 let poly =
355 Polygon::new(clip.exterior.clone(), all_holes).map_err(AlgorithmError::Core)?;
356 Ok(vec![poly])
357 } else if subj_inside_clip {
358 let holes = collect_overlapping_holes(&clip.interiors, &subject.exterior.coords);
360 let mut all_holes = subject.interiors.clone();
361 all_holes.extend(holes);
362 let poly = Polygon::new(subject.exterior.clone(), all_holes)
363 .map_err(AlgorithmError::Core)?;
364 Ok(vec![poly])
365 } else {
366 Ok(vec![])
367 }
368 }
369 ClipOperation::Difference => {
370 if subj_inside_clip {
371 Ok(vec![])
373 } else if clip_inside_subj {
374 build_subject_with_hole(subject, clip)
376 } else {
377 Ok(vec![subject.clone()])
378 }
379 }
380 ClipOperation::Union => {
381 if subj_inside_clip {
382 Ok(vec![clip.clone()])
383 } else if clip_inside_subj {
384 Ok(vec![subject.clone()])
385 } else {
386 Ok(vec![subject.clone(), clip.clone()])
387 }
388 }
389 ClipOperation::SymmetricDifference => {
390 let a = clip_polygons(subject, clip, ClipOperation::Difference)?;
391 let b = clip_polygons(clip, subject, ClipOperation::Difference)?;
392 let mut r = a;
393 r.extend(b);
394 Ok(r)
395 }
396 }
397}
398
399fn build_subject_with_hole(subject: &Polygon, clip: &Polygon) -> Result<Vec<Polygon>> {
401 let mut interiors = filter_unaffected_holes(&subject.interiors, clip)?;
402 interiors.push(clip.exterior.clone());
403
404 let mut result_polygons = Vec::new();
405 let main_poly =
406 Polygon::new(subject.exterior.clone(), interiors).map_err(AlgorithmError::Core)?;
407 result_polygons.push(main_poly);
408
409 for hole in &clip.interiors {
411 if hole.coords.len() >= 4
412 && !hole.coords.is_empty()
413 && point_in_ring(&hole.coords[0], &subject.exterior.coords)
414 && !is_point_in_any_hole(&hole.coords[0], subject)?
415 {
416 let hole_poly = Polygon::new(hole.clone(), vec![]).map_err(AlgorithmError::Core)?;
417 result_polygons.push(hole_poly);
418 }
419 }
420
421 Ok(result_polygons)
422}
423
424#[derive(Debug, Clone)]
430struct SegmentIx {
431 coord: Coordinate,
433 subj_seg: usize,
435 subj_t: f64,
437 clip_seg: usize,
439 clip_t: f64,
441}
442
443fn find_all_intersections(
444 subj_coords: &[Coordinate],
445 clip_coords: &[Coordinate],
446) -> Vec<SegmentIx> {
447 let sn = ring_vertex_count(subj_coords);
448 let cn = ring_vertex_count(clip_coords);
449 let mut result = Vec::new();
450
451 for i in 0..sn {
452 let i_next = (i + 1) % sn;
453 for j in 0..cn {
454 let j_next = (j + 1) % cn;
455 if let Some((t, u, coord)) = segment_intersect(
456 &subj_coords[i],
457 &subj_coords[i_next],
458 &clip_coords[j],
459 &clip_coords[j_next],
460 ) {
461 let dominated = result.iter().any(|ix: &SegmentIx| {
463 (ix.coord.x - coord.x).abs() < EPSILON && (ix.coord.y - coord.y).abs() < EPSILON
464 });
465 if !dominated {
466 result.push(SegmentIx {
467 coord,
468 subj_seg: i,
469 subj_t: t,
470 clip_seg: j,
471 clip_t: u,
472 });
473 }
474 }
475 }
476 }
477
478 result
479}
480
481fn ring_vertex_count(coords: &[Coordinate]) -> usize {
483 if coords.len() >= 2
484 && (coords[0].x - coords[coords.len() - 1].x).abs() < EPSILON
485 && (coords[0].y - coords[coords.len() - 1].y).abs() < EPSILON
486 {
487 coords.len() - 1
488 } else {
489 coords.len()
490 }
491}
492
493fn segment_intersect(
496 a1: &Coordinate,
497 a2: &Coordinate,
498 b1: &Coordinate,
499 b2: &Coordinate,
500) -> Option<(f64, f64, Coordinate)> {
501 let d1x = a2.x - a1.x;
502 let d1y = a2.y - a1.y;
503 let d2x = b2.x - b1.x;
504 let d2y = b2.y - b1.y;
505
506 let cross = d1x * d2y - d1y * d2x;
507 if cross.abs() < EPSILON {
508 return None; }
510
511 let dx = b1.x - a1.x;
512 let dy = b1.y - a1.y;
513
514 let t = (dx * d2y - dy * d2x) / cross;
515 let u = (dx * d1y - dy * d1x) / cross;
516
517 let inset = 1e-8;
521 if t >= -inset && t <= 1.0 + inset && u >= -inset && u <= 1.0 + inset {
522 let t_clamped = t.clamp(0.0, 1.0);
523 let u_clamped = u.clamp(0.0, 1.0);
524
525 let t_at_end = t_clamped < inset || t_clamped > 1.0 - inset;
527 let u_at_end = u_clamped < inset || u_clamped > 1.0 - inset;
528 if t_at_end && u_at_end {
529 return None; }
531
532 let x = a1.x + t_clamped * d1x;
533 let y = a1.y + t_clamped * d1y;
534 Some((t_clamped, u_clamped, Coordinate::new_2d(x, y)))
535 } else {
536 None
537 }
538}
539
540fn weiler_atherton_clip(
545 subject: &Polygon,
546 clip: &Polygon,
547 op: ClipOperation,
548 ixs: &[SegmentIx],
549) -> Result<Vec<Polygon>> {
550 let mut subj_list = ClipPolygon::from_ring(&subject.exterior.coords);
552 let mut clip_list = ClipPolygon::from_ring(&clip.exterior.coords);
553
554 insert_intersections(&mut subj_list, &mut clip_list, ixs);
556
557 label_entry_exit(&mut subj_list, &clip.exterior.coords, op);
559 let clip_op_inv = invert_op_for_clip(op);
560 label_entry_exit(&mut clip_list, &subject.exterior.coords, clip_op_inv);
561
562 let rings = trace_result_rings(&mut subj_list, &mut clip_list, op);
564
565 if rings.is_empty() {
566 return fallback_vertex_clip(subject, clip, op);
569 }
570
571 let result = build_output_polygons(&rings, subject, clip, op)?;
573
574 let result = validate_result_area(result, subject, clip, op)?;
576
577 Ok(result)
578}
579
580fn insert_intersections(subj: &mut ClipPolygon, clip: &mut ClipPolygon, ixs: &[SegmentIx]) {
583 let mut subj_inserts: Vec<(usize, f64, Coordinate, usize)> = Vec::new(); let mut clip_inserts: Vec<(usize, f64, Coordinate, usize)> = Vec::new();
586
587 for (ix_idx, ix) in ixs.iter().enumerate() {
588 subj_inserts.push((ix.subj_seg, ix.subj_t, ix.coord, ix_idx));
589 clip_inserts.push((ix.clip_seg, ix.clip_t, ix.coord, ix_idx));
590 }
591
592 subj_inserts.sort_by(|a, b| {
595 a.0.cmp(&b.0)
596 .then(b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
597 });
598 clip_inserts.sort_by(|a, b| {
599 a.0.cmp(&b.0)
600 .then(b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal))
601 });
602
603 let mut subj_positions: Vec<Option<usize>> = vec![None; ixs.len()];
605 let mut clip_positions: Vec<Option<usize>> = vec![None; ixs.len()];
606
607 for &(seg, alpha, coord, ix_idx) in &subj_inserts {
609 let new_idx = subj.verts.len();
610 let seg_next = subj.verts[seg].next;
611 subj.verts.push(ClipVertex {
612 coord,
613 is_intersection: true,
614 entering: false,
615 neighbor: None,
616 alpha,
617 next: seg_next,
618 visited: false,
619 });
620 subj.verts[seg].next = new_idx;
621 subj_positions[ix_idx] = Some(new_idx);
622 }
623
624 for &(seg, alpha, coord, ix_idx) in &clip_inserts {
626 let new_idx = clip.verts.len();
627 let seg_next = clip.verts[seg].next;
628 clip.verts.push(ClipVertex {
629 coord,
630 is_intersection: true,
631 entering: false,
632 neighbor: None,
633 alpha,
634 next: seg_next,
635 visited: false,
636 });
637 clip.verts[seg].next = new_idx;
638 clip_positions[ix_idx] = Some(new_idx);
639 }
640
641 for i in 0..ixs.len() {
643 if let (Some(si), Some(ci)) = (subj_positions[i], clip_positions[i]) {
644 subj.verts[si].neighbor = Some(ci);
645 clip.verts[ci].neighbor = Some(si);
646 }
647 }
648}
649
650fn label_entry_exit(poly: &mut ClipPolygon, other_ring: &[Coordinate], op: ClipOperation) {
656 let n = poly.len();
659 if n == 0 {
660 return;
661 }
662
663 let want_inside = match op {
665 ClipOperation::Intersection => true,
666 ClipOperation::Union => false,
667 ClipOperation::Difference => false,
668 ClipOperation::SymmetricDifference => false, };
670
671 let mut idx = 0;
673 let max_steps = poly.verts.len() * 2; let mut steps = 0;
675 loop {
676 if poly.verts[idx].is_intersection {
677 let prev_coord = find_prev_original_coord(poly, idx);
680 let was_inside = point_in_ring(&prev_coord, other_ring);
681
682 poly.verts[idx].entering = if want_inside { !was_inside } else { was_inside };
685 }
686
687 idx = poly.verts[idx].next;
688 steps += 1;
689 if idx == 0 || steps > max_steps {
690 break;
691 }
692 }
693}
694
695fn find_prev_original_coord(poly: &ClipPolygon, target: usize) -> Coordinate {
697 let mut last_non_ix = poly.verts[0].coord;
701 let mut idx = 0;
702 let max_steps = poly.verts.len() * 2;
703 let mut steps = 0;
704
705 loop {
706 if idx == target {
707 break;
708 }
709 if !poly.verts[idx].is_intersection {
710 last_non_ix = poly.verts[idx].coord;
711 }
712 idx = poly.verts[idx].next;
713 steps += 1;
714 if steps > max_steps {
715 break;
716 }
717 }
718
719 if steps == 0 {
722 idx = poly.verts[0].next;
723 let mut count = 0;
724 while idx != 0 && count < max_steps {
725 if !poly.verts[idx].is_intersection {
726 last_non_ix = poly.verts[idx].coord;
727 }
728 idx = poly.verts[idx].next;
729 count += 1;
730 }
731 }
732
733 last_non_ix
734}
735
736fn invert_op_for_clip(op: ClipOperation) -> ClipOperation {
738 match op {
739 ClipOperation::Intersection => ClipOperation::Intersection,
740 ClipOperation::Union => ClipOperation::Union,
741 ClipOperation::Difference => ClipOperation::Intersection, ClipOperation::SymmetricDifference => ClipOperation::SymmetricDifference,
743 }
744}
745
746fn trace_result_rings(
748 subj: &mut ClipPolygon,
749 clip: &mut ClipPolygon,
750 op: ClipOperation,
751) -> Vec<Vec<Coordinate>> {
752 let mut rings = Vec::new();
753 let max_total = (subj.verts.len() + clip.verts.len()) * 2;
754
755 loop {
757 let start = find_unvisited_entering(subj);
758 let start_idx = match start {
759 Some(idx) => idx,
760 None => break,
761 };
762
763 let mut ring_coords: Vec<Coordinate> = Vec::new();
764 let mut on_subject = true;
765 let mut current = start_idx;
766 let mut steps = 0;
767
768 loop {
769 if on_subject {
770 subj.verts[current].visited = true;
771 ring_coords.push(subj.verts[current].coord);
772
773 current = subj.verts[current].next;
775 steps += 1;
776
777 if current == start_idx {
779 break;
780 }
781
782 if steps > max_total {
783 break;
784 }
785
786 if subj.verts[current].is_intersection && !subj.verts[current].entering {
788 subj.verts[current].visited = true;
789 ring_coords.push(subj.verts[current].coord);
790 if let Some(neighbor) = subj.verts[current].neighbor {
791 current = clip.verts[neighbor].next;
792 on_subject = false;
793 }
794 }
795 } else {
796 clip.verts[current].visited = true;
798 ring_coords.push(clip.verts[current].coord);
799
800 current = clip.verts[current].next;
801 steps += 1;
802
803 if steps > max_total {
804 break;
805 }
806
807 if clip.verts[current].is_intersection && !clip.verts[current].entering {
809 clip.verts[current].visited = true;
810 ring_coords.push(clip.verts[current].coord);
811 if let Some(neighbor) = clip.verts[current].neighbor {
812 if neighbor == start_idx {
814 break;
815 }
816 current = subj.verts[neighbor].next;
817 on_subject = true;
818 }
819 }
820 }
821 }
822
823 if ring_coords.len() >= 3 {
825 close_ring(&mut ring_coords);
826 rings.push(ring_coords);
827 }
828 }
829
830 rings
831}
832
833fn find_unvisited_entering(poly: &ClipPolygon) -> Option<usize> {
834 for (i, v) in poly.verts.iter().enumerate() {
835 if v.is_intersection && v.entering && !v.visited {
836 return Some(i);
837 }
838 }
839 None
840}
841
842fn close_ring(coords: &mut Vec<Coordinate>) {
843 if let (Some(first), Some(last)) = (coords.first(), coords.last()) {
844 if (first.x - last.x).abs() > EPSILON || (first.y - last.y).abs() > EPSILON {
845 coords.push(*first);
846 }
847 }
848}
849
850fn build_output_polygons(
855 rings: &[Vec<Coordinate>],
856 subject: &Polygon,
857 clip: &Polygon,
858 op: ClipOperation,
859) -> Result<Vec<Polygon>> {
860 let mut exteriors: Vec<Vec<Coordinate>> = Vec::new();
862 let mut holes: Vec<Vec<Coordinate>> = Vec::new();
863
864 for ring in rings {
865 if ring.len() < 4 {
866 continue;
867 }
868 let area = signed_ring_area(ring);
869 if area.abs() < EPSILON {
870 continue; }
872 if area > 0.0 {
876 exteriors.push(ring.clone());
877 } else {
878 holes.push(ring.clone());
879 }
880 }
881
882 if exteriors.is_empty() && !holes.is_empty() {
885 std::mem::swap(&mut exteriors, &mut holes);
886 }
887
888 let mut result = Vec::new();
889
890 for ext_ring in &exteriors {
891 let mut assigned_holes = Vec::new();
893 for hole_ring in &holes {
894 if !hole_ring.is_empty() && point_in_ring(&hole_ring[0], ext_ring) {
895 if hole_ring.len() >= 4 {
896 if let Ok(ls) = LineString::new(hole_ring.clone()) {
897 assigned_holes.push(ls);
898 }
899 }
900 }
901 }
902
903 if op == ClipOperation::Difference {
905 let preserved = filter_unaffected_holes(&subject.interiors, clip)?;
906 for h in preserved {
907 if point_in_ring(&h.coords[0], ext_ring) {
908 assigned_holes.push(h);
909 }
910 }
911 }
912
913 let ext_ls = LineString::new(ext_ring.clone()).map_err(AlgorithmError::Core)?;
914 let poly = Polygon::new(ext_ls, assigned_holes).map_err(AlgorithmError::Core)?;
915 result.push(poly);
916 }
917
918 if result.is_empty() {
919 return fallback_vertex_clip(subject, clip, op);
921 }
922
923 Ok(result)
924}
925
926fn validate_result_area(
934 result: Vec<Polygon>,
935 subject: &Polygon,
936 clip: &Polygon,
937 op: ClipOperation,
938) -> Result<Vec<Polygon>> {
939 let total_area: f64 = result
940 .iter()
941 .map(|p| signed_ring_area(&p.exterior.coords).abs())
942 .sum();
943 let subj_area = signed_ring_area(&subject.exterior.coords).abs();
944 let clip_area = signed_ring_area(&clip.exterior.coords).abs();
945
946 let valid = match op {
947 ClipOperation::Intersection => total_area <= subj_area.min(clip_area) + EPSILON,
948 ClipOperation::Difference => total_area <= subj_area + EPSILON,
949 ClipOperation::Union => total_area <= subj_area + clip_area + EPSILON,
950 ClipOperation::SymmetricDifference => true,
951 };
952
953 if valid {
954 Ok(result)
955 } else {
956 fallback_vertex_clip(subject, clip, op)
958 }
959}
960
961fn fallback_vertex_clip(
966 subject: &Polygon,
967 clip: &Polygon,
968 op: ClipOperation,
969) -> Result<Vec<Polygon>> {
970 let subj_coords = &subject.exterior.coords;
971 let clip_coords = &clip.exterior.coords;
972
973 let mut result_coords: Vec<Coordinate> = Vec::new();
974
975 match op {
976 ClipOperation::Intersection => {
977 for c in subj_coords {
979 if point_in_ring(c, clip_coords) && !is_point_in_any_hole(c, clip).unwrap_or(false)
980 {
981 push_unique(&mut result_coords, *c);
982 }
983 }
984 for c in clip_coords {
985 if point_in_ring(c, subj_coords)
986 && !is_point_in_any_hole(c, subject).unwrap_or(false)
987 {
988 push_unique(&mut result_coords, *c);
989 }
990 }
991 let ixs = find_all_intersections(subj_coords, clip_coords);
993 for ix in &ixs {
994 push_unique(&mut result_coords, ix.coord);
995 }
996 }
997 ClipOperation::Difference => {
998 for c in subj_coords {
1000 if !point_in_ring(c, clip_coords) || is_point_in_any_hole(c, clip).unwrap_or(false)
1001 {
1002 push_unique(&mut result_coords, *c);
1003 }
1004 }
1005 let ixs = find_all_intersections(subj_coords, clip_coords);
1007 for ix in &ixs {
1008 push_unique(&mut result_coords, ix.coord);
1009 }
1010 }
1011 ClipOperation::Union => {
1012 for c in subj_coords {
1014 push_unique(&mut result_coords, *c);
1015 }
1016 for c in clip_coords {
1017 if !point_in_ring(c, subj_coords)
1018 || is_point_in_any_hole(c, subject).unwrap_or(false)
1019 {
1020 push_unique(&mut result_coords, *c);
1021 }
1022 }
1023 }
1024 ClipOperation::SymmetricDifference => {
1025 return Ok(vec![]);
1027 }
1028 }
1029
1030 if result_coords.len() < 3 {
1031 return match op {
1032 ClipOperation::Difference => Ok(vec![subject.clone()]),
1033 ClipOperation::Union => Ok(vec![subject.clone()]),
1034 _ => Ok(vec![]),
1035 };
1036 }
1037
1038 order_points_ccw(&mut result_coords);
1040 close_ring(&mut result_coords);
1041
1042 if result_coords.len() < 4 {
1043 return match op {
1044 ClipOperation::Difference => Ok(vec![subject.clone()]),
1045 ClipOperation::Union => Ok(vec![subject.clone()]),
1046 _ => Ok(vec![]),
1047 };
1048 }
1049
1050 let candidate_area = signed_ring_area(&result_coords).abs();
1053 let subj_area = signed_ring_area(subj_coords).abs();
1054 let clip_area = signed_ring_area(clip_coords).abs();
1055
1056 match op {
1057 ClipOperation::Difference => {
1058 if candidate_area > subj_area + EPSILON {
1059 return Ok(vec![subject.clone()]);
1061 }
1062 }
1063 ClipOperation::Intersection => {
1064 let max_valid = subj_area.min(clip_area);
1065 if candidate_area > max_valid + EPSILON {
1066 return Ok(vec![]);
1067 }
1068 }
1069 _ => {}
1070 }
1071
1072 let interiors = if op == ClipOperation::Difference {
1074 filter_unaffected_holes(&subject.interiors, clip)?
1075 } else {
1076 vec![]
1077 };
1078
1079 let ext = LineString::new(result_coords).map_err(AlgorithmError::Core)?;
1080 let poly = Polygon::new(ext, interiors).map_err(AlgorithmError::Core)?;
1081 Ok(vec![poly])
1082}
1083
1084fn push_unique(coords: &mut Vec<Coordinate>, c: Coordinate) {
1085 let dominated = coords
1086 .iter()
1087 .any(|e| (e.x - c.x).abs() < EPSILON && (e.y - c.y).abs() < EPSILON);
1088 if !dominated {
1089 coords.push(c);
1090 }
1091}
1092
1093fn order_points_ccw(coords: &mut [Coordinate]) {
1095 if coords.len() < 3 {
1096 return;
1097 }
1098 let n = coords.len() as f64;
1099 let cx: f64 = coords.iter().map(|c| c.x).sum::<f64>() / n;
1100 let cy: f64 = coords.iter().map(|c| c.y).sum::<f64>() / n;
1101
1102 coords.sort_by(|a, b| {
1103 let angle_a = (a.y - cy).atan2(a.x - cx);
1104 let angle_b = (b.y - cy).atan2(b.x - cx);
1105 angle_a
1106 .partial_cmp(&angle_b)
1107 .unwrap_or(std::cmp::Ordering::Equal)
1108 });
1109}
1110
1111fn is_ring_contained_in_polygon(ring: &[Coordinate], polygon: &Polygon) -> Result<bool> {
1118 let n = ring_vertex_count(ring);
1119 if n == 0 {
1120 return Ok(false);
1121 }
1122 for i in 0..n {
1123 if !point_in_ring(&ring[i], &polygon.exterior.coords) {
1124 return Ok(false);
1125 }
1126 if is_point_in_any_hole(&ring[i], polygon)? {
1127 return Ok(false);
1128 }
1129 }
1130 Ok(true)
1131}
1132
1133fn collect_overlapping_holes(
1137 holes: &[LineString],
1138 contained_exterior: &[Coordinate],
1139) -> Vec<LineString> {
1140 let mut result = Vec::new();
1141 for hole in holes {
1142 if hole.coords.is_empty() || hole.coords.len() < 4 {
1143 continue;
1144 }
1145 let hole_centroid = compute_ring_centroid(&hole.coords);
1147 if point_in_ring(&hole_centroid, contained_exterior) {
1148 result.push(hole.clone());
1149 }
1150 }
1151 result
1152}
1153
1154fn are_rings_identical(a: &[Coordinate], b: &[Coordinate]) -> bool {
1157 let na = ring_vertex_count(a);
1158 let nb = ring_vertex_count(b);
1159 if na != nb || na == 0 {
1160 return false;
1161 }
1162 for i in 0..na {
1163 if (a[i].x - b[i].x).abs() > EPSILON || (a[i].y - b[i].y).abs() > EPSILON {
1164 return false;
1165 }
1166 }
1167 true
1168}
1169
1170fn find_interior_test_point(coords: &[Coordinate]) -> Coordinate {
1173 let n = ring_vertex_count(coords);
1174 if n < 3 {
1175 return coords
1176 .get(0)
1177 .copied()
1178 .unwrap_or(Coordinate::new_2d(0.0, 0.0));
1179 }
1180
1181 let cx: f64 = coords[..n].iter().map(|c| c.x).sum::<f64>() / n as f64;
1184 let cy: f64 = coords[..n].iter().map(|c| c.y).sum::<f64>() / n as f64;
1185 Coordinate::new_2d(cx, cy)
1186}
1187
1188pub(crate) fn point_in_ring(point: &Coordinate, ring: &[Coordinate]) -> bool {
1190 let mut inside = false;
1191 let n = ring.len();
1192 if n < 3 {
1193 return false;
1194 }
1195
1196 let mut j = n - 1;
1197 for i in 0..n {
1198 let xi = ring[i].x;
1199 let yi = ring[i].y;
1200 let xj = ring[j].x;
1201 let yj = ring[j].y;
1202
1203 let intersect = ((yi > point.y) != (yj > point.y))
1204 && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
1205
1206 if intersect {
1207 inside = !inside;
1208 }
1209 j = i;
1210 }
1211 inside
1212}
1213
1214pub(crate) fn is_point_in_any_hole(point: &Coordinate, polygon: &Polygon) -> Result<bool> {
1216 for hole in &polygon.interiors {
1217 if point_in_ring(point, &hole.coords) {
1218 return Ok(true);
1219 }
1220 }
1221 Ok(false)
1222}
1223
1224pub(crate) fn signed_ring_area(coords: &[Coordinate]) -> f64 {
1226 if coords.len() < 3 {
1227 return 0.0;
1228 }
1229 let mut area = 0.0;
1230 let n = coords.len();
1231 for i in 0..n {
1232 let j = (i + 1) % n;
1233 area += coords[i].x * coords[j].y;
1234 area -= coords[j].x * coords[i].y;
1235 }
1236 area / 2.0
1237}
1238
1239pub(crate) fn filter_unaffected_holes(
1241 holes: &[LineString],
1242 clip: &Polygon,
1243) -> Result<Vec<LineString>> {
1244 let mut result = Vec::new();
1245 for hole in holes {
1246 if hole.coords.is_empty() {
1247 continue;
1248 }
1249 let centroid = compute_ring_centroid(&hole.coords);
1251 if !point_in_ring(¢roid, &clip.exterior.coords) {
1252 result.push(hole.clone());
1253 }
1254 }
1255 Ok(result)
1256}
1257
1258pub(crate) fn compute_ring_centroid(coords: &[Coordinate]) -> Coordinate {
1260 if coords.is_empty() {
1261 return Coordinate::new_2d(0.0, 0.0);
1262 }
1263 let n = coords.len() as f64;
1264 let sx: f64 = coords.iter().map(|c| c.x).sum();
1265 let sy: f64 = coords.iter().map(|c| c.y).sum();
1266 Coordinate::new_2d(sx / n, sy / n)
1267}
1268
1269#[cfg(test)]
1274mod tests {
1275 use super::*;
1276
1277 fn make_square(x: f64, y: f64, size: f64) -> Result<Polygon> {
1279 let coords = vec![
1280 Coordinate::new_2d(x, y),
1281 Coordinate::new_2d(x + size, y),
1282 Coordinate::new_2d(x + size, y + size),
1283 Coordinate::new_2d(x, y + size),
1284 Coordinate::new_2d(x, y),
1285 ];
1286 let ext = LineString::new(coords).map_err(AlgorithmError::Core)?;
1287 Polygon::new(ext, vec![]).map_err(AlgorithmError::Core)
1288 }
1289
1290 fn make_square_with_hole(
1292 x: f64,
1293 y: f64,
1294 size: f64,
1295 hx: f64,
1296 hy: f64,
1297 hsize: f64,
1298 ) -> Result<Polygon> {
1299 let ext_coords = vec![
1300 Coordinate::new_2d(x, y),
1301 Coordinate::new_2d(x + size, y),
1302 Coordinate::new_2d(x + size, y + size),
1303 Coordinate::new_2d(x, y + size),
1304 Coordinate::new_2d(x, y),
1305 ];
1306 let hole_coords = vec![
1307 Coordinate::new_2d(hx, hy),
1308 Coordinate::new_2d(hx + hsize, hy),
1309 Coordinate::new_2d(hx + hsize, hy + hsize),
1310 Coordinate::new_2d(hx, hy + hsize),
1311 Coordinate::new_2d(hx, hy),
1312 ];
1313 let ext = LineString::new(ext_coords).map_err(AlgorithmError::Core)?;
1314 let hole = LineString::new(hole_coords).map_err(AlgorithmError::Core)?;
1315 Polygon::new(ext, vec![hole]).map_err(AlgorithmError::Core)
1316 }
1317
1318 fn poly_area(poly: &Polygon) -> f64 {
1320 let ext_area = signed_ring_area(&poly.exterior.coords).abs();
1321 let hole_area: f64 = poly
1322 .interiors
1323 .iter()
1324 .map(|h| signed_ring_area(&h.coords).abs())
1325 .sum();
1326 ext_area - hole_area
1327 }
1328
1329 #[test]
1334 fn test_intersection_disjoint_squares() {
1335 let a = make_square(0.0, 0.0, 1.0).expect("make a");
1336 let b = make_square(5.0, 5.0, 1.0).expect("make b");
1337 let result = clip_polygons(&a, &b, ClipOperation::Intersection).expect("clip");
1338 assert!(
1339 result.is_empty(),
1340 "disjoint squares should have empty intersection"
1341 );
1342 }
1343
1344 #[test]
1345 fn test_intersection_overlapping_squares() {
1346 let a = make_square(0.0, 0.0, 1.0).expect("make a");
1349 let b = make_square(0.5, 0.5, 1.0).expect("make b");
1350 let result = clip_polygons(&a, &b, ClipOperation::Intersection).expect("clip");
1351 assert!(
1352 !result.is_empty(),
1353 "overlapping squares should have intersection"
1354 );
1355 let total_area: f64 = result.iter().map(|p| poly_area(p)).sum();
1356 assert!(
1357 (total_area - 0.25).abs() < 0.05,
1358 "intersection area should be ~0.25, got {total_area}"
1359 );
1360 }
1361
1362 #[test]
1363 fn test_intersection_containment() {
1364 let outer = make_square(0.0, 0.0, 10.0).expect("outer");
1365 let inner = make_square(2.0, 2.0, 3.0).expect("inner");
1366 let result = clip_polygons(&outer, &inner, ClipOperation::Intersection).expect("clip");
1367 assert_eq!(
1368 result.len(),
1369 1,
1370 "containment intersection should return inner polygon"
1371 );
1372 let area = poly_area(&result[0]);
1373 assert!(
1374 (area - 9.0).abs() < 0.1,
1375 "intersection should have area ~9.0, got {area}"
1376 );
1377 }
1378
1379 #[test]
1384 fn test_difference_disjoint() {
1385 let a = make_square(0.0, 0.0, 5.0).expect("a");
1386 let b = make_square(10.0, 10.0, 5.0).expect("b");
1387 let result = clip_polygons(&a, &b, ClipOperation::Difference).expect("clip");
1388 assert_eq!(result.len(), 1, "disjoint difference returns subject");
1389 }
1390
1391 #[test]
1392 fn test_difference_contained_creates_hole() {
1393 let outer = make_square(0.0, 0.0, 10.0).expect("outer");
1394 let inner = make_square(2.0, 2.0, 3.0).expect("inner");
1395 let result = clip_polygons(&outer, &inner, ClipOperation::Difference).expect("clip");
1396 assert_eq!(result.len(), 1);
1397 assert_eq!(result[0].interiors.len(), 1, "should have a hole");
1398 }
1399
1400 #[test]
1401 fn test_difference_completely_subtracted() {
1402 let inner = make_square(2.0, 2.0, 3.0).expect("inner");
1403 let outer = make_square(0.0, 0.0, 10.0).expect("outer");
1404 let result = clip_polygons(&inner, &outer, ClipOperation::Difference).expect("clip");
1405 assert!(result.is_empty(), "subject fully inside clip => empty");
1406 }
1407
1408 #[test]
1409 fn test_difference_with_hole_in_subject() {
1410 let a = make_square_with_hole(0.0, 0.0, 20.0, 5.0, 5.0, 5.0).expect("a");
1411 let b = make_square(30.0, 30.0, 5.0).expect("b");
1412 let result = clip_polygons(&a, &b, ClipOperation::Difference).expect("clip");
1413 assert_eq!(result.len(), 1);
1414 assert_eq!(result[0].interiors.len(), 1, "hole should be preserved");
1415 }
1416
1417 #[test]
1422 fn test_union_disjoint() {
1423 let a = make_square(0.0, 0.0, 5.0).expect("a");
1424 let b = make_square(10.0, 10.0, 5.0).expect("b");
1425 let result = clip_polygons(&a, &b, ClipOperation::Union).expect("clip");
1426 assert_eq!(result.len(), 2, "disjoint union returns both");
1427 }
1428
1429 #[test]
1430 fn test_union_containment() {
1431 let outer = make_square(0.0, 0.0, 10.0).expect("outer");
1432 let inner = make_square(2.0, 2.0, 3.0).expect("inner");
1433 let result = clip_polygons(&outer, &inner, ClipOperation::Union).expect("clip");
1434 assert_eq!(result.len(), 1, "containment union returns outer");
1435 let area = poly_area(&result[0]);
1436 assert!(
1437 (area - 100.0).abs() < 0.1,
1438 "union area should be ~100, got {area}"
1439 );
1440 }
1441
1442 #[test]
1447 fn test_xor_identical_polygons() {
1448 let a = make_square(0.0, 0.0, 5.0).expect("a");
1449 let b = make_square(0.0, 0.0, 5.0).expect("b");
1450 let result = clip_polygons(&a, &b, ClipOperation::SymmetricDifference).expect("clip");
1451 assert!(
1453 result.is_empty(),
1454 "XOR of identical polygons should be empty"
1455 );
1456 }
1457
1458 #[test]
1459 fn test_xor_disjoint() {
1460 let a = make_square(0.0, 0.0, 5.0).expect("a");
1461 let b = make_square(10.0, 10.0, 5.0).expect("b");
1462 let result = clip_polygons(&a, &b, ClipOperation::SymmetricDifference).expect("clip");
1463 assert_eq!(result.len(), 2, "XOR of disjoint returns both");
1464 }
1465
1466 #[test]
1471 fn test_intersection_l_shaped_concave() {
1472 let l_coords = vec![
1474 Coordinate::new_2d(0.0, 0.0),
1475 Coordinate::new_2d(2.0, 0.0),
1476 Coordinate::new_2d(2.0, 1.0),
1477 Coordinate::new_2d(1.0, 1.0),
1478 Coordinate::new_2d(1.0, 2.0),
1479 Coordinate::new_2d(0.0, 2.0),
1480 Coordinate::new_2d(0.0, 0.0),
1481 ];
1482 let l_ext = LineString::new(l_coords).expect("l");
1483 let l_shape = Polygon::new(l_ext, vec![]).expect("l poly");
1484 let b = make_square(0.5, 0.5, 2.0).expect("b");
1485
1486 let result = clip_polygons(&l_shape, &b, ClipOperation::Intersection).expect("clip");
1487 assert!(
1489 !result.is_empty(),
1490 "L-shape intersection should produce a result"
1491 );
1492 let total_area: f64 = result.iter().map(|p| poly_area(p)).sum();
1493 assert!(total_area > 0.0, "intersection area should be positive");
1494 assert!(
1495 total_area < 3.0,
1496 "intersection should be smaller than L-shape (area 3)"
1497 );
1498 }
1499
1500 #[test]
1505 fn test_shared_edge_intersection() {
1506 let a = make_square(0.0, 0.0, 1.0).expect("a");
1508 let b = make_square(1.0, 0.0, 1.0).expect("b");
1509 let result = clip_polygons(&a, &b, ClipOperation::Intersection).expect("clip");
1510 let total_area: f64 = result.iter().map(|p| poly_area(p)).sum();
1512 assert!(
1513 total_area < 0.01,
1514 "shared-edge intersection should be degenerate (area near 0), got {total_area}"
1515 );
1516 }
1517
1518 #[test]
1523 fn test_clip_multi_intersection() {
1524 let subjects = vec![make_square(0.0, 0.0, 10.0).expect("s")];
1525 let clips = vec![
1526 make_square(2.0, 2.0, 5.0).expect("c1"),
1527 make_square(3.0, 3.0, 5.0).expect("c2"),
1528 ];
1529 let result =
1530 clip_multi(&subjects, &clips, ClipOperation::Intersection).expect("clip_multi");
1531 let _ = result;
1534 }
1535
1536 #[test]
1541 fn test_bowtie_self_intersecting() {
1542 let bowtie_coords = vec![
1544 Coordinate::new_2d(0.0, 0.0),
1545 Coordinate::new_2d(1.0, 1.0),
1546 Coordinate::new_2d(1.0, 0.0),
1547 Coordinate::new_2d(0.0, 1.0),
1548 Coordinate::new_2d(0.0, 0.0),
1549 ];
1550 let bowtie_ext = LineString::new(bowtie_coords).expect("bowtie");
1551 let bowtie = Polygon::new(bowtie_ext, vec![]).expect("bowtie poly");
1552 let sq = make_square(0.0, 0.0, 2.0).expect("sq");
1553
1554 let result = clip_polygons(&bowtie, &sq, ClipOperation::Intersection);
1556 assert!(result.is_ok(), "clipping with bowtie should not error");
1557 }
1558
1559 #[test]
1564 fn test_intersection_with_hole() {
1565 let a = make_square_with_hole(0.0, 0.0, 10.0, 3.0, 3.0, 4.0).expect("a");
1566 let b = make_square(2.0, 2.0, 6.0).expect("b");
1567 let result = clip_polygons(&a, &b, ClipOperation::Intersection).expect("clip");
1568 let total_area: f64 = result.iter().map(|p| poly_area(p)).sum();
1572 assert!(total_area > 0.0, "should have some intersection");
1573 assert!(
1574 total_area < 36.1,
1575 "should be less than b's area, got {total_area}"
1576 );
1577 }
1578
1579 #[test]
1584 fn test_invalid_polygon_error() {
1585 let bad_coords = vec![
1587 Coordinate::new_2d(0.0, 0.0),
1588 Coordinate::new_2d(1.0, 0.0),
1589 Coordinate::new_2d(0.0, 0.0),
1590 ];
1591 let bad_ext = LineString::new(bad_coords);
1592 if let Ok(ext) = bad_ext {
1594 let poly = Polygon {
1595 exterior: ext,
1596 interiors: vec![],
1597 };
1598 let good = make_square(0.0, 0.0, 1.0).expect("good");
1599 let result = clip_polygons(&poly, &good, ClipOperation::Intersection);
1600 assert!(result.is_err(), "should reject polygon with < 4 coords");
1601 }
1602 }
1603
1604 #[test]
1609 fn test_area_invariant_intersection_le_min() {
1610 let a = make_square(0.0, 0.0, 3.0).expect("a");
1611 let b = make_square(1.0, 1.0, 3.0).expect("b");
1612 let result = clip_polygons(&a, &b, ClipOperation::Intersection).expect("clip");
1613 let ix_area: f64 = result.iter().map(|p| poly_area(p)).sum();
1614 let area_a = poly_area(&a);
1615 let area_b = poly_area(&b);
1616 let min_area = area_a.min(area_b);
1617 assert!(
1618 ix_area <= min_area + 0.1,
1619 "intersection area ({ix_area}) should be <= min({area_a}, {area_b}) = {min_area}"
1620 );
1621 }
1622
1623 #[test]
1624 fn test_area_invariant_difference_le_subject() {
1625 let a = make_square(0.0, 0.0, 3.0).expect("a");
1626 let b = make_square(1.0, 1.0, 3.0).expect("b");
1627 let result = clip_polygons(&a, &b, ClipOperation::Difference).expect("clip");
1628 let diff_area: f64 = result.iter().map(|p| poly_area(p)).sum();
1629 let area_a = poly_area(&a);
1630 assert!(
1631 diff_area <= area_a + 0.1,
1632 "difference area ({diff_area}) should be <= subject area ({area_a})"
1633 );
1634 }
1635}