use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_tag::{BoxTag, PolygonTag, RingTag, SameAs};
use geometry_trait::{
Box as BoxTrait, Point as PointTrait, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
corner,
};
pub trait WithinStrategy<P: PointTrait, G> {
fn within(&self, p: &P, g: &G) -> bool;
fn covered_by(&self, p: &P, g: &G) -> bool;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct WithinBox;
#[derive(Debug, Default, Clone, Copy)]
pub struct WithinRing;
#[derive(Debug, Default, Clone, Copy)]
pub struct WithinPoly;
impl<P, G> WithinStrategy<P, G> for WithinBox
where
G: BoxTrait<Point = P>,
P: PointMut,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn within(&self, p: &P, b: &G) -> bool {
let x = p.get::<0>();
let y = p.get::<1>();
let xmin = b.get_indexed::<{ corner::MIN }, 0>();
let ymin = b.get_indexed::<{ corner::MIN }, 1>();
let xmax = b.get_indexed::<{ corner::MAX }, 0>();
let ymax = b.get_indexed::<{ corner::MAX }, 1>();
xmin < x && x < xmax && ymin < y && y < ymax
}
#[inline]
fn covered_by(&self, p: &P, b: &G) -> bool {
let x = p.get::<0>();
let y = p.get::<1>();
let xmin = b.get_indexed::<{ corner::MIN }, 0>();
let ymin = b.get_indexed::<{ corner::MIN }, 1>();
let xmax = b.get_indexed::<{ corner::MAX }, 0>();
let ymax = b.get_indexed::<{ corner::MAX }, 1>();
xmin <= x && x <= xmax && ymin <= y && y <= ymax
}
}
impl<P, G> WithinStrategy<P, G> for WithinRing
where
G: RingTrait<Point = P>,
P: PointTrait,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn within(&self, p: &P, r: &G) -> bool {
winding_result(p, r) == InOut::Interior
}
#[inline]
fn covered_by(&self, p: &P, r: &G) -> bool {
!matches!(winding_result(p, r), InOut::Exterior)
}
}
impl<P, G> WithinStrategy<P, G> for WithinPoly
where
G: PolygonTrait<Point = P>,
P: PointTrait,
P::Scalar: CoordinateScalar,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
{
#[inline]
fn within(&self, p: &P, pg: &G) -> bool {
if !WithinRing.within(p, pg.exterior()) {
return false;
}
for hole in pg.interiors() {
if WithinRing.covered_by(p, hole) {
return false;
}
}
true
}
#[inline]
fn covered_by(&self, p: &P, pg: &G) -> bool {
if !WithinRing.covered_by(p, pg.exterior()) {
return false;
}
for hole in pg.interiors() {
if WithinRing.within(p, hole) {
return false;
}
}
true
}
}
#[doc(hidden)]
pub trait WithinStrategyForKind {
type S: Default;
}
impl WithinStrategyForKind for BoxTag {
type S = WithinBox;
}
impl WithinStrategyForKind for RingTag {
type S = WithinRing;
}
impl WithinStrategyForKind for PolygonTag {
type S = WithinPoly;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InOut {
Exterior,
Boundary,
Interior,
}
fn winding_result<P, R>(p: &P, r: &R) -> InOut
where
P: PointTrait,
P::Scalar: CoordinateScalar,
R: RingTrait<Point = P>,
{
let mut count: i32 = 0;
let mut it = r.points();
let Some(mut prev) = it.next() else {
return InOut::Exterior;
};
let first = prev;
for curr in it {
match apply_segment(p, prev, curr) {
Step::Touches => return InOut::Boundary,
Step::Count(c) => count += c,
}
prev = curr;
}
match apply_segment(p, prev, first) {
Step::Touches => return InOut::Boundary,
Step::Count(c) => count += c,
}
if count == 0 {
InOut::Exterior
} else {
InOut::Interior
}
}
#[derive(Debug, Clone, Copy)]
enum Step {
Touches,
Count(i32),
}
fn apply_segment<P>(p: &P, s1: &P, s2: &P) -> Step
where
P: PointTrait,
P::Scalar: CoordinateScalar,
{
let px = p.get::<0>();
let py = p.get::<1>();
let s1x = s1.get::<0>();
let s2x = s2.get::<0>();
let s1y = s1.get::<1>();
let s2y = s2.get::<1>();
let eq1 = s1x == px;
let eq2 = s2x == px;
if eq1 && eq2 {
let (lo, hi) = if s1y <= s2y { (s1y, s2y) } else { (s2y, s1y) };
if lo <= py && py <= hi {
return Step::Touches;
}
return Step::Count(0);
}
let count = if eq1 {
if s2x > px { 1 } else { -1 }
} else if eq2 {
if s1x > px { -1 } else { 1 }
} else if s1x < px && s2x > px {
2
} else if s2x < px && s1x > px {
-2
} else {
0
};
if count == 0 {
return Step::Count(0);
}
let side: i32 = if count == 1 || count == -1 {
let se = if eq1 { s1 } else { s2 };
let sey = se.get::<1>();
if py == sey {
0
} else if py < sey {
-count
} else {
count
}
} else {
let cross = (s2x - s1x) * (py - s1y) - (s2y - s1y) * (px - s1x);
if cross > P::Scalar::ZERO {
1
} else if cross < P::Scalar::ZERO {
-1
} else {
0
}
};
if side == 0 {
return Step::Touches;
}
if side * count > 0 {
Step::Count(count)
} else {
Step::Count(0)
}
}
#[cfg(test)]
mod tests {
use super::{WithinBox, WithinPoly, WithinRing, WithinStrategy};
use geometry_cs::Cartesian;
use geometry_model::{Box, Point2D, Polygon, Ring, polygon};
type P = Point2D<f64, Cartesian>;
fn pt(x: f64, y: f64) -> P {
Point2D::new(x, y)
}
fn box_polygon() -> Polygon<P> {
polygon![[(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)]]
}
#[test]
fn box_b1_inside() {
assert!(WithinPoly.within(&pt(1.0, 1.0), &box_polygon()));
}
#[test]
fn box_b2_outside() {
assert!(!WithinPoly.within(&pt(3.0, 3.0), &box_polygon()));
}
#[test]
fn box_corners_are_not_within() {
let p = box_polygon();
for (x, y) in [(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)] {
assert!(!WithinPoly.within(&pt(x, y), &p), "corner ({x},{y})");
}
}
#[test]
fn box_sides_are_not_within() {
let p = box_polygon();
for (x, y) in [(0.0, 1.0), (1.0, 2.0), (2.0, 1.0), (1.0, 0.0)] {
assert!(!WithinPoly.within(&pt(x, y), &p), "side ({x},{y})");
}
}
#[test]
fn triangle_interior_and_exterior() {
let t: Polygon<P> = polygon![[(0.0, 0.0), (0.0, 4.0), (6.0, 0.0), (0.0, 0.0)]];
assert!(WithinPoly.within(&pt(1.0, 1.0), &t));
assert!(!WithinPoly.within(&pt(3.0, 3.0), &t));
}
#[test]
fn hole_semantics() {
let with_hole: Polygon<P> = polygon![
[(0.0, 0.0), (0.0, 3.0), (3.0, 3.0), (3.0, 0.0), (0.0, 0.0)],
[(1.0, 1.0), (2.0, 1.0), (2.0, 2.0), (1.0, 2.0), (1.0, 1.0)]
];
assert!(WithinPoly.within(&pt(0.5, 0.5), &with_hole));
assert!(!WithinPoly.within(&pt(1.5, 1.5), &with_hole));
}
#[test]
fn covered_by_includes_boundary() {
let p = box_polygon();
assert!(WithinPoly.covered_by(&pt(0.0, 0.0), &p));
assert!(WithinPoly.covered_by(&pt(0.0, 1.0), &p));
assert!(WithinPoly.covered_by(&pt(1.0, 1.0), &p));
assert!(!WithinPoly.covered_by(&pt(3.0, 3.0), &p));
}
#[test]
fn box_geometry_strict_vs_non_strict() {
let b = Box::from_corners(pt(0.0, 0.0), pt(2.0, 2.0));
assert!(WithinBox.within(&pt(1.0, 1.0), &b));
assert!(!WithinBox.within(&pt(0.0, 0.0), &b));
assert!(WithinBox.covered_by(&pt(0.0, 0.0), &b));
assert!(!WithinBox.within(&pt(0.0, 1.0), &b));
assert!(WithinBox.covered_by(&pt(0.0, 1.0), &b));
assert!(!WithinBox.within(&pt(3.0, 3.0), &b));
assert!(!WithinBox.covered_by(&pt(3.0, 3.0), &b));
}
#[test]
fn ring_within_smoke() {
let r: Ring<P> = Ring::from_vec(vec![
pt(0.0, 0.0),
pt(0.0, 2.0),
pt(2.0, 2.0),
pt(2.0, 0.0),
pt(0.0, 0.0),
]);
assert!(WithinRing.within(&pt(1.0, 1.0), &r));
assert!(!WithinRing.within(&pt(0.0, 0.0), &r));
assert!(WithinRing.covered_by(&pt(0.0, 0.0), &r));
}
#[test]
fn open_ring_closes_implicitly() {
let mut r = Ring::<P, true, false>::new();
r.push(pt(0.0, 0.0));
r.push(pt(0.0, 2.0));
r.push(pt(2.0, 2.0));
r.push(pt(2.0, 0.0));
assert!(WithinRing.within(&pt(1.0, 1.0), &r));
assert!(!WithinRing.within(&pt(3.0, 3.0), &r));
}
}