use crate::arrangement::{Arrangement, InEdge};
use crate::geom::{Point, PointF, Polygon};
use crate::node::node_exact;
use crate::predicates::{crossing_f64, orient};
use crate::query::{Location, locate_in_ring, ring_area2};
use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RingId {
pub polygon: usize,
pub ring: usize,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ValidityError {
CoordinateOutOfRange(Point),
TooFewVertices(RingId),
DuplicateVertex(RingId, Point),
ZeroArea(RingId),
SelfIntersection(PointF),
SelfTouch(RingId, Point),
OverlappingEdges(Point, Point),
InvalidNesting(Point),
DisconnectedInterior(Point),
WrongOrientation(RingId),
NotCanonical(&'static str),
}
impl fmt::Display for ValidityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ValidityError::*;
match self {
CoordinateOutOfRange(p) => write!(f, "coordinate out of range at ({}, {})", p.x, p.y),
TooFewVertices(r) => {
write!(f, "ring {}/{} has fewer than 3 vertices", r.polygon, r.ring)
}
DuplicateVertex(r, p) => write!(
f,
"ring {}/{} repeats vertex ({}, {})",
r.polygon, r.ring, p.x, p.y
),
ZeroArea(r) => write!(f, "ring {}/{} has zero area", r.polygon, r.ring),
SelfIntersection(p) => write!(f, "self-intersection at ({}, {})", p.x, p.y),
SelfTouch(r, p) => write!(
f,
"ring {}/{} touches itself at ({}, {})",
r.polygon, r.ring, p.x, p.y
),
OverlappingEdges(a, b) => write!(
f,
"overlapping edges along ({}, {})-({}, {})",
a.x, a.y, b.x, b.y
),
InvalidNesting(p) => write!(f, "invalid nesting near ({}, {})", p.x, p.y),
DisconnectedInterior(p) => write!(f, "interior disconnected at ({}, {})", p.x, p.y),
WrongOrientation(r) => write!(
f,
"ring {}/{} has non-canonical orientation",
r.polygon, r.ring
),
NotCanonical(s) => write!(f, "not canonical: {s}"),
}
}
}
impl std::error::Error for ValidityError {}
pub fn validate(poly: &Polygon) -> Result<(), ValidityError> {
validate_set(core::slice::from_ref(poly))
}
pub fn validate_set(polys: &[Polygon]) -> Result<(), ValidityError> {
let mut edges: Vec<InEdge> = Vec::new();
let mut ring_of_edge: Vec<(usize, usize)> = Vec::new();
for (pi, poly) in polys.iter().enumerate() {
for (ri, ring) in poly.rings().enumerate() {
let id = RingId {
polygon: pi,
ring: ri,
};
let pts = &ring.0;
if let Some(&p) = pts.iter().find(|p| !p.in_range()) {
return Err(ValidityError::CoordinateOutOfRange(p));
}
if pts.len() < 3 {
return Err(ValidityError::TooFewVertices(id));
}
for i in 0..pts.len() {
if pts[i] == pts[(i + 1) % pts.len()] {
return Err(ValidityError::DuplicateVertex(id, pts[i]));
}
}
let a = ring_area2(pts);
if a == 0 {
return Err(ValidityError::ZeroArea(id));
}
let flip = (ri == 0) != (a > 0);
let n = pts.len();
for i in 0..n {
let (p, q) = (pts[i], pts[(i + 1) % n]);
let (p, q) = if flip { (q, p) } else { (p, q) };
edges.push(InEdge {
a: p,
b: q,
tag: ((pi as u64) << 32) | ri as u64,
operand: 0,
});
ring_of_edge.push((pi, ri));
}
}
}
let segs: Vec<(Point, Point)> = edges.iter().map(|e| (e.a, e.b)).collect();
let frags = match node_exact(&segs) {
Ok(f) => f,
Err(c) => {
let (a, b) = (segs[c.i as usize], segs[c.j as usize]);
let (x, y) = crossing_f64(a.0, a.1, b.0, b.1);
return Err(ValidityError::SelfIntersection(PointF::new(x, y)));
}
};
drop(segs);
let mut fr: Vec<(Point, Point)> = frags
.iter()
.map(|f| if f.a < f.b { (f.a, f.b) } else { (f.b, f.a) })
.collect();
fr.sort_unstable();
for w in fr.windows(2) {
if w[0] == w[1] {
return Err(ValidityError::OverlappingEdges(w[0].0, w[0].1));
}
}
drop(fr);
let mut vr: Vec<(Point, (usize, usize))> = Vec::with_capacity(frags.len() * 2);
for f in &frags {
let r = ring_of_edge[f.src as usize];
vr.push((f.a, r));
vr.push((f.b, r));
}
vr.sort_unstable();
let mut uf = UnionFind::new(0);
let mut ring_node: std::collections::HashMap<(usize, usize), usize> =
std::collections::HashMap::new();
let mut rings_here: Vec<(usize, usize)> = Vec::new();
let mut i = 0;
while i < vr.len() {
let p = vr[i].0;
let mut j = i;
rings_here.clear();
while j < vr.len() && vr[j].0 == p {
let r = vr[j].1;
let mut k = j;
while k < vr.len() && vr[k].0 == p && vr[k].1 == r {
k += 1;
}
if k - j > 2 {
return Err(ValidityError::SelfTouch(
RingId {
polygon: r.0,
ring: r.1,
},
p,
));
}
rings_here.push(r);
j = k;
}
let mut a = 0;
while a < rings_here.len() {
let mut b = a;
while b < rings_here.len() && rings_here[b].0 == rings_here[a].0 {
b += 1;
}
if b - a >= 2 {
let pn = uf.add();
for r in &rings_here[a..b] {
let rn = *ring_node.entry(*r).or_insert_with(|| uf.add());
if !uf.union(pn, rn) {
return Err(ValidityError::DisconnectedInterior(p));
}
}
}
a = b;
}
i = j;
}
drop(vr);
let arr = Arrangement::from_frags(&edges, frags);
for (k, e) in arr.edges.iter().enumerate() {
let wb = arr.below[k][0];
let wa = wb + e.delta[0];
if !(0..=1).contains(&wb) || !(0..=1).contains(&wa) {
return Err(ValidityError::InvalidNesting(e.lo));
}
}
for poly in polys {
for h in &poly.holes {
if !hole_inside(&poly.outer.0, &h.0) {
return Err(ValidityError::InvalidNesting(h.0[0]));
}
}
}
Ok(())
}
fn hole_inside(outer: &[Point], hole: &[Point]) -> bool {
for &p in hole {
match locate_in_ring(outer, p) {
Location::Inside => return true,
Location::Outside => return false,
Location::OnBoundary => {}
}
}
let dbl: Vec<Point> = outer.iter().map(|p| Point::new(2 * p.x, 2 * p.y)).collect();
let n = hole.len();
for i in 0..n {
let (a, b) = (hole[i], hole[(i + 1) % n]);
let m = Point::new(a.x + b.x, a.y + b.y);
match locate_in_ring(&dbl, m) {
Location::Inside => return true,
Location::Outside => return false,
Location::OnBoundary => {}
}
}
false
}
struct UnionFind {
parent: Vec<usize>,
}
impl UnionFind {
fn new(n: usize) -> Self {
UnionFind {
parent: (0..n).collect(),
}
}
fn add(&mut self) -> usize {
self.parent.push(self.parent.len());
self.parent.len() - 1
}
fn find(&mut self, mut x: usize) -> usize {
while self.parent[x] != x {
self.parent[x] = self.parent[self.parent[x]];
x = self.parent[x];
}
x
}
fn union(&mut self, a: usize, b: usize) -> bool {
let (ra, rb) = (self.find(a), self.find(b));
if ra == rb {
return false;
}
self.parent[ra] = rb;
true
}
}
pub fn check_canonical(polys: &[Polygon], collinear_removed: bool) -> Result<(), ValidityError> {
validate_set(polys)?;
let mut shared: Vec<Point> = Vec::new();
if collinear_removed {
let mut all: Vec<Point> = polys
.iter()
.flat_map(|p| p.rings().flat_map(|r| r.0.iter().copied()))
.collect();
all.sort_unstable();
for w in all.windows(2) {
if w[0] == w[1] && shared.last() != Some(&w[0]) {
shared.push(w[0]);
}
}
}
for (pi, poly) in polys.iter().enumerate() {
for (ri, ring) in poly.rings().enumerate() {
let id = RingId {
polygon: pi,
ring: ri,
};
if (ring_area2(&ring.0) > 0) != (ri == 0) {
return Err(ValidityError::WrongOrientation(id));
}
let min = ring.0.iter().min().copied();
if ring.0.first().copied() != min {
return Err(ValidityError::NotCanonical(
"ring does not start at its minimum vertex",
));
}
if collinear_removed {
let n = ring.0.len();
for i in 0..n {
let (a, v, b) = (ring.0[(i + n - 1) % n], ring.0[i], ring.0[(i + 1) % n]);
if orient(a, v, b) == 0 && shared.binary_search(&v).is_err() {
return Err(ValidityError::NotCanonical("collinear vertex"));
}
}
}
}
if poly.holes.windows(2).any(|w| w[0].0 >= w[1].0) {
return Err(ValidityError::NotCanonical("holes not sorted"));
}
}
if polys.windows(2).any(|w| w[0].outer.0 >= w[1].outer.0) {
return Err(ValidityError::NotCanonical("polygons not sorted"));
}
Ok(())
}