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}