1use alloc::vec::Vec;
32
33use geometry_coords::CoordinateScalar;
34use geometry_cs::{CartesianFamily, CoordinateSystem};
35use geometry_model::{MultiPolygon, Polygon, Ring};
36use geometry_tag::SameAs;
37use geometry_trait::{Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait};
38
39use crate::assemble::assemble_multipolygon;
40use crate::predicate::range_guard::polygon_in_range;
41use crate::traverse::{OverlayOp, TraversalError, enrich, traverse};
42use crate::turn::{RingKind, get_turns_ring_ring};
43
44fn both_in_range<G1, G2, P>(g1: &G1, g2: &G2) -> bool
51where
52 G1: PolygonTrait<Point = P>,
53 G2: PolygonTrait<Point = P>,
54 P: Point,
55 P::Scalar: CoordinateScalar + Into<f64>,
56{
57 polygon_in_range(g1) && polygon_in_range(g2)
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum OverlayError {
63 Unsupported,
67}
68
69impl From<TraversalError> for OverlayError {
70 fn from(_: TraversalError) -> Self {
71 OverlayError::Unsupported
72 }
73}
74
75pub fn intersection<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
101where
102 G1: PolygonTrait<Point = P>,
103 G2: PolygonTrait<Point = P>,
104 P: PointMut + Default + Copy,
105 P::Scalar: CoordinateScalar + Into<f64>,
106 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
107{
108 if has_holes(g1) || has_holes(g2) || !both_in_range(g1, g2) {
109 return Err(OverlayError::Unsupported);
110 }
111 let (r1, r2) = (g1.exterior(), g2.exterior());
112 let turns = get_turns_ring_ring(r1, 0, RingKind::Exterior, r2, 1, RingKind::Exterior);
113
114 if turns.is_empty() {
115 return Ok(containment_result(g1, g2, OverlayOp::Intersection));
119 }
120
121 let enriched = enrich(r1, r2, &turns);
122 let rings = traverse(&enriched, &turns, OverlayOp::Intersection)?;
123 Ok(assemble_multipolygon(&rings))
124}
125
126fn has_holes<G, P>(g: &G) -> bool
131where
132 G: PolygonTrait<Point = P>,
133 P: Point,
134{
135 g.interiors().next().is_some()
136}
137
138pub fn union_poly<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
163where
164 G1: PolygonTrait<Point = P>,
165 G2: PolygonTrait<Point = P>,
166 P: PointMut + Default + Copy,
167 P::Scalar: CoordinateScalar + Into<f64>,
168 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
169{
170 if has_holes(g1) || has_holes(g2) || !both_in_range(g1, g2) {
171 return Err(OverlayError::Unsupported);
172 }
173 let (r1, r2) = (g1.exterior(), g2.exterior());
174 let turns = get_turns_ring_ring(r1, 0, RingKind::Exterior, r2, 1, RingKind::Exterior);
175
176 if turns.is_empty() {
177 return Ok(union_no_crossing(g1, g2));
180 }
181
182 let enriched = enrich(r1, r2, &turns);
183 let rings = traverse(&enriched, &turns, OverlayOp::Union)?;
184 Ok(assemble_multipolygon(&rings))
185}
186
187pub fn difference<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
213where
214 G1: PolygonTrait<Point = P>,
215 G2: PolygonTrait<Point = P>,
216 P: PointMut + Default + Copy,
217 P::Scalar: CoordinateScalar + Into<f64>,
218 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
219{
220 if has_holes(g1) || has_holes(g2) || !both_in_range(g1, g2) {
221 return Err(OverlayError::Unsupported);
222 }
223 let r1 = g1.exterior();
224 let r2 = g2.exterior();
225 let turns = get_turns_ring_ring(r1, 0, RingKind::Exterior, r2, 1, RingKind::Exterior);
226
227 if turns.is_empty() {
228 return difference_no_crossing(g1, g2);
232 }
233
234 let enriched = enrich(r1, r2, &turns);
235 let rings = traverse(&enriched, &turns, OverlayOp::Difference)?;
236 Ok(assemble_multipolygon(&rings))
237}
238
239pub fn sym_difference<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
265where
266 G1: PolygonTrait<Point = P>,
267 G2: PolygonTrait<Point = P>,
268 P: PointMut + Default + Copy,
269 P::Scalar: CoordinateScalar + Into<f64>,
270 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
271{
272 let a_minus_b = difference(g1, g2)?;
273 let b_minus_a = difference(g2, g1)?;
274 let mut polygons: Vec<Polygon<P>> = a_minus_b.0;
278 polygons.extend(b_minus_a.0);
279 Ok(MultiPolygon(polygons))
280}
281
282fn difference_no_crossing<G1, G2, P>(
291 g1: &G1,
292 g2: &G2,
293) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
294where
295 G1: PolygonTrait<Point = P>,
296 G2: PolygonTrait<Point = P>,
297 P: PointMut + Default + Copy,
298 P::Scalar: CoordinateScalar,
299 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
300{
301 if first_vertex_within(g1, g2) {
302 return Ok(MultiPolygon(Vec::new()));
304 }
305 if first_vertex_within(g2, g1) {
306 return Err(OverlayError::Unsupported);
308 }
309 Ok(MultiPolygon(Vec::from([clone_polygon(g1)])))
311}
312
313fn containment_result<G1, G2, P>(g1: &G1, g2: &G2, _op: OverlayOp) -> MultiPolygon<Polygon<P>>
316where
317 G1: PolygonTrait<Point = P>,
318 G2: PolygonTrait<Point = P>,
319 P: PointMut + Default + Copy,
320 P::Scalar: CoordinateScalar,
321 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
322{
323 if first_vertex_within(g1, g2) {
324 MultiPolygon(Vec::from([clone_polygon(g1)]))
325 } else if first_vertex_within(g2, g1) {
326 MultiPolygon(Vec::from([clone_polygon(g2)]))
327 } else {
328 MultiPolygon(Vec::new())
329 }
330}
331
332fn union_no_crossing<G1, G2, P>(g1: &G1, g2: &G2) -> MultiPolygon<Polygon<P>>
335where
336 G1: PolygonTrait<Point = P>,
337 G2: PolygonTrait<Point = P>,
338 P: PointMut + Default + Copy,
339 P::Scalar: CoordinateScalar,
340 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
341{
342 if first_vertex_within(g1, g2) {
343 MultiPolygon(Vec::from([clone_polygon(g2)]))
344 } else if first_vertex_within(g2, g1) {
345 MultiPolygon(Vec::from([clone_polygon(g1)]))
346 } else {
347 MultiPolygon(Vec::from([clone_polygon(g1), clone_polygon(g2)]))
348 }
349}
350
351fn first_vertex_within<GI, GO, P>(inner: &GI, outer: &GO) -> bool
353where
354 GI: PolygonTrait<Point = P>,
355 GO: PolygonTrait<Point = P>,
356 P: PointMut + Default + Copy,
357 P::Scalar: CoordinateScalar,
358 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
359{
360 let Some(v) = inner.exterior().points().next() else {
361 return false;
362 };
363 let outer_model = clone_polygon(outer);
366 geometry_algorithm::within(v, &outer_model)
367}
368
369fn clone_polygon<G, P>(g: &G) -> Polygon<P>
372where
373 G: PolygonTrait<Point = P>,
374 P: Point + Copy,
375{
376 let outer: Ring<P> = Ring::from_vec(g.exterior().points().copied().collect());
377 let inners: Vec<Ring<P>> = g
378 .interiors()
379 .map(|r| Ring::from_vec(r.points().copied().collect()))
380 .collect();
381 Polygon::with_inners(outer, inners)
382}
383
384#[cfg(test)]
385mod tests {
386 use super::{OverlayError, intersection, union_poly};
387 use geometry_algorithm::ring_area;
388 use geometry_cs::Cartesian;
389 use geometry_model::{Point2D, Polygon, polygon};
390 use geometry_trait::{MultiPolygon as _, Polygon as _};
391
392 type P = Point2D<f64, Cartesian>;
393
394 fn close(a: f64, b: f64) -> bool {
395 (a - b).abs() <= 1e-5 * a.abs().max(b.abs()).max(1.0)
396 }
397
398 fn total_area(mp: &geometry_model::MultiPolygon<Polygon<P>>) -> f64 {
399 mp.polygons().map(|pg| ring_area(pg.exterior()).abs()).sum()
400 }
401
402 #[test]
403 fn intersection_of_offset_squares() {
404 let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
405 let b: Polygon<P> = polygon![[(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0), (1.0, 1.0)]];
406 let out = intersection(&a, &b).unwrap();
407 assert_eq!(out.polygons().count(), 1);
408 assert!(close(total_area(&out), 1.0), "area {}", total_area(&out));
409 }
410
411 #[test]
412 fn intersection_disjoint_is_empty() {
413 let a: Polygon<P> = polygon![[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)]];
414 let b: Polygon<P> = polygon![[(5.0, 5.0), (6.0, 5.0), (6.0, 6.0), (5.0, 6.0), (5.0, 5.0)]];
415 let out = intersection(&a, &b).unwrap();
416 assert_eq!(out.polygons().count(), 0);
417 }
418
419 #[test]
420 fn intersection_contained_is_inner() {
421 let big: Polygon<P> = polygon![[
422 (0.0, 0.0),
423 (10.0, 0.0),
424 (10.0, 10.0),
425 (0.0, 10.0),
426 (0.0, 0.0)
427 ]];
428 let small: Polygon<P> =
429 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
430 let out = intersection(&big, &small).unwrap();
431 assert_eq!(out.polygons().count(), 1);
432 assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
433 }
434
435 #[test]
436 fn union_of_offset_squares_area() {
437 let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
439 let b: Polygon<P> = polygon![[(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0), (1.0, 1.0)]];
440 let out = union_poly(&a, &b).unwrap();
441 assert_eq!(out.polygons().count(), 1);
442 assert!(close(total_area(&out), 7.0), "area {}", total_area(&out));
443 }
444
445 #[test]
446 fn union_disjoint_is_two_polygons() {
447 let a: Polygon<P> = polygon![[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)]];
448 let b: Polygon<P> = polygon![[(5.0, 5.0), (6.0, 5.0), (6.0, 6.0), (5.0, 6.0), (5.0, 5.0)]];
449 let out = union_poly(&a, &b).unwrap();
450 assert_eq!(out.polygons().count(), 2);
451 assert!(close(total_area(&out), 2.0), "area {}", total_area(&out));
452 }
453
454 #[test]
455 fn difference_of_offset_squares_area() {
456 let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
458 let b: Polygon<P> = polygon![[(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0), (1.0, 1.0)]];
459 let out = super::difference(&a, &b).unwrap();
460 assert_eq!(out.polygons().count(), 1);
461 assert!(close(total_area(&out), 3.0), "area {}", total_area(&out));
462 }
463
464 #[test]
465 fn difference_disjoint_is_first_whole() {
466 let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
467 let b: Polygon<P> = polygon![[(5.0, 5.0), (6.0, 5.0), (6.0, 6.0), (5.0, 6.0), (5.0, 5.0)]];
468 let out = super::difference(&a, &b).unwrap();
469 assert_eq!(out.polygons().count(), 1);
470 assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
471 }
472
473 #[test]
474 fn difference_a_inside_b_is_empty() {
475 let big: Polygon<P> = polygon![[
476 (0.0, 0.0),
477 (10.0, 0.0),
478 (10.0, 10.0),
479 (0.0, 10.0),
480 (0.0, 0.0)
481 ]];
482 let small: Polygon<P> =
483 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
484 let out = super::difference(&small, &big).unwrap();
485 assert_eq!(out.polygons().count(), 0);
486 }
487
488 #[test]
489 fn difference_with_contained_subtrahend_is_refused_not_over_reported() {
490 let big: Polygon<P> = polygon![[
494 (0.0, 0.0),
495 (10.0, 0.0),
496 (10.0, 10.0),
497 (0.0, 10.0),
498 (0.0, 0.0)
499 ]];
500 let small: Polygon<P> =
501 polygon![[(3.0, 3.0), (5.0, 3.0), (5.0, 5.0), (3.0, 5.0), (3.0, 3.0)]];
502 assert_eq!(
503 super::difference(&big, &small),
504 Err(OverlayError::Unsupported)
505 );
506 assert_eq!(
507 super::sym_difference(&big, &small),
508 Err(OverlayError::Unsupported)
509 );
510 }
511
512 #[test]
513 fn input_with_holes_is_refused_not_silently_wrong() {
514 let donut: Polygon<P> = polygon![
517 [
518 (0.0, 0.0),
519 (10.0, 0.0),
520 (10.0, 10.0),
521 (0.0, 10.0),
522 (0.0, 0.0)
523 ],
524 [(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
525 ];
526 let sq: Polygon<P> = polygon![[(2.0, 2.0), (8.0, 2.0), (8.0, 8.0), (2.0, 8.0), (2.0, 2.0)]];
527 assert_eq!(intersection(&donut, &sq), Err(OverlayError::Unsupported));
528 assert_eq!(union_poly(&donut, &sq), Err(OverlayError::Unsupported));
529 assert_eq!(
530 super::difference(&donut, &sq),
531 Err(OverlayError::Unsupported)
532 );
533 assert_eq!(
534 super::sym_difference(&donut, &sq),
535 Err(OverlayError::Unsupported)
536 );
537 }
538
539 #[test]
540 fn out_of_range_coordinates_are_refused_not_silently_wrong() {
541 let a: Polygon<P> = polygon![[
547 (0.0, 0.0),
548 (2e14, 0.0),
549 (2e14, 2e14),
550 (0.0, 2e14),
551 (0.0, 0.0)
552 ]];
553 let b: Polygon<P> = polygon![[
554 (1e14, 1e14),
555 (3e14, 1e14),
556 (3e14, 3e14),
557 (1e14, 3e14),
558 (1e14, 1e14)
559 ]];
560 assert_eq!(intersection(&a, &b), Err(OverlayError::Unsupported));
561 assert_eq!(union_poly(&a, &b), Err(OverlayError::Unsupported));
562 assert_eq!(super::difference(&a, &b), Err(OverlayError::Unsupported));
563 assert_eq!(
564 super::sym_difference(&a, &b),
565 Err(OverlayError::Unsupported)
566 );
567 }
568
569 #[test]
570 fn sym_difference_of_offset_squares_area() {
571 let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
573 let b: Polygon<P> = polygon![[(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0), (1.0, 1.0)]];
574 let out = super::sym_difference(&a, &b).unwrap();
575 assert!(close(total_area(&out), 6.0), "area {}", total_area(&out));
576 }
577
578 #[test]
579 fn union_contained_is_outer() {
580 let big: Polygon<P> = polygon![[
581 (0.0, 0.0),
582 (10.0, 0.0),
583 (10.0, 10.0),
584 (0.0, 10.0),
585 (0.0, 0.0)
586 ]];
587 let small: Polygon<P> =
588 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
589 let out = union_poly(&big, &small).unwrap();
590 assert_eq!(out.polygons().count(), 1);
591 assert!(close(total_area(&out), 100.0), "area {}", total_area(&out));
592 }
593}