sif_rtree/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs, missing_debug_implementations)]
3
4//! A simple library implementing an immutable, flat representation of an [R-tree](https://en.wikipedia.org/wiki/R-tree)
5//!
6//! The library uses the same overlap-minimizing top-down bulk loading algorithm as the [rstar](https://github.com/georust/rstar) crate.
7//! Its supports several kinds of spatial queries, nearest neighbour search and has a simple implementation as the objects in the index are fixed after construction.
8//! This also enables a flat and thereby cache-friendly memory layout which can be backed by memory maps.
9//!
10//! The library provides optional integration with [serde] for (de-)serialization of the trees.
11//!
12//! # Example
13//!
14//! ```
15//! use std::ops::ControlFlow;
16//!
17//! use sif_rtree::{DEF_NODE_LEN, Distance, RTree, Object};
18//!
19//! struct Something(usize, [f64; 2]);
20//!
21//! impl Object for Something {
22//!     type Point = [f64; 2];
23//!
24//!     fn aabb(&self) -> (Self::Point, Self::Point) {
25//!         (self.1, self.1)
26//!     }
27//! }
28//!
29//! impl Distance<[f64; 2]> for Something {
30//!     fn distance_2(&self, point: &[f64; 2]) -> f64 {
31//!         self.1.distance_2(point)
32//!     }
33//! }
34//!
35//! let index = RTree::new(
36//!     DEF_NODE_LEN,
37//!     vec![
38//!         Something(0, [-0.4, -3.3]),
39//!         Something(1, [-4.5, -1.8]),
40//!         Something(2, [0.7, 2.0]),
41//!         Something(3, [1.7, 1.5]),
42//!         Something(4, [-1.3, 2.3]),
43//!         Something(5, [2.2, 1.0]),
44//!         Something(6, [-3.7, 3.8]),
45//!         Something(7, [-3.2, -0.1]),
46//!         Something(8, [1.4, 2.7]),
47//!         Something(9, [3.1, -0.0]),
48//!         Something(10, [4.3, 0.8]),
49//!         Something(11, [3.9, -3.3]),
50//!         Something(12, [0.4, -3.2]),
51//!     ],
52//! );
53//!
54//! let mut close_by = Vec::new();
55//!
56//! index
57//!     .look_up_within_distance_of_point(&[0., 0.], 3., |thing| {
58//!         close_by.push(thing.0);
59//!
60//!         ControlFlow::<()>::Continue(())
61//!     })
62//!     .continue_value()
63//!     .unwrap();
64//!
65//! assert_eq!(close_by, [3, 5, 4, 2]);
66//! ```
67//!
68//! The [`RTree`] data structure is generic over its backing storage as long as it can be converted into a slice via the [`AsRef`] trait. This can for instance be used to memory map R-trees from persistent storage.
69//!
70//! ```no_run
71//! # fn main() -> std::io::Result<()> {
72//! use std::fs::File;
73//! use std::mem::size_of;
74//! use std::slice::from_raw_parts;
75//!
76//! use memmap2::Mmap;
77//!
78//! use sif_rtree::{Node, Object, Point, RTree};
79//!
80//! #[derive(Clone, Copy)]
81//! struct Triangle([[f64; 3]; 3]);
82//!
83//! impl Object for Triangle {
84//!     type Point = [f64; 3];
85//!
86//!     fn aabb(&self) -> (Self::Point, Self::Point) {
87//!         let min = self.0[0].min(&self.0[1]).min(&self.0[2]);
88//!         let max = self.0[0].max(&self.0[1]).max(&self.0[2]);
89//!         (min, max)
90//!     }
91//! }
92//!
93//! let file = File::open("index.bin")?;
94//! let map = unsafe { Mmap::map(&file)? };
95//!
96//! struct TriangleSoup(Mmap);
97//!
98//! impl AsRef<[Node<Triangle>]> for TriangleSoup {
99//!     fn as_ref(&self) -> &[Node<Triangle>] {
100//!         let ptr = self.0.as_ptr().cast();
101//!         let len = self.0.len() / size_of::<Node<Triangle>>();
102//!
103//!         unsafe { from_raw_parts(ptr, len) }
104//!     }
105//! }
106//!
107//! let index = RTree::new_unchecked(TriangleSoup(map));
108//! # Ok(()) }
109//! ```
110
111mod build;
112mod iter;
113mod look_up;
114mod nearest;
115
116pub use build::DEF_NODE_LEN;
117
118use std::marker::PhantomData;
119use std::num::NonZeroUsize;
120use std::ops::Deref;
121
122use num_traits::{Num, Zero};
123#[cfg(feature = "serde")]
124use serde::{de::DeserializeOwned, Deserialize, Serialize};
125
126/// Defines a [finite-dimensional][Self::DIM] space in terms of [coordinate values][Self::coord] along a chosen set of axes
127pub trait Point: Clone {
128    /// The dimension of the underlying space
129    const DIM: usize;
130
131    /// The type of the coordinate values
132    type Coord: Num + Copy + PartialOrd;
133
134    /// Access the coordinate value of the point along the given `axis`
135    fn coord(&self, axis: usize) -> Self::Coord;
136
137    /// Builds a new point by specifying its coordinate values along each axis
138    ///
139    /// # Example
140    ///
141    /// ```rust
142    /// use sif_rtree::Point;
143    ///
144    /// fn scale<P>(point: P, factor: P::Coord) -> P
145    /// where
146    ///     P: Point,
147    /// {
148    ///     P::build(|axis| point.coord(axis) * factor)
149    /// }
150    /// ```
151    fn build<F>(f: F) -> Self
152    where
153        F: FnMut(usize) -> Self::Coord;
154
155    /// Computes the point which has the minimum coordinate values of `self` and `other` along each axis
156    ///
157    /// The default implementation is based on [`build`][Self::build] and [`PartialOrd`] and assumes that coordinate values are finite.
158    fn min(&self, other: &Self) -> Self {
159        Self::build(|axis| {
160            let this = self.coord(axis);
161            let other = other.coord(axis);
162
163            if this < other {
164                this
165            } else {
166                other
167            }
168        })
169    }
170
171    /// Computes the point which has the maximum coordinate values of `self` and `other` along each axis
172    ///
173    /// The default implementation is based on [`build`][Self::build] and [`PartialOrd`] and assumes that coordinate values are finite.
174    fn max(&self, other: &Self) -> Self {
175        Self::build(|axis| {
176            let this = self.coord(axis);
177            let other = other.coord(axis);
178
179            if this > other {
180                this
181            } else {
182                other
183            }
184        })
185    }
186}
187
188/// `N`-dimensional space using [Euclidean distance](https://en.wikipedia.org/wiki/Euclidean_distance)
189impl<T, const N: usize> Point for [T; N]
190where
191    T: Num + Copy + PartialOrd,
192{
193    const DIM: usize = N;
194
195    type Coord = T;
196
197    #[inline]
198    fn coord(&self, axis: usize) -> Self::Coord {
199        self[axis]
200    }
201
202    fn build<F>(mut f: F) -> Self
203    where
204        F: FnMut(usize) -> Self::Coord,
205    {
206        let mut res = [T::zero(); N];
207
208        (0..N).for_each(|axis| res[axis] = f(axis));
209
210        res
211    }
212}
213
214impl<T, const N: usize> Distance<[T; N]> for [T; N]
215where
216    T: Num + Copy + PartialOrd,
217{
218    fn distance_2(&self, point: &[T; N]) -> T {
219        (0..N).fold(T::zero(), |res, axis| {
220            let diff = self[axis] - point[axis];
221
222            res + diff * diff
223        })
224    }
225}
226
227/// Defines the objects which can be organized in an [`RTree`] by specifying their extent in the vector space defined via the [`Point`] trait
228pub trait Object {
229    /// The [`Point`] implementation used to represent the [axis-aligned bounding boxes (AABB)][`Self::aabb`] of these objects.
230    type Point: Point;
231
232    /// Return the axis-aligned bounding box (AABB) associated with this object.
233    ///
234    /// Note that this method is called repeatedly during construction and querying. Hence it might be necessary to cache the value internally to avoid the cost of computing it repeatedly.
235    fn aabb(&self) -> (Self::Point, Self::Point);
236}
237
238/// Defines a distance metric between a type (objects, points, AABB, etc.) and [points][`Point`]
239pub trait Distance<P>
240where
241    P: Point,
242{
243    /// Return the squared distance between `self` and `point`
244    ///
245    /// Generally, only the relation between two distance values is required so that computing square roots can be avoided.
246    fn distance_2(&self, point: &P) -> P::Coord;
247
248    /// Checks whether `self` contains `point`
249    ///
250    /// The default implementation just checks whether the squared distance is zero.
251    fn contains(&self, point: &P) -> bool {
252        self.distance_2(point) == P::Coord::zero()
253    }
254}
255
256// Should be a power of two and as large as possible without `Twig` becoming the largest variant of `Node`.
257const TWIG_LEN: usize = 4;
258
259/// A node in the tree
260///
261/// The tree starts at a root node which is always a [`Branch`][Self::Branch] stored at index zero.
262/// It ends with the [`Leaf`][Self::Leaf] nodes which contains the objects stored in the tree.
263#[derive(Debug, Clone)]
264#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
265pub enum Node<O>
266where
267    O: Object,
268{
269    /// A branch in the tree
270    ///
271    /// The indices of the nodes belonging to this branch are stored separately in [`Twig`][Self::Twig] nodes immediately following this node.
272    /// The first of these `Twig` nodes starts with padding if `len` is not a multiple of `TWIG_LEN`.
273    Branch {
274        /// The number of nodes belonging to this branch
275        len: NonZeroUsize,
276        /// The merged axis-aligned bounding box (AABB) of all the nodes belonging to this branch
277        #[cfg_attr(
278            feature = "serde",
279            serde(bound = "O::Point: Serialize + DeserializeOwned")
280        )]
281        aabb: (O::Point, O::Point),
282    },
283    /// Contains the indices of nodes belonging to a branch
284    Twig([usize; TWIG_LEN]),
285    /// Contains an object stored in the tree
286    Leaf(O),
287}
288
289const ROOT_IDX: usize = 0;
290
291/// An immutable, flat representation of an [R-tree](https://en.wikipedia.org/wiki/R-tree)
292///
293/// Accelerates spatial queries by grouping objects based on their axis-aligned bounding boxes (AABB).
294///
295/// Note that this tree dereferences to and deserializes as a slice of nodes. Modifying node geometry through interior mutability or deserializing a modified sequence is safe but will lead to incorrect results.
296#[derive(Debug, Clone)]
297#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
298#[cfg_attr(feature = "serde", serde(transparent))]
299pub struct RTree<O, S = Box<[Node<O>]>>
300where
301    O: Object,
302    S: AsRef<[Node<O>]>,
303{
304    nodes: S,
305    _marker: PhantomData<O>,
306}
307
308impl<O, S> RTree<O, S>
309where
310    O: Object,
311    S: AsRef<[Node<O>]>,
312{
313    /// Interprets the given `nodes` as a tree
314    ///
315    /// Supplying `nodes` which are not actually organized as an R-tree is safe but will lead to incorrect results.
316    pub fn new_unchecked(nodes: S) -> Self {
317        assert!(!nodes.as_ref().is_empty());
318
319        Self {
320            nodes,
321            _marker: PhantomData,
322        }
323    }
324
325    /// Iterators over the objects stored in the leaf nodes of the tree
326    pub fn objects(&self) -> impl Iterator<Item = &O> {
327        self.nodes.as_ref().iter().filter_map(|node| match node {
328            Node::Branch { .. } | Node::Twig(_) => None,
329            Node::Leaf(obj) => Some(obj),
330        })
331    }
332}
333
334impl<O, S> Deref for RTree<O, S>
335where
336    O: Object,
337    S: AsRef<[Node<O>]>,
338{
339    type Target = [Node<O>];
340
341    fn deref(&self) -> &Self::Target {
342        self.nodes.as_ref()
343    }
344}
345
346impl<O, S> AsRef<[Node<O>]> for RTree<O, S>
347where
348    O: Object,
349    S: AsRef<[Node<O>]>,
350{
351    fn as_ref(&self) -> &[Node<O>] {
352        self.nodes.as_ref()
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    use std::cmp::Ordering;
361
362    use proptest::{collection::vec, strategy::Strategy};
363
364    pub fn random_points(len: usize) -> impl Strategy<Value = Vec<[f32; 3]>> {
365        (
366            vec(0.0_f32..=1.0, len),
367            vec(0.0_f32..=1.0, len),
368            vec(0.0_f32..=1.0, len),
369        )
370            .prop_map(|(x, y, z)| {
371                x.into_iter()
372                    .zip(y)
373                    .zip(z)
374                    .map(|((x, y), z)| [x, y, z])
375                    .collect()
376            })
377    }
378
379    #[derive(Debug, Clone, PartialEq)]
380    pub struct RandomObject(pub [f32; 3], pub [f32; 3]);
381
382    impl Eq for RandomObject {}
383
384    impl PartialOrd for RandomObject {
385        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
386            Some(self.cmp(other))
387        }
388    }
389
390    impl Ord for RandomObject {
391        fn cmp(&self, other: &Self) -> Ordering {
392            (self.0, self.1).partial_cmp(&(other.0, other.1)).unwrap()
393        }
394    }
395
396    impl Object for RandomObject {
397        type Point = [f32; 3];
398
399        fn aabb(&self) -> (Self::Point, Self::Point) {
400            (self.0, self.1)
401        }
402    }
403
404    impl Distance<[f32; 3]> for RandomObject {
405        fn distance_2(&self, point: &[f32; 3]) -> f32 {
406            self.aabb().distance_2(point)
407        }
408
409        fn contains(&self, point: &[f32; 3]) -> bool {
410            self.aabb().contains(point)
411        }
412    }
413
414    pub fn random_objects(len: usize) -> impl Strategy<Value = Vec<RandomObject>> {
415        (random_points(len), random_points(len)).prop_map(|(left, right)| {
416            left.into_iter()
417                .zip(right)
418                .map(|(left, right)| {
419                    let lower = left.min(&right);
420                    let upper = left.max(&right);
421                    RandomObject(lower, upper)
422                })
423                .collect()
424        })
425    }
426}