use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_model::{Polygon, Ring};
use geometry_tag::SameAs;
use geometry_trait::{Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait};
use crate::operation::OverlayError;
use crate::predicate::range_guard::polygon_in_range;
use crate::surface_point::point_on_surface;
use crate::turn::info::Method;
use crate::turn::{RingKind, get_turns_ring_ring};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dimension {
Empty,
Point,
Curve,
Area,
}
impl Dimension {
#[must_use]
pub fn is_set(self) -> bool {
self != Dimension::Empty
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct De9im {
pub m: [[Dimension; 3]; 3],
}
pub mod feature {
pub const INTERIOR: usize = 0;
pub const BOUNDARY: usize = 1;
pub const EXTERIOR: usize = 2;
}
impl De9im {
#[must_use]
pub fn interior_interior(&self) -> Dimension {
self.m[feature::INTERIOR][feature::INTERIOR]
}
#[must_use]
pub fn boundary_boundary(&self) -> Dimension {
self.m[feature::BOUNDARY][feature::BOUNDARY]
}
#[must_use]
pub fn interior_exterior(&self) -> Dimension {
self.m[feature::INTERIOR][feature::EXTERIOR]
}
#[must_use]
pub fn exterior_interior(&self) -> Dimension {
self.m[feature::EXTERIOR][feature::INTERIOR]
}
}
pub fn relate<G1, G2, P>(g1: &G1, g2: &G2) -> Result<De9im, 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 !polygon_in_range(g1) || !polygon_in_range(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);
let boundaries_meet = !turns.is_empty();
let boundaries_cross_transversally = turns.iter().any(|t| t.method == Method::Crosses);
let rep1 = point_on_surface(g1);
let rep2 = point_on_surface(g2);
let g2_model = clone_polygon(g2);
let g1_model = clone_polygon(g1);
let rep1_in_g2 = rep1.is_some_and(|p| geometry_algorithm::within(&p, &g2_model));
let rep2_in_g1 = rep2.is_some_and(|p| geometry_algorithm::within(&p, &g1_model));
if boundaries_meet && !boundaries_cross_transversally && !rep1_in_g2 && !rep2_in_g1 {
return Err(OverlayError::Unsupported);
}
let interiors_overlap = boundaries_cross_transversally || rep1_in_g2 || rep2_in_g1;
let mut m = [[Dimension::Empty; 3]; 3];
if interiors_overlap {
m[feature::INTERIOR][feature::INTERIOR] = Dimension::Area;
}
if boundaries_meet {
m[feature::BOUNDARY][feature::BOUNDARY] = Dimension::Point;
}
let g1_contained = rep1_in_g2 && !boundaries_cross_transversally;
if !g1_contained {
m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Area;
}
let g2_contained = rep2_in_g1 && !boundaries_cross_transversally;
if !g2_contained {
m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
}
m[feature::EXTERIOR][feature::EXTERIOR] = Dimension::Area;
Ok(De9im { m })
}
pub fn touches<G1, G2, P>(g1: &G1, g2: &G2) -> Result<bool, 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 matrix = relate(g1, g2)?;
Ok(!matrix.interior_interior().is_set() && matrix.boundary_boundary().is_set())
}
pub fn overlaps<G1, G2, P>(g1: &G1, g2: &G2) -> Result<bool, 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 matrix = relate(g1, g2)?;
Ok(matrix.interior_interior() == Dimension::Area
&& matrix.interior_exterior() == Dimension::Area
&& matrix.exterior_interior() == Dimension::Area)
}
#[allow(
clippy::unnecessary_wraps,
reason = "The Result is intentional: it keeps `crosses` signature-compatible with the sibling fallible predicates `overlaps`/`touches`, so callers handle one uniform surface."
)]
pub fn crosses<G1, G2, P>(_g1: &G1, _g2: &G2) -> Result<bool, OverlayError>
where
G1: PolygonTrait<Point = P>,
G2: PolygonTrait<Point = P>,
P: Point,
{
Ok(false)
}
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 = g
.interiors()
.map(|r| Ring::from_vec(r.points().copied().collect()))
.collect();
Polygon::with_inners(outer, inners)
}
#[cfg(test)]
mod tests {
use super::{Dimension, crosses, overlaps, relate, touches};
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Polygon, polygon};
type P = Point2D<f64, Cartesian>;
fn square(x: f64, y: f64, s: f64) -> Polygon<P> {
polygon![[(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)]]
}
#[test]
fn overlapping_squares_overlap() {
let a = square(0.0, 0.0, 2.0);
let b = square(1.0, 1.0, 2.0);
assert_eq!(relate(&a, &b).unwrap().interior_interior(), Dimension::Area);
assert!(overlaps(&a, &b).unwrap());
assert!(!touches(&a, &b).unwrap());
assert!(!crosses(&a, &b).unwrap());
}
#[test]
fn edge_touching_squares_are_unsupported() {
use crate::operation::OverlayError;
let a = square(0.0, 0.0, 2.0);
let b = square(2.0, 0.0, 2.0);
assert_eq!(relate(&a, &b), Err(OverlayError::Unsupported));
assert_eq!(touches(&a, &b), Err(OverlayError::Unsupported));
assert_eq!(overlaps(&a, &b), Err(OverlayError::Unsupported));
}
#[test]
fn edge_aligned_overlap_is_unsupported_not_false() {
use crate::operation::OverlayError;
let a: Polygon<P> = polygon![[(0.0, 0.0), (3.0, 0.0), (3.0, 1.0), (0.0, 1.0), (0.0, 0.0)]];
let b: Polygon<P> = polygon![[(2.0, 0.0), (5.0, 0.0), (5.0, 1.0), (2.0, 1.0), (2.0, 0.0)]];
assert_eq!(overlaps(&a, &b), Err(OverlayError::Unsupported));
}
#[test]
fn out_of_range_coordinates_are_unsupported() {
use crate::operation::OverlayError;
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!(relate(&a, &b), Err(OverlayError::Unsupported));
assert_eq!(overlaps(&a, &b), Err(OverlayError::Unsupported));
}
#[test]
fn disjoint_squares_neither() {
let a = square(0.0, 0.0, 1.0);
let b = square(5.0, 5.0, 1.0);
assert!(!touches(&a, &b).unwrap());
assert!(!overlaps(&a, &b).unwrap());
assert_eq!(
relate(&a, &b).unwrap().interior_interior(),
Dimension::Empty
);
}
#[test]
fn contained_square_does_not_overlap_or_touch() {
let big = square(0.0, 0.0, 10.0);
let small = square(3.0, 3.0, 2.0);
assert_eq!(
relate(&big, &small).unwrap().interior_interior(),
Dimension::Area
);
assert!(!overlaps(&big, &small).unwrap());
assert!(!touches(&big, &small).unwrap());
}
}