use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_model::{MultiPolygon, Polygon, Ring};
use geometry_tag::SameAs;
use geometry_trait::{Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait};
use crate::assemble::assemble_multipolygon;
use crate::predicate::range_guard::polygon_in_range;
use crate::traverse::{OverlayOp, TraversalError, enrich, traverse};
use crate::turn::{RingKind, get_turns_ring_ring};
fn both_in_range<G1, G2, P>(g1: &G1, g2: &G2) -> bool
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: Point,
P::Scalar: CoordinateScalar + Into<f64>,
{
polygon_in_range(g1) && polygon_in_range(g2)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverlayError {
Unsupported,
}
impl From<TraversalError> for OverlayError {
fn from(_: TraversalError) -> Self {
OverlayError::Unsupported
}
}
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>,
{
if has_holes(g1) || has_holes(g2) || !both_in_range(g1, g2) {
return Err(OverlayError::Unsupported);
}
let (r1, r2) = (g1.exterior(), g2.exterior());
let turns = get_turns_ring_ring(r1, 0, RingKind::Exterior, r2, 1, RingKind::Exterior);
if turns.is_empty() {
return Ok(containment_result(g1, g2, OverlayOp::Intersection));
}
let enriched = enrich(r1, r2, &turns);
let rings = traverse(&enriched, &turns, OverlayOp::Intersection)?;
Ok(assemble_multipolygon(&rings))
}
fn has_holes<G, P>(g: &G) -> bool
where
G: PolygonTrait<Point = P>,
P: Point,
{
g.interiors().next().is_some()
}
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>,
{
if has_holes(g1) || has_holes(g2) || !both_in_range(g1, g2) {
return Err(OverlayError::Unsupported);
}
let (r1, r2) = (g1.exterior(), g2.exterior());
let turns = get_turns_ring_ring(r1, 0, RingKind::Exterior, r2, 1, RingKind::Exterior);
if turns.is_empty() {
return Ok(union_no_crossing(g1, g2));
}
let enriched = enrich(r1, r2, &turns);
let rings = traverse(&enriched, &turns, OverlayOp::Union)?;
Ok(assemble_multipolygon(&rings))
}
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>,
{
if has_holes(g1) || has_holes(g2) || !both_in_range(g1, g2) {
return Err(OverlayError::Unsupported);
}
let r1 = g1.exterior();
let r2 = g2.exterior();
let turns = get_turns_ring_ring(r1, 0, RingKind::Exterior, r2, 1, RingKind::Exterior);
if turns.is_empty() {
return difference_no_crossing(g1, g2);
}
let enriched = enrich(r1, r2, &turns);
let rings = traverse(&enriched, &turns, OverlayOp::Difference)?;
Ok(assemble_multipolygon(&rings))
}
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>,
{
let a_minus_b = difference(g1, g2)?;
let b_minus_a = difference(g2, g1)?;
let mut polygons: Vec<Polygon<P>> = a_minus_b.0;
polygons.extend(b_minus_a.0);
Ok(MultiPolygon(polygons))
}
fn difference_no_crossing<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,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
if first_vertex_within(g1, g2) {
return Ok(MultiPolygon(Vec::new()));
}
if first_vertex_within(g2, g1) {
return Err(OverlayError::Unsupported);
}
Ok(MultiPolygon(Vec::from([clone_polygon(g1)])))
}
fn containment_result<G1, G2, P>(g1: &G1, g2: &G2, _op: OverlayOp) -> MultiPolygon<Polygon<P>>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
if first_vertex_within(g1, g2) {
MultiPolygon(Vec::from([clone_polygon(g1)]))
} else if first_vertex_within(g2, g1) {
MultiPolygon(Vec::from([clone_polygon(g2)]))
} else {
MultiPolygon(Vec::new())
}
}
fn union_no_crossing<G1, G2, P>(g1: &G1, g2: &G2) -> MultiPolygon<Polygon<P>>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
if first_vertex_within(g1, g2) {
MultiPolygon(Vec::from([clone_polygon(g2)]))
} else if first_vertex_within(g2, g1) {
MultiPolygon(Vec::from([clone_polygon(g1)]))
} else {
MultiPolygon(Vec::from([clone_polygon(g1), clone_polygon(g2)]))
}
}
fn first_vertex_within<GI, GO, P>(inner: &GI, outer: &GO) -> bool
where
GI: PolygonTrait<Point = P>,
GO: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
let Some(v) = inner.exterior().points().next() else {
return false;
};
let outer_model = clone_polygon(outer);
geometry_algorithm::within(v, &outer_model)
}
fn clone_polygon<G, P>(g: &G) -> Polygon<P>
where
G: PolygonTrait<Point = P>,
P: Point + Copy,
{
let outer: Ring<P> = Ring::from_vec(g.exterior().points().copied().collect());
let inners: Vec<Ring<P>> = g
.interiors()
.map(|r| Ring::from_vec(r.points().copied().collect()))
.collect();
Polygon::with_inners(outer, inners)
}
#[cfg(test)]
mod tests {
use super::{OverlayError, intersection, union_poly};
use geometry_algorithm::ring_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| ring_area(pg.exterior()).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_is_refused_not_over_reported() {
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)]];
assert_eq!(
super::difference(&big, &small),
Err(OverlayError::Unsupported)
);
assert_eq!(
super::sym_difference(&big, &small),
Err(OverlayError::Unsupported)
);
}
#[test]
fn input_with_holes_is_refused_not_silently_wrong() {
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_eq!(intersection(&donut, &sq), Err(OverlayError::Unsupported));
assert_eq!(union_poly(&donut, &sq), Err(OverlayError::Unsupported));
assert_eq!(
super::difference(&donut, &sq),
Err(OverlayError::Unsupported)
);
assert_eq!(
super::sym_difference(&donut, &sq),
Err(OverlayError::Unsupported)
);
}
#[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));
}
}