1use geometry_coords::CoordinateScalar;
25use geometry_cs::{CartesianFamily, CoordinateSystem};
26use geometry_model::{MultiPolygon, Polygon};
27use geometry_tag::SameAs;
28use geometry_trait::{PointMut, Polygon as PolygonTrait};
29
30use crate::traverse::TraversalError;
31
32use super::areal::{ArealOp, overlay as areal_overlay};
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
222#[cfg(test)]
223mod tests {
224 use super::{OverlayError, intersection, union_poly};
225 use geometry_algorithm::area;
226 use geometry_cs::Cartesian;
227 use geometry_model::{Point2D, Polygon, polygon};
228 use geometry_trait::{MultiPolygon as _, Polygon as _};
229
230 type P = Point2D<f64, Cartesian>;
231
232 fn close(a: f64, b: f64) -> bool {
233 (a - b).abs() <= 1e-5 * a.abs().max(b.abs()).max(1.0)
234 }
235
236 fn total_area(mp: &geometry_model::MultiPolygon<Polygon<P>>) -> f64 {
237 mp.polygons().map(|pg| area(pg).abs()).sum()
238 }
239
240 #[test]
241 fn intersection_of_offset_squares() {
242 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)]];
243 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)]];
244 let out = intersection(&a, &b).unwrap();
245 assert_eq!(out.polygons().count(), 1);
246 assert!(close(total_area(&out), 1.0), "area {}", total_area(&out));
247 }
248
249 #[test]
250 fn intersection_disjoint_is_empty() {
251 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)]];
252 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)]];
253 let out = intersection(&a, &b).unwrap();
254 assert_eq!(out.polygons().count(), 0);
255 }
256
257 #[test]
258 fn intersection_contained_is_inner() {
259 let big: Polygon<P> = polygon![[
260 (0.0, 0.0),
261 (10.0, 0.0),
262 (10.0, 10.0),
263 (0.0, 10.0),
264 (0.0, 0.0)
265 ]];
266 let small: Polygon<P> =
267 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
268 let out = intersection(&big, &small).unwrap();
269 assert_eq!(out.polygons().count(), 1);
270 assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
271 }
272
273 #[test]
274 fn union_of_offset_squares_area() {
275 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)]];
277 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)]];
278 let out = union_poly(&a, &b).unwrap();
279 assert_eq!(out.polygons().count(), 1);
280 assert!(close(total_area(&out), 7.0), "area {}", total_area(&out));
281 }
282
283 #[test]
284 fn union_disjoint_is_two_polygons() {
285 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)]];
286 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)]];
287 let out = union_poly(&a, &b).unwrap();
288 assert_eq!(out.polygons().count(), 2);
289 assert!(close(total_area(&out), 2.0), "area {}", total_area(&out));
290 }
291
292 #[test]
293 fn difference_of_offset_squares_area() {
294 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)]];
296 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)]];
297 let out = super::difference(&a, &b).unwrap();
298 assert_eq!(out.polygons().count(), 1);
299 assert!(close(total_area(&out), 3.0), "area {}", total_area(&out));
300 }
301
302 #[test]
303 fn difference_disjoint_is_first_whole() {
304 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)]];
305 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)]];
306 let out = super::difference(&a, &b).unwrap();
307 assert_eq!(out.polygons().count(), 1);
308 assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
309 }
310
311 #[test]
312 fn difference_a_inside_b_is_empty() {
313 let big: Polygon<P> = polygon![[
314 (0.0, 0.0),
315 (10.0, 0.0),
316 (10.0, 10.0),
317 (0.0, 10.0),
318 (0.0, 0.0)
319 ]];
320 let small: Polygon<P> =
321 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
322 let out = super::difference(&small, &big).unwrap();
323 assert_eq!(out.polygons().count(), 0);
324 }
325
326 #[test]
327 fn difference_with_contained_subtrahend_emits_a_hole() {
328 let big: Polygon<P> = polygon![[
329 (0.0, 0.0),
330 (10.0, 0.0),
331 (10.0, 10.0),
332 (0.0, 10.0),
333 (0.0, 0.0)
334 ]];
335 let small: Polygon<P> =
336 polygon![[(3.0, 3.0), (5.0, 3.0), (5.0, 5.0), (3.0, 5.0), (3.0, 3.0)]];
337 let difference = super::difference(&big, &small).unwrap();
338 assert_eq!(difference.polygons().count(), 1);
339 assert_eq!(difference.polygons().next().unwrap().interiors().count(), 1);
340 assert!(close(total_area(&difference), 96.0));
341 assert!(close(
342 total_area(&super::sym_difference(&big, &small).unwrap()),
343 96.0
344 ));
345 }
346
347 #[test]
348 fn input_with_holes_participates_in_all_operations() {
349 let donut: Polygon<P> = polygon![
350 [
351 (0.0, 0.0),
352 (10.0, 0.0),
353 (10.0, 10.0),
354 (0.0, 10.0),
355 (0.0, 0.0)
356 ],
357 [(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
358 ];
359 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)]];
360 assert!(close(total_area(&intersection(&donut, &sq).unwrap()), 20.0));
361 assert!(close(total_area(&union_poly(&donut, &sq).unwrap()), 100.0));
362 assert!(close(
363 total_area(&super::difference(&donut, &sq).unwrap()),
364 64.0
365 ));
366 assert!(close(
367 total_area(&super::sym_difference(&donut, &sq).unwrap()),
368 80.0
369 ));
370 }
371
372 #[test]
373 fn out_of_range_coordinates_are_refused_not_silently_wrong() {
374 let a: Polygon<P> = polygon![[
380 (0.0, 0.0),
381 (2e14, 0.0),
382 (2e14, 2e14),
383 (0.0, 2e14),
384 (0.0, 0.0)
385 ]];
386 let b: Polygon<P> = polygon![[
387 (1e14, 1e14),
388 (3e14, 1e14),
389 (3e14, 3e14),
390 (1e14, 3e14),
391 (1e14, 1e14)
392 ]];
393 assert_eq!(intersection(&a, &b), Err(OverlayError::Unsupported));
394 assert_eq!(union_poly(&a, &b), Err(OverlayError::Unsupported));
395 assert_eq!(super::difference(&a, &b), Err(OverlayError::Unsupported));
396 assert_eq!(
397 super::sym_difference(&a, &b),
398 Err(OverlayError::Unsupported)
399 );
400 }
401
402 #[test]
403 fn sym_difference_of_offset_squares_area() {
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)]];
406 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)]];
407 let out = super::sym_difference(&a, &b).unwrap();
408 assert!(close(total_area(&out), 6.0), "area {}", total_area(&out));
409 }
410
411 #[test]
412 fn union_contained_is_outer() {
413 let big: Polygon<P> = polygon![[
414 (0.0, 0.0),
415 (10.0, 0.0),
416 (10.0, 10.0),
417 (0.0, 10.0),
418 (0.0, 0.0)
419 ]];
420 let small: Polygon<P> =
421 polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
422 let out = union_poly(&big, &small).unwrap();
423 assert_eq!(out.polygons().count(), 1);
424 assert!(close(total_area(&out), 100.0), "area {}", total_area(&out));
425 }
426}