geo_index/rtree/
trait.rs

1use std::cmp::Reverse;
2use std::collections::BinaryHeap;
3
4use geo_traits::{CoordTrait, RectTrait};
5
6use crate::error::Result;
7use crate::indices::Indices;
8use crate::r#type::IndexableNum;
9use crate::rtree::index::{RTree, RTreeRef};
10use crate::rtree::traversal::{IntersectionIterator, Node};
11use crate::rtree::util::upper_bound;
12use crate::rtree::RTreeMetadata;
13use crate::GeoIndexError;
14
15/// A trait for searching and accessing data out of an RTree.
16pub trait RTreeIndex<N: IndexableNum>: Sized {
17    /// A slice representing all the bounding boxes of all elements contained within this tree,
18    /// including the bounding boxes of each internal node.
19    fn boxes(&self) -> &[N];
20
21    /// A slice representing the indices within the `boxes` slice, including internal nodes.
22    fn indices(&self) -> Indices;
23
24    /// Access the metadata describing this RTree
25    fn metadata(&self) -> &RTreeMetadata<N>;
26
27    /// The total number of items contained in this RTree.
28    fn num_items(&self) -> u32 {
29        self.metadata().num_items()
30    }
31
32    /// The total number of nodes in this RTree, including both leaf and intermediate nodes.
33    fn num_nodes(&self) -> usize {
34        self.metadata().num_nodes()
35    }
36
37    /// The maximum number of elements in each node.
38    fn node_size(&self) -> u16 {
39        self.metadata().node_size()
40    }
41
42    /// The offsets into [RTreeIndex::boxes] where each level's boxes starts and ends. The tree is
43    /// laid out bottom-up, and there's an implicit initial 0. So the boxes of the lowest level of
44    /// the tree are located from `boxes[0..self.level_bounds()[0]]`.
45    fn level_bounds(&self) -> &[usize] {
46        self.metadata().level_bounds()
47    }
48
49    /// The number of levels (height) of the tree.
50    fn num_levels(&self) -> usize {
51        self.level_bounds().len()
52    }
53
54    /// The tree is laid out from bottom to top. Level 0 is the _base_ of the tree. Each integer
55    /// higher is one level higher of the tree.
56    fn boxes_at_level(&self, level: usize) -> Result<&[N]> {
57        let level_bounds = self.level_bounds();
58        if level >= level_bounds.len() {
59            return Err(GeoIndexError::General("Level out of bounds".to_string()));
60        }
61        let result = if level == 0 {
62            &self.boxes()[0..level_bounds[0]]
63        } else if level == level_bounds.len() {
64            &self.boxes()[level_bounds[level]..]
65        } else {
66            &self.boxes()[level_bounds[level - 1]..level_bounds[level]]
67        };
68        Ok(result)
69    }
70
71    /// Search an RTree given the provided bounding box.
72    ///
73    /// Results are the indexes of the inserted objects in insertion order.
74    fn search(&self, min_x: N, min_y: N, max_x: N, max_y: N) -> Vec<u32> {
75        let boxes = self.boxes();
76        let indices = self.indices();
77        if boxes.is_empty() {
78            return vec![];
79        }
80
81        let mut outer_node_index = boxes.len().checked_sub(4);
82
83        let mut queue = vec![];
84        let mut results = vec![];
85
86        while let Some(node_index) = outer_node_index {
87            // find the end index of the node
88            let end = (node_index + self.node_size() as usize * 4)
89                .min(upper_bound(node_index, self.level_bounds()));
90
91            // search through child nodes
92            for pos in (node_index..end).step_by(4) {
93                // check if node bbox intersects with query bbox
94                if max_x < boxes[pos] {
95                    continue; // maxX < nodeMinX
96                }
97                if max_y < boxes[pos + 1] {
98                    continue; // maxY < nodeMinY
99                }
100                if min_x > boxes[pos + 2] {
101                    continue; // minX > nodeMaxX
102                }
103                if min_y > boxes[pos + 3] {
104                    continue; // minY > nodeMaxY
105                }
106
107                let index = indices.get(pos >> 2);
108
109                if node_index >= self.num_items() as usize * 4 {
110                    queue.push(index); // node; add it to the search queue
111                } else {
112                    // Since the max items of the index is u32, we can coerce to u32
113                    results.push(index.try_into().unwrap()); // leaf item
114                }
115            }
116
117            outer_node_index = queue.pop();
118        }
119
120        results
121    }
122
123    /// Search an RTree given the provided bounding box.
124    ///
125    /// Results are the indexes of the inserted objects in insertion order.
126    fn search_rect(&self, rect: &impl RectTrait<T = N>) -> Vec<u32> {
127        self.search(
128            rect.min().x(),
129            rect.min().y(),
130            rect.max().x(),
131            rect.max().y(),
132        )
133    }
134
135    /// Search items in order of distance from the given point.
136    ///
137    /// ```
138    /// use geo_index::rtree::{RTreeBuilder, RTreeIndex, RTreeRef};
139    /// use geo_index::rtree::sort::HilbertSort;
140    ///
141    /// // Create an RTree
142    /// let mut builder = RTreeBuilder::<f64>::new(3);
143    /// builder.add(0., 0., 2., 2.);
144    /// builder.add(1., 1., 3., 3.);
145    /// builder.add(2., 2., 4., 4.);
146    /// let tree = builder.finish::<HilbertSort>();
147    ///
148    /// let results = tree.neighbors(5., 5., None, None);
149    /// assert_eq!(results, vec![2, 1, 0]);
150    /// ```
151    fn neighbors(
152        &self,
153        x: N,
154        y: N,
155        max_results: Option<usize>,
156        max_distance: Option<N>,
157    ) -> Vec<u32> {
158        let boxes = self.boxes();
159        let indices = self.indices();
160        let max_distance = max_distance.unwrap_or(N::max_value());
161
162        let mut outer_node_index = Some(boxes.len() - 4);
163        let mut queue = BinaryHeap::new();
164        let mut results: Vec<u32> = vec![];
165        let max_dist_squared = max_distance * max_distance;
166
167        'outer: while let Some(node_index) = outer_node_index {
168            // find the end index of the node
169            let end = (node_index + self.node_size() as usize * 4)
170                .min(upper_bound(node_index, self.level_bounds()));
171
172            // add child nodes to the queue
173            for pos in (node_index..end).step_by(4) {
174                let index = indices.get(pos >> 2);
175
176                let dx = axis_dist(x, boxes[pos], boxes[pos + 2]);
177                let dy = axis_dist(y, boxes[pos + 1], boxes[pos + 3]);
178                let dist = dx * dx + dy * dy;
179                if dist > max_dist_squared {
180                    continue;
181                }
182
183                if node_index >= self.num_items() as usize * 4 {
184                    // node (use even id)
185                    queue.push(Reverse(NeighborNode {
186                        id: index << 1,
187                        dist,
188                    }));
189                } else {
190                    // leaf item (use odd id)
191                    queue.push(Reverse(NeighborNode {
192                        id: (index << 1) + 1,
193                        dist,
194                    }));
195                }
196            }
197
198            // pop items from the queue
199            while !queue.is_empty() && queue.peek().is_some_and(|val| (val.0.id & 1) != 0) {
200                let dist = queue.peek().unwrap().0.dist;
201                if dist > max_dist_squared {
202                    break 'outer;
203                }
204                let item = queue.pop().unwrap();
205                results.push((item.0.id >> 1).try_into().unwrap());
206                if max_results.is_some_and(|max_results| results.len() == max_results) {
207                    break 'outer;
208                }
209            }
210
211            if let Some(item) = queue.pop() {
212                outer_node_index = Some(item.0.id >> 1);
213            } else {
214                outer_node_index = None;
215            }
216        }
217
218        results
219    }
220
221    /// Search items in order of distance from the given coordinate.
222    fn neighbors_coord(
223        &self,
224        coord: &impl CoordTrait<T = N>,
225        max_results: Option<usize>,
226        max_distance: Option<N>,
227    ) -> Vec<u32> {
228        self.neighbors(coord.x(), coord.y(), max_results, max_distance)
229    }
230
231    /// Returns an iterator over the indexes of objects in this and another tree that intersect.
232    ///
233    /// Each returned object is of the form `(u32, u32)`, where the first is the positional
234    /// index of the "left" tree and the second is the index of the "right" tree.
235    fn intersection_candidates_with_other_tree<'a>(
236        &'a self,
237        other: &'a impl RTreeIndex<N>,
238    ) -> impl Iterator<Item = (u32, u32)> + 'a {
239        IntersectionIterator::from_trees(self, other)
240    }
241
242    /// Access the root node of the RTree for manual traversal.
243    fn root(&self) -> Node<'_, N, Self> {
244        Node::from_root(self)
245    }
246}
247
248/// A wrapper around a node and its distance for use in the priority queue.
249#[derive(Debug, Clone, Copy, PartialEq)]
250struct NeighborNode<N: IndexableNum> {
251    id: usize,
252    dist: N,
253}
254
255impl<N: IndexableNum> Eq for NeighborNode<N> {}
256
257impl<N: IndexableNum> Ord for NeighborNode<N> {
258    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
259        // We don't allow NaN. This should only panic on NaN
260        self.dist.partial_cmp(&other.dist).unwrap()
261    }
262}
263
264impl<N: IndexableNum> PartialOrd for NeighborNode<N> {
265    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
266        Some(self.cmp(other))
267    }
268}
269
270impl<N: IndexableNum> RTreeIndex<N> for RTree<N> {
271    fn boxes(&self) -> &[N] {
272        self.metadata.boxes_slice(&self.buffer)
273    }
274
275    fn indices(&self) -> Indices {
276        self.metadata.indices_slice(&self.buffer)
277    }
278
279    fn metadata(&self) -> &RTreeMetadata<N> {
280        &self.metadata
281    }
282}
283
284impl<N: IndexableNum> RTreeIndex<N> for RTreeRef<'_, N> {
285    fn boxes(&self) -> &[N] {
286        self.boxes
287    }
288
289    fn indices(&self) -> Indices {
290        self.indices
291    }
292
293    fn metadata(&self) -> &RTreeMetadata<N> {
294        &self.metadata
295    }
296}
297
298/// 1D distance from a value to a range.
299#[allow(dead_code)]
300#[inline]
301fn axis_dist<N: IndexableNum>(k: N, min: N, max: N) -> N {
302    if k < min {
303        min - k
304    } else if k <= max {
305        N::zero()
306    } else {
307        k - max
308    }
309}
310
311#[cfg(test)]
312mod test {
313    // Replication of tests from flatbush js
314    mod js {
315        use crate::rtree::RTreeIndex;
316        use crate::test::{flatbush_js_test_data, flatbush_js_test_index};
317
318        #[test]
319        fn performs_bbox_search() {
320            let data = flatbush_js_test_data();
321            let index = flatbush_js_test_index();
322            let ids = index.search(40., 40., 60., 60.);
323
324            let mut results: Vec<usize> = vec![];
325            for id in ids {
326                results.push(data[4 * id as usize] as usize);
327                results.push(data[4 * id as usize + 1] as usize);
328                results.push(data[4 * id as usize + 2] as usize);
329                results.push(data[4 * id as usize + 3] as usize);
330            }
331
332            results.sort();
333
334            let mut expected = vec![
335                57, 59, 58, 59, 48, 53, 52, 56, 40, 42, 43, 43, 43, 41, 47, 43,
336            ];
337            expected.sort();
338
339            assert_eq!(results, expected);
340        }
341    }
342}