Skip to main content

geometry_algorithm/
coordinate_position.rs

1//! Tri-state point-in-geometry classification.
2//!
3//! Boost's Cartesian winding strategy computes a three-way result before
4//! `within` and `covered_by` reduce it to booleans. This module exposes that
5//! distinction through the same public tag-dispatched strategy path.
6
7use geometry_strategy::{WithinStrategy, WithinStrategyForKind};
8use geometry_trait::{Geometry, Point};
9
10use crate::{covered_by, within};
11
12/// The position of a point relative to a geometry.
13///
14/// Mirrors the `-1` / `0` / `+1` result of
15/// `strategy/cartesian/point_in_poly_winding.hpp:69-74` with descriptive
16/// variants.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum CoordinatePosition {
19    /// The point lies in the strict interior.
20    Inside,
21    /// The point lies on the geometry boundary.
22    OnBoundary,
23    /// The point lies outside the geometry, including inside a polygon hole.
24    Outside,
25}
26
27/// Classify a point as inside, on the boundary, or outside a geometry.
28///
29/// This preserves the tri-state result that [`within()`] and [`covered_by()`]
30/// expose as separate boolean predicates.
31#[inline]
32#[must_use]
33pub fn coordinate_position<P, G>(point: &P, geometry: &G) -> CoordinatePosition
34where
35    P: Point,
36    G: Geometry,
37    G::Kind: WithinStrategyForKind,
38    <G::Kind as WithinStrategyForKind>::S: WithinStrategy<P, G>,
39{
40    if within(point, geometry) {
41        CoordinatePosition::Inside
42    } else if covered_by(point, geometry) {
43        CoordinatePosition::OnBoundary
44    } else {
45        CoordinatePosition::Outside
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::{CoordinatePosition, coordinate_position};
52    use geometry_cs::Cartesian;
53    use geometry_model::{Point2D, Polygon, polygon};
54
55    type P = Point2D<f64, Cartesian>;
56
57    fn square_with_hole() -> Polygon<P> {
58        polygon![
59            [(0.0, 0.0), (0.0, 5.0), (5.0, 5.0), (5.0, 0.0), (0.0, 0.0)],
60            [(2.0, 2.0), (3.0, 2.0), (3.0, 3.0), (2.0, 3.0), (2.0, 2.0)]
61        ]
62    }
63
64    #[test]
65    fn distinguishes_interior_boundary_and_exterior() {
66        let polygon = square_with_hole();
67        assert_eq!(
68            coordinate_position(&P::new(1.0, 1.0), &polygon),
69            CoordinatePosition::Inside
70        );
71        assert_eq!(
72            coordinate_position(&P::new(0.0, 1.0), &polygon),
73            CoordinatePosition::OnBoundary
74        );
75        assert_eq!(
76            coordinate_position(&P::new(2.5, 2.5), &polygon),
77            CoordinatePosition::Outside
78        );
79    }
80}