1use geometry_coords::CoordinateScalar;
25use geometry_cs::{CartesianFamily, CoordinateSystem};
26use geometry_model::{MultiPolygon, Polygon};
27use geometry_tag::SameAs;
28use geometry_trait::{MultiPolygon as MultiPolygonTrait, PointMut, Polygon as PolygonTrait};
29
30use crate::traverse::TraversalError;
31
32use super::areal::{ArealOp, overlay as areal_overlay, overlay_multi as areal_overlay_multi};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum OverlayError {
37 Unsupported,
41}
42
43impl From<TraversalError> for OverlayError {
44 fn from(_: TraversalError) -> Self {
45 OverlayError::Unsupported
46 }
47}
48
49#[inline]
74#[must_use = "intersection can fail and the resulting geometry should be used"]
75pub fn intersection<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
76where
77 G1: PolygonTrait<Point = P>,
78 G2: PolygonTrait<Point = P>,
79 P: PointMut + Default + Copy,
80 P::Scalar: CoordinateScalar + Into<f64>,
81 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
82{
83 areal_overlay(g1, g2, ArealOp::Intersection)
84}
85
86#[inline]
111#[must_use = "union can fail and the resulting geometry should be used"]
112pub fn union_poly<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
113where
114 G1: PolygonTrait<Point = P>,
115 G2: PolygonTrait<Point = P>,
116 P: PointMut + Default + Copy,
117 P::Scalar: CoordinateScalar + Into<f64>,
118 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
119{
120 areal_overlay(g1, g2, ArealOp::Union)
121}
122
123#[inline]
136#[must_use = "union can fail and the resulting geometry should be used"]
137pub fn r#union<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
138where
139 G1: PolygonTrait<Point = P>,
140 G2: PolygonTrait<Point = P>,
141 P: PointMut + Default + Copy,
142 P::Scalar: CoordinateScalar + Into<f64>,
143 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
144{
145 union_poly(g1, g2)
146}
147
148#[inline]
173#[must_use = "difference can fail and the resulting geometry should be used"]
174pub fn difference<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
175where
176 G1: PolygonTrait<Point = P>,
177 G2: PolygonTrait<Point = P>,
178 P: PointMut + Default + Copy,
179 P::Scalar: CoordinateScalar + Into<f64>,
180 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
181{
182 areal_overlay(g1, g2, ArealOp::Difference)
183}
184
185#[inline]
210#[must_use = "symmetric difference can fail and the resulting geometry should be used"]
211pub fn sym_difference<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
212where
213 G1: PolygonTrait<Point = P>,
214 G2: PolygonTrait<Point = P>,
215 P: PointMut + Default + Copy,
216 P::Scalar: CoordinateScalar + Into<f64>,
217 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
218{
219 areal_overlay(g1, g2, ArealOp::SymDifference)
220}
221
222pub fn intersection_multi<G1, G2, P>(
237 g1: &G1,
238 g2: &G2,
239) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
240where
241 G1: MultiPolygonTrait<Point = P>,
242 G2: MultiPolygonTrait<Point = P>,
243 P: PointMut + Default + Copy,
244 P::Scalar: CoordinateScalar + Into<f64>,
245 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
246{
247 areal_overlay_multi(g1, g2, ArealOp::Intersection)
248}
249
250pub fn union_multi<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
256where
257 G1: MultiPolygonTrait<Point = P>,
258 G2: MultiPolygonTrait<Point = P>,
259 P: PointMut + Default + Copy,
260 P::Scalar: CoordinateScalar + Into<f64>,
261 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
262{
263 areal_overlay_multi(g1, g2, ArealOp::Union)
264}
265
266pub fn difference_multi<G1, G2, P>(
272 g1: &G1,
273 g2: &G2,
274) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
275where
276 G1: MultiPolygonTrait<Point = P>,
277 G2: MultiPolygonTrait<Point = P>,
278 P: PointMut + Default + Copy,
279 P::Scalar: CoordinateScalar + Into<f64>,
280 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
281{
282 areal_overlay_multi(g1, g2, ArealOp::Difference)
283}
284
285pub fn sym_difference_multi<G1, G2, P>(
291 g1: &G1,
292 g2: &G2,
293) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
294where
295 G1: MultiPolygonTrait<Point = P>,
296 G2: MultiPolygonTrait<Point = P>,
297 P: PointMut + Default + Copy,
298 P::Scalar: CoordinateScalar + Into<f64>,
299 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
300{
301 areal_overlay_multi(g1, g2, ArealOp::SymDifference)
302}
303
304#[cfg(test)]
305mod tests {
306 use super::{OverlayError, intersection, union_poly};
307 use geometry_algorithm::area;
308 use geometry_cs::Cartesian;
309 use geometry_model::{Point2D, Polygon, polygon};
310 use geometry_trait::{MultiPolygon as _, Polygon as _};
311
312 type P = Point2D<f64, Cartesian>;
313
314 fn close(a: f64, b: f64) -> bool {
315 (a - b).abs() <= 1e-5 * a.abs().max(b.abs()).max(1.0)
316 }
317
318 fn total_area(mp: &geometry_model::MultiPolygon<Polygon<P>>) -> f64 {
319 mp.polygons().map(|pg| area(pg).abs()).sum()
320 }
321
322 #[test]
323 fn intersection_of_offset_squares() {
324 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)]];
325 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)]];
326 let out = intersection(&a, &b).unwrap();
327 assert_eq!(out.polygons().count(), 1);
328 assert!(close(total_area(&out), 1.0), "area {}", total_area(&out));
329 }
330
331 #[test]
332 fn intersection_disjoint_is_empty() {
333 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)]];
334 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)]];
335 let out = intersection(&a, &b).unwrap();
336 assert_eq!(out.polygons().count(), 0);
337 }
338
339 #[test]
340 fn intersection_contained_is_inner() {
341 let big: Polygon<P> = polygon![[
342 (0.0, 0.0),
343 (10.0, 0.0),
344 (10.0, 10.0),
345 (0.0, 10.0),
346 (0.0, 0.0)
347 ]];
348 let small: Polygon<P> =
349 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
350 let out = intersection(&big, &small).unwrap();
351 assert_eq!(out.polygons().count(), 1);
352 assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
353 }
354
355 #[test]
356 fn union_of_offset_squares_area() {
357 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)]];
359 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)]];
360 let out = union_poly(&a, &b).unwrap();
361 assert_eq!(out.polygons().count(), 1);
362 assert!(close(total_area(&out), 7.0), "area {}", total_area(&out));
363 }
364
365 #[test]
366 fn union_disjoint_is_two_polygons() {
367 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)]];
368 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)]];
369 let out = union_poly(&a, &b).unwrap();
370 assert_eq!(out.polygons().count(), 2);
371 assert!(close(total_area(&out), 2.0), "area {}", total_area(&out));
372 }
373
374 #[test]
375 fn difference_of_offset_squares_area() {
376 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)]];
378 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)]];
379 let out = super::difference(&a, &b).unwrap();
380 assert_eq!(out.polygons().count(), 1);
381 assert!(close(total_area(&out), 3.0), "area {}", total_area(&out));
382 }
383
384 #[test]
385 fn difference_disjoint_is_first_whole() {
386 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)]];
387 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)]];
388 let out = super::difference(&a, &b).unwrap();
389 assert_eq!(out.polygons().count(), 1);
390 assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
391 }
392
393 #[test]
394 fn difference_a_inside_b_is_empty() {
395 let big: Polygon<P> = polygon![[
396 (0.0, 0.0),
397 (10.0, 0.0),
398 (10.0, 10.0),
399 (0.0, 10.0),
400 (0.0, 0.0)
401 ]];
402 let small: Polygon<P> =
403 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
404 let out = super::difference(&small, &big).unwrap();
405 assert_eq!(out.polygons().count(), 0);
406 }
407
408 #[test]
409 fn difference_with_contained_subtrahend_emits_a_hole() {
410 let big: Polygon<P> = polygon![[
411 (0.0, 0.0),
412 (10.0, 0.0),
413 (10.0, 10.0),
414 (0.0, 10.0),
415 (0.0, 0.0)
416 ]];
417 let small: Polygon<P> =
418 polygon![[(3.0, 3.0), (5.0, 3.0), (5.0, 5.0), (3.0, 5.0), (3.0, 3.0)]];
419 let difference = super::difference(&big, &small).unwrap();
420 assert_eq!(difference.polygons().count(), 1);
421 assert_eq!(difference.polygons().next().unwrap().interiors().count(), 1);
422 assert!(close(total_area(&difference), 96.0));
423 assert!(close(
424 total_area(&super::sym_difference(&big, &small).unwrap()),
425 96.0
426 ));
427 }
428
429 #[test]
430 fn input_with_holes_participates_in_all_operations() {
431 let donut: Polygon<P> = polygon![
432 [
433 (0.0, 0.0),
434 (10.0, 0.0),
435 (10.0, 10.0),
436 (0.0, 10.0),
437 (0.0, 0.0)
438 ],
439 [(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
440 ];
441 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)]];
442 assert!(close(total_area(&intersection(&donut, &sq).unwrap()), 20.0));
443 assert!(close(total_area(&union_poly(&donut, &sq).unwrap()), 100.0));
444 assert!(close(
445 total_area(&super::difference(&donut, &sq).unwrap()),
446 64.0
447 ));
448 assert!(close(
449 total_area(&super::sym_difference(&donut, &sq).unwrap()),
450 80.0
451 ));
452 }
453
454 #[test]
455 fn out_of_range_coordinates_are_refused_not_silently_wrong() {
456 let a: Polygon<P> = polygon![[
462 (0.0, 0.0),
463 (2e14, 0.0),
464 (2e14, 2e14),
465 (0.0, 2e14),
466 (0.0, 0.0)
467 ]];
468 let b: Polygon<P> = polygon![[
469 (1e14, 1e14),
470 (3e14, 1e14),
471 (3e14, 3e14),
472 (1e14, 3e14),
473 (1e14, 1e14)
474 ]];
475 assert_eq!(intersection(&a, &b), Err(OverlayError::Unsupported));
476 assert_eq!(union_poly(&a, &b), Err(OverlayError::Unsupported));
477 assert_eq!(super::difference(&a, &b), Err(OverlayError::Unsupported));
478 assert_eq!(
479 super::sym_difference(&a, &b),
480 Err(OverlayError::Unsupported)
481 );
482 }
483
484 #[test]
485 fn sym_difference_of_offset_squares_area() {
486 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)]];
488 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)]];
489 let out = super::sym_difference(&a, &b).unwrap();
490 assert!(close(total_area(&out), 6.0), "area {}", total_area(&out));
491 }
492
493 #[test]
494 fn union_contained_is_outer() {
495 let big: Polygon<P> = polygon![[
496 (0.0, 0.0),
497 (10.0, 0.0),
498 (10.0, 10.0),
499 (0.0, 10.0),
500 (0.0, 0.0)
501 ]];
502 let small: Polygon<P> =
503 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
504 let out = union_poly(&big, &small).unwrap();
505 assert_eq!(out.polygons().count(), 1);
506 assert!(close(total_area(&out), 100.0), "area {}", total_area(&out));
507 }
508}