Skip to main content

geometry_algorithm/
disjoint.rs

1//! `disjoint(&a, &b)` — see
2//! `boost/geometry/algorithms/disjoint.hpp`.
3//!
4//! Defined as the negation of [`crate::intersects()`] for every pair
5//! the intersects kernel handles. Mirrors Boost's interface header
6//! that resolves one of the two through the other
7//! (`algorithms/detail/intersects/interface.hpp:64-78`).
8
9use geometry_coords::CoordinateScalar;
10use geometry_strategy::{CartesianIntersects, IntersectsStrategy};
11use geometry_trait::{Box as BoxTrait, Geometry, Point as PointTrait};
12
13/// `true` iff `a` and `b` share **no** point.
14///
15/// Mirrors `boost::geometry::disjoint(a, b)` from
16/// `boost/geometry/algorithms/disjoint.hpp`.
17#[inline]
18#[must_use]
19pub fn disjoint<A, B>(a: &A, b: &B) -> bool
20where
21    CartesianIntersects: IntersectsStrategy<A, B>,
22{
23    !CartesianIntersects.intersects(a, b)
24}
25
26/// `disjoint` for two axis-aligned boxes — a direct separating-axis
27/// test, skipping the general intersects machinery.
28///
29/// Two boxes are disjoint iff they are separated on **either** axis:
30/// one's maximum on an axis is below the other's minimum. Mirrors the
31/// box/box specialisation in
32/// `boost/geometry/strategies/cartesian/disjoint_box_box.hpp`, which
33/// short-circuits per axis rather than routing through `intersects`.
34///
35/// This is a CC5 fast path: `disjoint(Box, Box)` is one of the hottest
36/// pair-combinations (envelope pruning, rtree node tests), and the
37/// per-axis comparison is far cheaper than the generic areal intersects.
38///
39/// # Examples
40///
41/// ```
42/// use geometry_algorithm::disjoint_box_box;
43/// use geometry_cs::Cartesian;
44/// use geometry_model::{Box, Point2D};
45///
46/// type P = Point2D<f64, Cartesian>;
47/// let a = Box::from_corners(P::new(0.0, 0.0), P::new(2.0, 2.0));
48/// let far = Box::from_corners(P::new(5.0, 5.0), P::new(6.0, 6.0));
49/// let overlapping = Box::from_corners(P::new(1.0, 1.0), P::new(3.0, 3.0));
50/// assert!(disjoint_box_box(&a, &far));
51/// assert!(!disjoint_box_box(&a, &overlapping));
52/// ```
53#[inline]
54#[must_use]
55pub fn disjoint_box_box<A, B, T>(a: &A, b: &B) -> bool
56where
57    A: BoxTrait,
58    B: BoxTrait,
59    <A as Geometry>::Point: PointTrait<Scalar = T>,
60    <B as Geometry>::Point: PointTrait<Scalar = T>,
61    T: CoordinateScalar,
62{
63    // `get_indexed::<I, D>`: I = 0 is min-corner, I = 1 is max-corner.
64    let a_min_x = a.get_indexed::<0, 0>();
65    let a_min_y = a.get_indexed::<0, 1>();
66    let a_max_x = a.get_indexed::<1, 0>();
67    let a_max_y = a.get_indexed::<1, 1>();
68    let b_min_x = b.get_indexed::<0, 0>();
69    let b_min_y = b.get_indexed::<0, 1>();
70    let b_max_x = b.get_indexed::<1, 0>();
71    let b_max_y = b.get_indexed::<1, 1>();
72
73    a_max_x < b_min_x || b_max_x < a_min_x || a_max_y < b_min_y || b_max_y < a_min_y
74}
75
76#[cfg(test)]
77mod tests {
78    use super::disjoint;
79    use crate::intersects::intersects;
80    use geometry_cs::Cartesian;
81    use geometry_model::{Linestring, Point2D, linestring};
82
83    type P = Point2D<f64, Cartesian>;
84    type LS = Linestring<P>;
85
86    #[test]
87    fn disjoint_matches_negated_intersects() {
88        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0)];
89        let b: LS = linestring![(10.0, 10.0), (11.0, 11.0)];
90        assert!(disjoint(&a, &b));
91        assert!(!intersects(&a, &b));
92    }
93
94    #[test]
95    fn disjoint_false_when_intersecting() {
96        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0)];
97        let b: LS = linestring![(1.0, -1.0), (1.0, 1.0)];
98        assert!(!disjoint(&a, &b));
99        assert!(intersects(&a, &b));
100    }
101
102    /// Polygon–polygon disjointness: separated squares are disjoint,
103    /// overlapping ones are not.
104    #[test]
105    fn polygon_pair_disjointness() {
106        use geometry_model::{Polygon, polygon};
107        let a: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)]];
108        let apart: Polygon<P> = polygon![[
109            (10.0, 10.0),
110            (14.0, 10.0),
111            (14.0, 14.0),
112            (10.0, 14.0),
113            (10.0, 10.0)
114        ]];
115        let overlapping: Polygon<P> =
116            polygon![[(2.0, 2.0), (6.0, 2.0), (6.0, 6.0), (2.0, 6.0), (2.0, 2.0)]];
117        assert!(disjoint(&a, &apart));
118        assert!(!disjoint(&a, &overlapping));
119    }
120
121    #[test]
122    fn box_box_fast_path() {
123        use crate::disjoint::disjoint_box_box;
124        use geometry_model::Box;
125
126        let a = Box::from_corners(P::new(0.0, 0.0), P::new(2.0, 2.0));
127        // Separated on x.
128        let right = Box::from_corners(P::new(5.0, 0.0), P::new(6.0, 2.0));
129        assert!(disjoint_box_box(&a, &right));
130        // Separated on y.
131        let above = Box::from_corners(P::new(0.0, 5.0), P::new(2.0, 6.0));
132        assert!(disjoint_box_box(&a, &above));
133        // Overlapping.
134        let over = Box::from_corners(P::new(1.0, 1.0), P::new(3.0, 3.0));
135        assert!(!disjoint_box_box(&a, &over));
136        // Touching edges count as *not* disjoint (closed boxes).
137        let touch = Box::from_corners(P::new(2.0, 0.0), P::new(4.0, 2.0));
138        assert!(!disjoint_box_box(&a, &touch));
139    }
140}