use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_model::{MultiPolygon, Polygon};
use geometry_tag::SameAs;
use geometry_trait::{PointMut, Polygon as PolygonTrait};
use crate::traverse::TraversalError;
use super::areal::{ArealOp, overlay as areal_overlay};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverlayError {
Unsupported,
}
impl From<TraversalError> for OverlayError {
fn from(_: TraversalError) -> Self {
OverlayError::Unsupported
}
}
#[inline]
#[must_use = "intersection can fail and the resulting geometry should be used"]
pub fn intersection<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
areal_overlay(g1, g2, ArealOp::Intersection)
}
#[inline]
#[must_use = "union can fail and the resulting geometry should be used"]
pub fn union_poly<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
areal_overlay(g1, g2, ArealOp::Union)
}
#[inline]
#[must_use = "union can fail and the resulting geometry should be used"]
pub fn r#union<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
union_poly(g1, g2)
}
#[inline]
#[must_use = "difference can fail and the resulting geometry should be used"]
pub fn difference<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
areal_overlay(g1, g2, ArealOp::Difference)
}
#[inline]
#[must_use = "symmetric difference can fail and the resulting geometry should be used"]
pub fn sym_difference<G1, G2, P>(g1: &G1, g2: &G2) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar + Into<f64>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
areal_overlay(g1, g2, ArealOp::SymDifference)
}
#[cfg(test)]
mod tests {
use super::{OverlayError, intersection, union_poly};
use geometry_algorithm::area;
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Polygon, polygon};
use geometry_trait::{MultiPolygon as _, Polygon as _};
type P = Point2D<f64, Cartesian>;
fn close(a: f64, b: f64) -> bool {
(a - b).abs() <= 1e-5 * a.abs().max(b.abs()).max(1.0)
}
fn total_area(mp: &geometry_model::MultiPolygon<Polygon<P>>) -> f64 {
mp.polygons().map(|pg| area(pg).abs()).sum()
}
#[test]
fn intersection_of_offset_squares() {
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)]];
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)]];
let out = intersection(&a, &b).unwrap();
assert_eq!(out.polygons().count(), 1);
assert!(close(total_area(&out), 1.0), "area {}", total_area(&out));
}
#[test]
fn intersection_disjoint_is_empty() {
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)]];
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)]];
let out = intersection(&a, &b).unwrap();
assert_eq!(out.polygons().count(), 0);
}
#[test]
fn intersection_contained_is_inner() {
let big: Polygon<P> = polygon![[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
]];
let small: Polygon<P> =
polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
let out = intersection(&big, &small).unwrap();
assert_eq!(out.polygons().count(), 1);
assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
}
#[test]
fn union_of_offset_squares_area() {
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)]];
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)]];
let out = union_poly(&a, &b).unwrap();
assert_eq!(out.polygons().count(), 1);
assert!(close(total_area(&out), 7.0), "area {}", total_area(&out));
}
#[test]
fn union_disjoint_is_two_polygons() {
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)]];
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)]];
let out = union_poly(&a, &b).unwrap();
assert_eq!(out.polygons().count(), 2);
assert!(close(total_area(&out), 2.0), "area {}", total_area(&out));
}
#[test]
fn difference_of_offset_squares_area() {
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)]];
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)]];
let out = super::difference(&a, &b).unwrap();
assert_eq!(out.polygons().count(), 1);
assert!(close(total_area(&out), 3.0), "area {}", total_area(&out));
}
#[test]
fn difference_disjoint_is_first_whole() {
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)]];
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)]];
let out = super::difference(&a, &b).unwrap();
assert_eq!(out.polygons().count(), 1);
assert!(close(total_area(&out), 4.0), "area {}", total_area(&out));
}
#[test]
fn difference_a_inside_b_is_empty() {
let big: Polygon<P> = polygon![[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
]];
let small: Polygon<P> =
polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
let out = super::difference(&small, &big).unwrap();
assert_eq!(out.polygons().count(), 0);
}
#[test]
fn difference_with_contained_subtrahend_emits_a_hole() {
let big: Polygon<P> = polygon![[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
]];
let small: Polygon<P> =
polygon![[(3.0, 3.0), (5.0, 3.0), (5.0, 5.0), (3.0, 5.0), (3.0, 3.0)]];
let difference = super::difference(&big, &small).unwrap();
assert_eq!(difference.polygons().count(), 1);
assert_eq!(difference.polygons().next().unwrap().interiors().count(), 1);
assert!(close(total_area(&difference), 96.0));
assert!(close(
total_area(&super::sym_difference(&big, &small).unwrap()),
96.0
));
}
#[test]
fn input_with_holes_participates_in_all_operations() {
let donut: Polygon<P> = polygon![
[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
],
[(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
];
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)]];
assert!(close(total_area(&intersection(&donut, &sq).unwrap()), 20.0));
assert!(close(total_area(&union_poly(&donut, &sq).unwrap()), 100.0));
assert!(close(
total_area(&super::difference(&donut, &sq).unwrap()),
64.0
));
assert!(close(
total_area(&super::sym_difference(&donut, &sq).unwrap()),
80.0
));
}
#[test]
fn out_of_range_coordinates_are_refused_not_silently_wrong() {
let a: Polygon<P> = polygon![[
(0.0, 0.0),
(2e14, 0.0),
(2e14, 2e14),
(0.0, 2e14),
(0.0, 0.0)
]];
let b: Polygon<P> = polygon![[
(1e14, 1e14),
(3e14, 1e14),
(3e14, 3e14),
(1e14, 3e14),
(1e14, 1e14)
]];
assert_eq!(intersection(&a, &b), Err(OverlayError::Unsupported));
assert_eq!(union_poly(&a, &b), Err(OverlayError::Unsupported));
assert_eq!(super::difference(&a, &b), Err(OverlayError::Unsupported));
assert_eq!(
super::sym_difference(&a, &b),
Err(OverlayError::Unsupported)
);
}
#[test]
fn sym_difference_of_offset_squares_area() {
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)]];
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)]];
let out = super::sym_difference(&a, &b).unwrap();
assert!(close(total_area(&out), 6.0), "area {}", total_area(&out));
}
#[test]
fn union_contained_is_outer() {
let big: Polygon<P> = polygon![[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
]];
let small: Polygon<P> =
polygon![[(2.0, 2.0), (4.0, 2.0), (4.0, 4.0), (2.0, 4.0), (2.0, 2.0)]];
let out = union_poly(&big, &small).unwrap();
assert_eq!(out.polygons().count(), 1);
assert!(close(total_area(&out), 100.0), "area {}", total_area(&out));
}
}