use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_model::{Linestring, Polygon, Ring, Segment};
use geometry_strategy::{CartesianIntersects, IntersectsStrategy};
use geometry_trait::{Linestring as LinestringTrait, Point, Polygon as PolygonTrait};
#[inline]
#[must_use]
pub fn is_simple<G: IsSimple>(g: &G) -> bool {
g.is_simple()
}
#[doc(hidden)]
pub trait IsSimple {
fn is_simple(&self) -> bool;
}
impl<P> IsSimple for Linestring<P>
where
P: Point,
P::Scalar: CoordinateScalar,
CartesianIntersects: IntersectsStrategy<Segment<P>, Segment<P>>,
P: geometry_trait::PointMut + Default + Copy,
{
fn is_simple(&self) -> bool {
let pts: Vec<P> = self.points().copied().collect();
linestring_points_simple(&pts)
}
}
impl<P, const CW: bool, const CL: bool> IsSimple for Polygon<P, CW, CL>
where
P: Point,
P::Scalar: CoordinateScalar,
{
fn is_simple(&self) -> bool {
ring_lacks_duplicates(self.exterior()) && self.interiors().all(|r| ring_lacks_duplicates(r))
}
}
fn ring_lacks_duplicates<P, const CW: bool, const CL: bool>(r: &Ring<P, CW, CL>) -> bool
where
P: Point,
P::Scalar: CoordinateScalar,
{
let pts: &[P] = &r.0;
if pts.is_empty() {
return false;
}
for w in pts.windows(2) {
if points_equal(&w[0], &w[1]) {
return false;
}
}
if matches!(
geometry_trait::Ring::closure(r),
geometry_trait::Closure::Open
) && pts.len() >= 2
&& points_equal(&pts[pts.len() - 1], &pts[0])
{
return false;
}
true
}
fn linestring_points_simple<P>(pts: &[P]) -> bool
where
P: Point + geometry_trait::PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
CartesianIntersects: IntersectsStrategy<Segment<P>, Segment<P>>,
{
if pts.len() < 2 {
return true;
}
let closed = points_equal(&pts[0], &pts[pts.len() - 1]);
let segs: Vec<Segment<P>> = pts.windows(2).map(|w| Segment::new(w[0], w[1])).collect();
for i in 0..segs.len() {
if points_equal(&pts[i], &pts[i + 1]) {
return false;
}
for j in (i + 1)..segs.len() {
let adjacent = j == i + 1;
if adjacent {
if doubles_back(&pts[i], &pts[i + 1], &pts[j + 1]) {
return false;
}
} else if closed && i == 0 && j == segs.len() - 1 {
} else if CartesianIntersects.intersects(&segs[i], &segs[j]) {
return false;
}
}
}
true
}
#[inline]
fn points_equal<P: Point>(a: &P, b: &P) -> bool {
a.get::<0>() == b.get::<0>() && a.get::<1>() == b.get::<1>()
}
#[inline]
fn doubles_back<P: Point>(p0: &P, p1: &P, p2: &P) -> bool
where
P::Scalar: CoordinateScalar,
{
let ax = p1.get::<0>() - p0.get::<0>();
let ay = p1.get::<1>() - p0.get::<1>();
let bx = p2.get::<0>() - p1.get::<0>();
let by = p2.get::<1>() - p1.get::<1>();
let cross = ax * by - ay * bx;
if cross != P::Scalar::ZERO {
return false;
}
ax * bx + ay * by < P::Scalar::ZERO
}
#[cfg(test)]
mod tests {
use super::is_simple;
use geometry_cs::Cartesian;
use geometry_model::{Linestring, Point2D, Polygon, linestring, polygon};
type Pt = Point2D<f64, Cartesian>;
#[test]
fn two_point_is_simple() {
let ls: Linestring<Pt> = linestring![(0., 0.), (1., 2.)];
assert!(is_simple(&ls));
}
#[test]
fn three_point_is_simple() {
let ls: Linestring<Pt> = linestring![(0., 0.), (1., 2.), (2., 3.)];
assert!(is_simple(&ls));
}
#[test]
fn consecutive_duplicate_not_simple() {
let ls: Linestring<Pt> = linestring![(0., 0.), (0., 0.), (1., 0.)];
assert!(!is_simple(&ls));
}
#[test]
fn figure_eight_not_simple() {
let ls: Linestring<Pt> =
linestring![(0., 0.), (1., 0.), (2., 0.), (1., 1.), (1., 0.), (1., -1.)];
assert!(!is_simple(&ls));
}
#[test]
fn bowtie_linestring_not_simple() {
let ls: Linestring<Pt> = linestring![(0., 0.), (2., 2.), (2., 0.), (0., 2.)];
assert!(!is_simple(&ls));
}
#[test]
fn closed_simple_quadrilateral() {
let ls: Linestring<Pt> = linestring![(0., 0.), (1., 0.), (1., 1.), (0., 0.)];
assert!(is_simple(&ls));
}
#[test]
fn closed_square_is_simple() {
let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)];
assert!(is_simple(&ls));
}
#[test]
fn unit_square_polygon_is_simple() {
let pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (4., 3.), (0., 3.), (0., 0.)]];
assert!(is_simple(&pg));
}
#[test]
fn polygon_with_disjoint_hole_is_simple() {
let pg: Polygon<Pt> = polygon![
[(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
[(2., 2.), (4., 2.), (4., 4.), (2., 4.), (2., 2.)],
];
assert!(is_simple(&pg));
}
#[test]
fn bowtie_polygon_is_simple_but_invalid() {
let pg: Polygon<Pt> = polygon![[(0., 0.), (2., 2.), (0., 2.), (2., 0.), (0., 0.)]];
assert!(is_simple(&pg));
}
#[test]
fn hole_touching_outer_is_simple() {
let pg: Polygon<Pt> = polygon![
[(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
[(0., 0.), (5., 5.), (10., 0.), (5., 0.), (0., 0.)],
];
assert!(is_simple(&pg));
}
#[test]
fn polygon_with_consecutive_duplicate_is_not_simple() {
let pg: Polygon<Pt> =
polygon![[(0., 0.), (4., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)]];
assert!(!is_simple(&pg));
}
#[test]
fn polygon_with_duplicate_in_hole_is_not_simple() {
let pg: Polygon<Pt> = polygon![
[(0., 0.), (10., 0.), (10., 10.), (0., 10.), (0., 0.)],
[(2., 2.), (4., 2.), (4., 2.), (4., 4.), (2., 4.), (2., 2.)],
];
assert!(!is_simple(&pg));
}
#[test]
fn polygon_with_empty_exterior_is_not_simple() {
use geometry_model::Ring;
let pg: Polygon<Pt> = Polygon::new(Ring::new());
assert!(!is_simple(&pg));
}
}