Skip to main content

geometry_strategy/
disjoint.rs

1//! Per-CS strategy for the `disjoint` set-relation algorithm.
2//!
3//! Mirrors `boost::geometry::disjoint(g1, g2)` from
4//! `boost/geometry/algorithms/disjoint.hpp`. Boost's interface header
5//! defines `disjoint` as the primary kernel and resolves `intersects`
6//! through its negation
7//! (`algorithms/detail/intersects/interface.hpp:64-78`). The Rust
8//! port flips that: [`crate::intersects::CartesianIntersects`] is the
9//! primary kernel because every per-pair test is naturally an
10//! "is there a shared point?" question, and `disjoint` is the
11//! negation. The two trait surfaces stay parallel so callers can pick
12//! either entry point without surprises.
13
14use crate::intersects::{CartesianIntersects, IntersectsStrategy};
15
16/// A strategy for "do these two geometries share **no** point?".
17///
18/// Mirrors `boost::geometry::disjoint(g1, g2)` from
19/// `boost/geometry/algorithms/disjoint.hpp`. Symmetric like
20/// [`crate::intersects::IntersectsStrategy`].
21pub trait DisjointStrategy<A, B> {
22    /// `true` iff `a` and `b` share **no** point.
23    fn disjoint(&self, a: &A, b: &B) -> bool;
24}
25
26/// The Cartesian disjoint kernel — Boost's default for the Cartesian
27/// coordinate system. Implemented as `!CartesianIntersects` for every
28/// pair where the intersects kernel is defined; specialised faster
29/// per-pair tests (e.g. box-box bounding-box compare) can be added
30/// later without changing the public surface.
31#[derive(Debug, Default, Clone, Copy)]
32pub struct CartesianDisjoint;
33
34impl<A, B> DisjointStrategy<A, B> for CartesianDisjoint
35where
36    CartesianIntersects: IntersectsStrategy<A, B>,
37{
38    #[inline]
39    fn disjoint(&self, a: &A, b: &B) -> bool {
40        !CartesianIntersects.intersects(a, b)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::{CartesianDisjoint, DisjointStrategy};
47    use geometry_cs::Cartesian;
48    use geometry_model::{Linestring, Point2D, linestring};
49
50    type P = Point2D<f64, Cartesian>;
51    type LS = Linestring<P>;
52
53    #[test]
54    fn linestrings_disjoint_when_apart() {
55        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0)];
56        let b: LS = linestring![(10.0, 10.0), (11.0, 11.0)];
57        assert!(CartesianDisjoint.disjoint(&a, &b));
58    }
59
60    #[test]
61    fn linestrings_not_disjoint_when_crossing() {
62        let a: LS = linestring![(0.0, 0.0), (2.0, 0.0)];
63        let b: LS = linestring![(1.0, -1.0), (1.0, 1.0)];
64        assert!(!CartesianDisjoint.disjoint(&a, &b));
65    }
66
67    // KC1.T2 witness: proves this strategy accepts read-only `Point`
68    // operands (that need not implement `PointMut`). If it compiles,
69    // the read-only bound is locked.
70    fn _accepts_readonly_point<A, B, S>(s: &S, a: &A, b: &B) -> bool
71    where
72        A: geometry_trait::Point,
73        B: geometry_trait::Point,
74        S: DisjointStrategy<A, B>,
75    {
76        s.disjoint(a, b)
77    }
78
79    /// Invoke the read-only-point witness so its body runs too.
80    #[test]
81    #[allow(
82        clippy::used_underscore_items,
83        reason = "the test exists to run the compile-time witness's body"
84    )]
85    fn readonly_point_witness_computes_disjointness() {
86        assert!(_accepts_readonly_point(
87            &CartesianDisjoint,
88            &P::new(0.0, 0.0),
89            &P::new(1.0, 1.0)
90        ));
91        assert!(!_accepts_readonly_point(
92            &CartesianDisjoint,
93            &P::new(1.0, 1.0),
94            &P::new(1.0, 1.0)
95        ));
96    }
97}