use rstar::primitives::GeomWithData;
use rstar::RTree;
type Entry = GeomWithData<[f64; 2], usize>;
#[derive(Default)]
pub struct Neighbourhood {
tree: RTree<Entry>,
}
impl Neighbourhood {
pub fn new() -> Neighbourhood {
Neighbourhood { tree: RTree::new() }
}
pub fn from_points(points: &[[f64; 2]]) -> Neighbourhood {
let entries: Vec<Entry> = points
.iter()
.enumerate()
.map(|(i, p)| GeomWithData::new(*p, i))
.collect();
Neighbourhood {
tree: RTree::bulk_load(entries),
}
}
pub fn insert(&mut self, xy: [f64; 2], index: usize) {
self.tree.insert(GeomWithData::new(xy, index));
}
pub fn nearest(&self, target: [f64; 2], max_n: usize, radius: f64) -> Vec<(usize, f64)> {
let mut out = Vec::with_capacity(max_n);
self.nearest_into(target, max_n, radius, &mut out);
out
}
pub fn nearest_into(
&self,
target: [f64; 2],
max_n: usize,
radius: f64,
out: &mut Vec<(usize, f64)>,
) {
out.clear();
if max_n == 0 {
return;
}
let r2 = radius * radius;
for (entry, d2) in self.tree.nearest_neighbor_iter_with_distance_2(target) {
if d2 > r2 {
break;
}
out.push((entry.data, d2.sqrt()));
if out.len() >= max_n {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_up_to_max_n_nearest_first() {
let pts = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [3.0, 0.0]];
let nb = Neighbourhood::from_points(&pts);
let got = nb.nearest([0.0, 0.0], 2, 100.0);
assert_eq!(got.len(), 2);
assert_eq!(got[0].0, 0); assert_eq!(got[1].0, 1); assert!(got[0].1 <= got[1].1);
}
#[test]
fn radius_excludes_far_points() {
let pts = [[0.0, 0.0], [1.0, 0.0], [10.0, 0.0]];
let nb = Neighbourhood::from_points(&pts);
let got = nb.nearest([0.0, 0.0], 10, 2.0);
assert_eq!(got.len(), 2); }
#[test]
fn insert_grows_the_index() {
let mut nb = Neighbourhood::new();
assert!(nb.nearest([0.0, 0.0], 5, 100.0).is_empty());
nb.insert([0.0, 0.0], 7);
nb.insert([1.0, 1.0], 9);
let got = nb.nearest([0.0, 0.0], 5, 100.0);
assert_eq!(got.len(), 2);
assert_eq!(got[0].0, 7);
}
#[test]
fn zero_max_n_is_empty() {
let nb = Neighbourhood::from_points(&[[0.0, 0.0]]);
assert!(nb.nearest([0.0, 0.0], 0, 100.0).is_empty());
}
}