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}