Skip to main content

geometry_rtree/
predicate.rs

1//! Spatial query predicates.
2//!
3//! Mirrors `boost/geometry/index/predicates.hpp`. A predicate is tested
4//! against candidate values during a query walk; the tree prunes any
5//! subtree whose bounds cannot satisfy the predicate.
6//!
7//! The built-in [`Predicate`] covers Boost's box-query relations. The
8//! [`QueryPredicate`] trait also supports Boost's logical `and`, `not`,
9//! and `satisfies` predicates without giving up subtree pruning.
10
11use crate::bounds::Bounds;
12use crate::indexable::Indexable;
13
14/// A spatial query against the index, expressed on bounding boxes.
15///
16/// Mirrors the `index::detail::predicates` family
17/// (`index/predicates.hpp`). Each variant carries the query box.
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum Predicate {
20    /// Values whose bounds intersect the query box. Boost's
21    /// `index::intersects`.
22    Intersects(Bounds),
23    /// Values whose non-degenerate bounds are fully inside the query
24    /// box. Boost's `index::within`.
25    Within(Bounds),
26    /// Values whose bounds contain a non-degenerate query box. Boost's
27    /// `index::contains`.
28    Contains(Bounds),
29    /// Values whose bounds are inside the query box, including
30    /// degenerate values and coincident boundaries. Boost's
31    /// `index::covered_by`.
32    CoveredBy(Bounds),
33    /// Values whose bounds contain the query box, including a
34    /// degenerate query. Boost's `index::covers`.
35    Covers(Bounds),
36    /// Values whose bounds share no point with the query box. Boost's
37    /// `index::disjoint`.
38    Disjoint(Bounds),
39    /// Values whose bounds overlap the query box without either box
40    /// covering the other. Boost's `index::overlaps`.
41    Overlaps(Bounds),
42}
43
44impl Predicate {
45    /// Whether a leaf value with box `value` satisfies this predicate.
46    #[must_use]
47    #[inline]
48    pub fn matches(&self, value: &Bounds) -> bool {
49        match self {
50            Predicate::Intersects(q) => q.intersects(value),
51            Predicate::Within(q) => value.within(q),
52            Predicate::Contains(q) => q.within(value),
53            Predicate::CoveredBy(q) => value.covered_by(q),
54            Predicate::Covers(q) => q.covered_by(value),
55            Predicate::Disjoint(q) => q.disjoint(value),
56            Predicate::Overlaps(q) => q.overlaps(value),
57        }
58    }
59
60    /// Whether a subtree with box `node` could contain any matching
61    /// value — the pruning test. A subtree is skipped when this is
62    /// `false`.
63    #[must_use]
64    #[inline]
65    pub fn could_match(&self, node: &Bounds) -> bool {
66        match self {
67            Predicate::Intersects(q) => q.intersects(node),
68            Predicate::Within(q) | Predicate::CoveredBy(q) | Predicate::Overlaps(q) => {
69                q.intersects(node)
70            }
71            Predicate::Contains(q) | Predicate::Covers(q) => node.contains(q),
72            // If the whole node is covered by q, every value below it
73            // intersects q and none can be disjoint.
74            Predicate::Disjoint(q) => !node.covered_by(q),
75        }
76    }
77
78    /// Whether EVERY value in a subtree with box `node` is a guaranteed
79    /// match — the containment fast path. A covered subtree is dumped
80    /// without per-node or per-value tests.
81    ///
82    /// Sound because a value's box is contained in its node's box:
83    /// `Intersects` and `CoveredBy` can dump a node covered by the
84    /// query, while `Disjoint` can dump one wholly apart from it.
85    /// `Within` cannot use the covered-node shortcut because a subtree
86    /// may contain degenerate values.
87    #[must_use]
88    #[inline]
89    pub fn covers_all(&self, node: &Bounds) -> bool {
90        match self {
91            Predicate::Intersects(q) | Predicate::CoveredBy(q) => q.contains(node),
92            Predicate::Disjoint(q) => q.disjoint(node),
93            Predicate::Within(_)
94            | Predicate::Contains(_)
95            | Predicate::Covers(_)
96            | Predicate::Overlaps(_) => false,
97        }
98    }
99
100    /// Combine this spatial predicate with another condition.
101    #[must_use]
102    #[inline]
103    pub fn and<P>(self, other: P) -> AndPredicate<Self, P> {
104        and(self, other)
105    }
106}
107
108impl core::ops::Not for Predicate {
109    type Output = NotPredicate<Self>;
110
111    #[inline]
112    fn not(self) -> Self::Output {
113        not(self)
114    }
115}
116
117/// A predicate accepted by [`Rtree::query_with`](crate::Rtree::query_with).
118///
119/// `matches` decides leaf values. `could_match` and `covers_all` are
120/// conservative subtree tests: false from the former prunes a subtree;
121/// true from the latter yields it without further predicate calls.
122pub trait QueryPredicate<T: Indexable> {
123    /// Whether one indexed value satisfies the predicate.
124    fn matches(&self, value: &T) -> bool;
125
126    /// Whether a subtree may contain a match.
127    fn could_match(&self, node: &Bounds) -> bool;
128
129    /// Whether every value in a subtree is guaranteed to match.
130    fn covers_all(&self, node: &Bounds) -> bool;
131}
132
133impl<T: Indexable> QueryPredicate<T> for Predicate {
134    #[inline]
135    fn matches(&self, value: &T) -> bool {
136        self.matches(&value.bounds())
137    }
138
139    #[inline]
140    fn could_match(&self, node: &Bounds) -> bool {
141        self.could_match(node)
142    }
143
144    #[inline]
145    fn covers_all(&self, node: &Bounds) -> bool {
146        self.covers_all(node)
147    }
148}
149
150/// The conjunction of two query predicates.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct AndPredicate<Left, Right> {
153    left: Left,
154    right: Right,
155}
156
157/// Combine two query predicates with logical conjunction.
158#[must_use]
159#[inline]
160pub fn and<Left, Right>(left: Left, right: Right) -> AndPredicate<Left, Right> {
161    AndPredicate { left, right }
162}
163
164impl<T, Left, Right> QueryPredicate<T> for AndPredicate<Left, Right>
165where
166    T: Indexable,
167    Left: QueryPredicate<T>,
168    Right: QueryPredicate<T>,
169{
170    #[inline]
171    fn matches(&self, value: &T) -> bool {
172        self.left.matches(value) && self.right.matches(value)
173    }
174
175    #[inline]
176    fn could_match(&self, node: &Bounds) -> bool {
177        self.left.could_match(node) && self.right.could_match(node)
178    }
179
180    #[inline]
181    fn covers_all(&self, node: &Bounds) -> bool {
182        self.left.covers_all(node) && self.right.covers_all(node)
183    }
184}
185
186/// The logical negation of a query predicate.
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub struct NotPredicate<Inner> {
189    inner: Inner,
190}
191
192/// Negate a query predicate.
193#[must_use]
194#[inline]
195pub fn not<Inner>(inner: Inner) -> NotPredicate<Inner> {
196    NotPredicate { inner }
197}
198
199impl<T, Inner> QueryPredicate<T> for NotPredicate<Inner>
200where
201    T: Indexable,
202    Inner: QueryPredicate<T>,
203{
204    #[inline]
205    fn matches(&self, value: &T) -> bool {
206        !self.inner.matches(value)
207    }
208
209    #[inline]
210    fn could_match(&self, node: &Bounds) -> bool {
211        !self.inner.covers_all(node)
212    }
213
214    #[inline]
215    fn covers_all(&self, node: &Bounds) -> bool {
216        !self.inner.could_match(node)
217    }
218}
219
220/// A value-level query condition corresponding to Boost's
221/// `index::satisfies` predicate.
222pub struct Satisfies<F> {
223    condition: F,
224}
225
226/// Build a value-level `satisfies` query predicate.
227#[must_use]
228#[inline]
229pub fn satisfies<F>(condition: F) -> Satisfies<F> {
230    Satisfies { condition }
231}
232
233impl<T, F> QueryPredicate<T> for Satisfies<F>
234where
235    T: Indexable,
236    F: Fn(&T) -> bool,
237{
238    #[inline]
239    fn matches(&self, value: &T) -> bool {
240        (self.condition)(value)
241    }
242
243    #[inline]
244    fn could_match(&self, _node: &Bounds) -> bool {
245        true
246    }
247
248    #[inline]
249    fn covers_all(&self, _node: &Bounds) -> bool {
250        false
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::Predicate;
257    use crate::bounds::Bounds;
258
259    #[test]
260    fn intersects_matches_overlap() {
261        let p = Predicate::Intersects(Bounds::new([0.0, 0.0], [2.0, 2.0]));
262        assert!(p.matches(&Bounds::new([1.0, 1.0], [3.0, 3.0])));
263        assert!(!p.matches(&Bounds::new([5.0, 5.0], [6.0, 6.0])));
264    }
265
266    #[test]
267    fn within_matches_contained() {
268        let p = Predicate::Within(Bounds::new([0.0, 0.0], [10.0, 10.0]));
269        assert!(p.matches(&Bounds::new([2.0, 2.0], [3.0, 3.0])));
270        assert!(!p.matches(&Bounds::new([2.0, 2.0], [12.0, 3.0])));
271    }
272
273    #[test]
274    fn contains_matches_covering() {
275        let p = Predicate::Contains(Bounds::new([4.0, 4.0], [5.0, 5.0]));
276        assert!(p.matches(&Bounds::new([0.0, 0.0], [10.0, 10.0])));
277        assert!(!p.matches(&Bounds::new([0.0, 0.0], [4.5, 4.5])));
278    }
279
280    #[test]
281    fn pruning_skips_disjoint_subtrees() {
282        let p = Predicate::Intersects(Bounds::new([0.0, 0.0], [1.0, 1.0]));
283        assert!(p.could_match(&Bounds::new([0.5, 0.5], [9.0, 9.0])));
284        assert!(!p.could_match(&Bounds::new([5.0, 5.0], [9.0, 9.0])));
285    }
286
287    const QUERY: Bounds = Bounds::new([0.0, 0.0], [10.0, 10.0]);
288    const CONTAINED: Bounds = Bounds::new([2.0, 2.0], [3.0, 3.0]);
289    const OVERLAPPING: Bounds = Bounds::new([5.0, 5.0], [15.0, 15.0]);
290    const DISJOINT: Bounds = Bounds::new([20.0, 20.0], [30.0, 30.0]);
291
292    #[test]
293    fn covers_all_intersects() {
294        let p = Predicate::Intersects(QUERY);
295        assert!(p.covers_all(&CONTAINED));
296        assert!(p.covers_all(&QUERY));
297        assert!(!p.covers_all(&OVERLAPPING));
298        assert!(!p.covers_all(&DISJOINT));
299    }
300
301    #[test]
302    fn covers_all_within_never_assumes_value_dimension() {
303        let p = Predicate::Within(QUERY);
304        assert!(!p.covers_all(&CONTAINED));
305        assert!(!p.covers_all(&QUERY));
306        assert!(!p.covers_all(&OVERLAPPING));
307        assert!(!p.covers_all(&DISJOINT));
308    }
309
310    #[test]
311    fn covers_all_contains_never() {
312        let p = Predicate::Contains(QUERY);
313        assert!(!p.covers_all(&CONTAINED));
314        assert!(!p.covers_all(&QUERY));
315        assert!(!p.covers_all(&OVERLAPPING));
316        assert!(!p.covers_all(&DISJOINT));
317    }
318}