use crate::errors::SpartError;
use crate::geometry::{
BSPBounds, BoundedObject, BoundingVolume, BoundingVolumeFromPoint, DistanceMetric,
HasMinDistance, Point2D, Point3D,
};
use crate::knn::KnnHeap;
use crate::rtree_common::{
KnnCandidate, compute_group_mbr as common_compute_group_mbr,
delete_entry as common_delete_entry, node_height as common_node_height,
search_node as common_search_node,
};
use ordered_float::OrderedFloat;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use tracing::info;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) enum RStarTreeEntry<T: BoundedObject> {
Leaf {
mbr: T::Volume,
object: T,
},
Node {
mbr: T::Volume,
child: Box<RStarTreeNode<T>>,
},
}
impl<T: BoundedObject> RStarTreeEntry<T> {
pub(crate) fn mbr(&self) -> &T::Volume {
match self {
RStarTreeEntry::Leaf { mbr, .. } => mbr,
RStarTreeEntry::Node { mbr, .. } => mbr,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub(crate) struct RStarTreeNode<T: BoundedObject> {
pub(crate) entries: Vec<RStarTreeEntry<T>>,
pub(crate) is_leaf: bool,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RStarTree<T: BoundedObject> {
root: RStarTreeNode<T>,
max_entries: usize,
min_entries: usize,
len: usize,
}
impl<T: BoundedObject> crate::rtree_common::EntryAccess for RStarTreeEntry<T> {
type BV = T::Volume;
type Node = RStarTreeNode<T>;
type Obj = T;
fn mbr(&self) -> &Self::BV {
RStarTreeEntry::mbr(self)
}
fn as_leaf_obj(&self) -> Option<&Self::Obj> {
match self {
RStarTreeEntry::Leaf { object, .. } => Some(object),
_ => None,
}
}
fn child(&self) -> Option<&<Self as crate::rtree_common::EntryAccess>::Node> {
match self {
RStarTreeEntry::Node { child, .. } => Some(child),
_ => None,
}
}
fn child_mut(&mut self) -> Option<&mut <Self as crate::rtree_common::EntryAccess>::Node> {
match self {
RStarTreeEntry::Node { child, .. } => Some(child),
_ => None,
}
}
fn set_mbr(&mut self, new_mbr: Self::BV) {
if let RStarTreeEntry::Node { mbr, .. } = self {
*mbr = new_mbr;
}
}
fn into_child(self) -> Option<Box<<Self as crate::rtree_common::EntryAccess>::Node>>
where
Self: Sized,
{
match self {
RStarTreeEntry::Node { child, .. } => Some(child),
_ => None,
}
}
}
impl<T: BoundedObject> crate::rtree_common::NodeAccess for RStarTreeNode<T> {
type Entry = RStarTreeEntry<T>;
fn is_leaf(&self) -> bool {
self.is_leaf
}
fn entries(&self) -> &Vec<Self::Entry> {
&self.entries
}
fn entries_mut(&mut self) -> &mut Vec<Self::Entry> {
&mut self.entries
}
}
impl<T: BoundedObject> RStarTree<T> {
pub fn new(max_entries: usize) -> Result<Self, SpartError> {
if max_entries < 2 {
return Err(SpartError::InvalidCapacity {
capacity: max_entries,
});
}
info!("Creating new RStarTree with max_entries: {}", max_entries);
Ok(RStarTree {
root: RStarTreeNode {
entries: Vec::new(),
is_leaf: true,
},
max_entries,
min_entries: (max_entries as f64 * 0.4).ceil() as usize,
len: 0,
})
}
pub fn insert(&mut self, object: T)
where
T: Clone,
T::Volume: BSPBounds,
{
info!("Inserting object into RStarTree: {:?}", object);
let entry = RStarTreeEntry::Leaf {
mbr: object.mbr(),
object,
};
self.insert_entry_at(entry, 0);
self.len += 1;
}
fn insert_entry_at(&mut self, entry: RStarTreeEntry<T>, target_height: usize)
where
T: Clone,
T::Volume: BSPBounds,
{
let mut state = InsertState {
max_entries: self.max_entries,
min_entries: self.min_entries,
reinserted_heights: Vec::new(),
pending: vec![(entry, target_height)],
};
while let Some((entry, entry_height)) = state.pending.pop() {
let height = common_node_height(&self.root);
let target_height = entry_height.min(height);
let overflow = insert_recursive(
&mut self.root,
entry,
target_height,
height,
true,
&mut state,
);
if let Some(sibling) = overflow {
self.grow_root(sibling);
}
}
}
fn grow_root(&mut self, sibling: RStarTreeEntry<T>) {
info!("Root overflowed; growing the tree by one level");
let old_root = std::mem::replace(
&mut self.root,
RStarTreeNode {
entries: Vec::with_capacity(2),
is_leaf: false,
},
);
match common_compute_group_mbr(&old_root.entries) {
Some(mbr) => {
self.root.entries.push(RStarTreeEntry::Node {
mbr,
child: Box::new(old_root),
});
self.root.entries.push(sibling);
}
None => {
self.root = match sibling {
RStarTreeEntry::Node { child, .. } => *child,
leaf => RStarTreeNode {
entries: vec![leaf],
is_leaf: true,
},
};
}
}
}
fn condense_root(&mut self) {
while !self.root.is_leaf && self.root.entries.len() == 1 {
match self.root.entries.pop() {
Some(RStarTreeEntry::Node { child, .. }) => self.root = *child,
Some(other) => {
self.root.entries.push(other);
break;
}
None => break,
}
}
if !self.root.is_leaf && self.root.entries.is_empty() {
self.root.is_leaf = true;
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn clear(&mut self) {
self.root = RStarTreeNode {
entries: Vec::new(),
is_leaf: true,
};
self.len = 0;
}
pub fn range_search_bbox(&self, query: &T::Volume) -> Vec<&T> {
info!("Performing range search with query: {:?}", query);
let mut result = Vec::new();
common_search_node(&self.root, query, &mut result);
result
}
pub fn insert_bulk(&mut self, objects: Vec<T>)
where
T: Clone,
T::Volume: BSPBounds,
{
if objects.is_empty() {
return;
}
if self.root.entries.is_empty() {
info!(
"Bulk loading {} objects into an empty RStarTree",
objects.len()
);
let mut entries: Vec<RStarTreeEntry<T>> = objects
.into_iter()
.map(|object| RStarTreeEntry::Leaf {
mbr: object.mbr(),
object,
})
.collect();
sort_by_center(&mut entries, 0);
self.len += entries.len();
self.root = pack_entries(entries, self.max_entries);
} else {
for object in objects {
self.insert(object);
}
}
}
#[doc(hidden)]
pub fn height(&self) -> usize {
common_node_height(&self.root) + 1
}
}
struct InsertState<T: BoundedObject> {
max_entries: usize,
min_entries: usize,
reinserted_heights: Vec<usize>,
pending: Vec<(RStarTreeEntry<T>, usize)>,
}
fn pack_entries<T: BoundedObject>(
entries: Vec<RStarTreeEntry<T>>,
max_entries: usize,
) -> RStarTreeNode<T> {
let mut level = entries;
let mut level_is_leaf = true;
while level.len() > max_entries {
let mut parents = Vec::with_capacity(level.len().div_ceil(max_entries));
let mut remaining = level;
while !remaining.is_empty() {
let take = remaining.len().min(max_entries);
let child = RStarTreeNode {
entries: remaining.drain(..take).collect(),
is_leaf: level_is_leaf,
};
if let Some(mbr) = common_compute_group_mbr(&child.entries) {
parents.push(RStarTreeEntry::Node {
mbr,
child: Box::new(child),
});
}
}
level = parents;
level_is_leaf = false;
}
RStarTreeNode {
entries: level,
is_leaf: level_is_leaf,
}
}
fn refresh_mbr<T: BoundedObject>(entry: &mut RStarTreeEntry<T>) {
if let RStarTreeEntry::Node { mbr, child } = entry {
if let Some(new_mbr) = common_compute_group_mbr(&child.entries) {
*mbr = new_mbr;
}
}
}
fn sort_by_center<T: BoundedObject>(entries: &mut [RStarTreeEntry<T>], dim: usize)
where
T::Volume: BSPBounds,
{
entries.sort_by(|a, b| {
let ca = a.mbr().center(dim).unwrap_or(0.0);
let cb = b.mbr().center(dim).unwrap_or(0.0);
ca.partial_cmp(&cb).unwrap_or(Ordering::Equal)
});
}
fn choose_subtree<T: BoundedObject>(node: &RStarTreeNode<T>, mbr: &T::Volume) -> usize {
let children_are_leaves = matches!(
node.entries.first(),
Some(RStarTreeEntry::Node { child, .. }) if child.is_leaf
);
let mut best_index = 0;
let mut best_key = (
OrderedFloat(f64::INFINITY),
OrderedFloat(f64::INFINITY),
OrderedFloat(f64::INFINITY),
);
for (i, entry) in node.entries.iter().enumerate() {
let candidate = entry.mbr();
let overlap = if children_are_leaves {
let enlarged = candidate.union(mbr);
node.entries
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, sibling)| enlarged.overlap(sibling.mbr()))
.sum::<f64>()
} else {
0.0
};
let key = (
OrderedFloat(overlap),
OrderedFloat(candidate.enlargement(mbr)),
OrderedFloat(candidate.area()),
);
if key < best_key {
best_key = key;
best_index = i;
}
}
best_index
}
fn insert_recursive<T: BoundedObject + Clone>(
node: &mut RStarTreeNode<T>,
entry: RStarTreeEntry<T>,
target_height: usize,
node_height: usize,
is_root: bool,
state: &mut InsertState<T>,
) -> Option<RStarTreeEntry<T>>
where
T::Volume: BSPBounds,
{
if node_height == target_height {
node.entries.push(entry);
} else {
let best_index = choose_subtree(node, entry.mbr());
let can_descend = matches!(
node.entries.get(best_index),
Some(RStarTreeEntry::Node { .. })
);
let overflow = if can_descend {
let RStarTreeEntry::Node { child, .. } = &mut node.entries[best_index] else {
unreachable!("checked by `can_descend`")
};
insert_recursive(child, entry, target_height, node_height - 1, false, state)
} else {
node.entries.push(entry);
None
};
if can_descend {
refresh_mbr(&mut node.entries[best_index]);
}
if let Some(sibling) = overflow {
node.entries.push(sibling);
}
}
if node.entries.len() <= state.max_entries {
return None;
}
if !is_root && !state.reinserted_heights.contains(&node_height) {
state.reinserted_heights.push(node_height);
for entry in forced_reinsert(node, state.max_entries) {
state.pending.push((entry, node_height));
}
return None;
}
split_node(node, state.min_entries)
}
fn split_node<T: BoundedObject + Clone>(
node: &mut RStarTreeNode<T>,
min_entries: usize,
) -> Option<RStarTreeEntry<T>>
where
T::Volume: BSPBounds,
{
let entries = std::mem::take(&mut node.entries);
let (group1, group2) = split_entries(entries, min_entries);
node.entries = group1;
let sibling = RStarTreeNode {
entries: group2,
is_leaf: node.is_leaf,
};
let mbr = common_compute_group_mbr(&sibling.entries)?;
Some(RStarTreeEntry::Node {
mbr,
child: Box::new(sibling),
})
}
fn forced_reinsert<T: BoundedObject + Clone>(
node: &mut RStarTreeNode<T>,
max_entries: usize,
) -> Vec<RStarTreeEntry<T>>
where
T::Volume: BSPBounds,
{
let Some(node_mbr) = common_compute_group_mbr(&node.entries) else {
return Vec::new();
};
let node_center: Vec<f64> = (0..T::Volume::DIM)
.map(|d| node_mbr.center(d).unwrap_or(0.0))
.collect();
let mut ranked: Vec<(OrderedFloat<f64>, RStarTreeEntry<T>)> = node
.entries
.drain(..)
.map(|entry| {
let distance = (0..T::Volume::DIM)
.map(|d| {
let center = entry.mbr().center(d).unwrap_or(0.0);
(center - node_center[d]).powi(2)
})
.sum::<f64>();
(OrderedFloat(distance), entry)
})
.collect();
ranked.sort_by(|a, b| b.0.cmp(&a.0));
let count = ((max_entries as f64 * 0.3).ceil() as usize)
.clamp(1, ranked.len().saturating_sub(1).max(1));
let mut removed = Vec::with_capacity(count);
for (i, (_, entry)) in ranked.into_iter().enumerate() {
if i < count {
removed.push(entry);
} else {
node.entries.push(entry);
}
}
removed
}
fn split_entries<T: BoundedObject + Clone>(
mut entries: Vec<RStarTreeEntry<T>>,
min_entries: usize,
) -> (Vec<RStarTreeEntry<T>>, Vec<RStarTreeEntry<T>>)
where
T::Volume: BSPBounds,
{
if entries.len() < 2 {
return (entries, Vec::new());
}
let min_entries = min_entries.clamp(1, entries.len() / 2);
let last_split = entries.len() - min_entries;
let mut best_axis = 0;
let mut smallest_margin = f64::INFINITY;
for dim in 0..T::Volume::DIM {
sort_by_center(&mut entries, dim);
let mut margin = 0.0;
for k in min_entries..=last_split {
if let (Some(mbr1), Some(mbr2)) = (
common_compute_group_mbr(&entries[..k]),
common_compute_group_mbr(&entries[k..]),
) {
margin += mbr1.margin() + mbr2.margin();
}
}
if margin < smallest_margin {
smallest_margin = margin;
best_axis = dim;
}
}
sort_by_center(&mut entries, best_axis);
let mut best_split = min_entries;
let mut best_key = (OrderedFloat(f64::INFINITY), OrderedFloat(f64::INFINITY));
for k in min_entries..=last_split {
let (Some(mbr1), Some(mbr2)) = (
common_compute_group_mbr(&entries[..k]),
common_compute_group_mbr(&entries[k..]),
) else {
continue;
};
let key = (
OrderedFloat(mbr1.overlap(&mbr2)),
OrderedFloat(mbr1.area() + mbr2.area()),
);
if key < best_key {
best_key = key;
best_split = k;
}
}
let group2 = entries.split_off(best_split);
(entries, group2)
}
impl<T: BoundedObject> RStarTree<T>
where
T: PartialEq + Clone,
T::Volume: BSPBounds,
{
pub fn delete(&mut self, object: &T) -> bool {
info!("Attempting to delete object: {:?}", object);
let object_mbr = object.mbr();
let mut reinsert_list = Vec::new();
let height = common_node_height(&self.root);
let deleted = common_delete_entry(
&mut self.root,
object,
&object_mbr,
self.min_entries,
height,
&mut reinsert_list,
);
if deleted {
self.len = self.len.saturating_sub(1);
for (entry, entry_height) in reinsert_list {
self.insert_entry_at(entry, entry_height);
}
self.condense_root();
}
deleted
}
}
impl<T: std::fmt::Debug + Clone> RStarTree<Point2D<T>> {
pub fn knn_search<M: DistanceMetric<Point2D<T>>>(
&self,
query: &Point2D<T>,
k: usize,
) -> Vec<&Point2D<T>> {
if k == 0 {
return Vec::new();
}
let mut results = KnnHeap::new(k);
let mut pending: BinaryHeap<KnnCandidate<RStarTreeEntry<Point2D<T>>>> = BinaryHeap::new();
for entry in &self.root.entries {
pending.push(KnnCandidate {
dist: entry.mbr().min_distance_sq(query),
entry,
});
}
while let Some(KnnCandidate { dist, entry }) = pending.pop() {
if dist > results.worst() {
break;
}
match entry {
RStarTreeEntry::Leaf { object, .. } => {
results.offer(M::distance_sq(query, object), object);
}
RStarTreeEntry::Node { child, .. } => {
for child_entry in &child.entries {
let child_dist = child_entry.mbr().min_distance_sq(query);
if child_dist <= results.worst() {
pending.push(KnnCandidate {
dist: child_dist,
entry: child_entry,
});
}
}
}
}
}
results.into_sorted_vec()
}
}
impl<T: std::fmt::Debug + Clone> RStarTree<Point3D<T>> {
pub fn knn_search<M: DistanceMetric<Point3D<T>>>(
&self,
query: &Point3D<T>,
k: usize,
) -> Vec<&Point3D<T>> {
if k == 0 {
return Vec::new();
}
let mut results = KnnHeap::new(k);
let mut pending: BinaryHeap<KnnCandidate<RStarTreeEntry<Point3D<T>>>> = BinaryHeap::new();
for entry in &self.root.entries {
pending.push(KnnCandidate {
dist: entry.mbr().min_distance_sq(query),
entry,
});
}
while let Some(KnnCandidate { dist, entry }) = pending.pop() {
if dist > results.worst() {
break;
}
match entry {
RStarTreeEntry::Leaf { object, .. } => {
results.offer(M::distance_sq(query, object), object);
}
RStarTreeEntry::Node { child, .. } => {
for child_entry in &child.entries {
let child_dist = child_entry.mbr().min_distance_sq(query);
if child_dist <= results.worst() {
pending.push(KnnCandidate {
dist: child_dist,
entry: child_entry,
});
}
}
}
}
}
results.into_sorted_vec()
}
}
impl<T> RStarTree<T>
where
T: BoundedObject + PartialEq + std::fmt::Debug,
T::Volume: BoundingVolumeFromPoint<T> + HasMinDistance<T> + Clone,
{
pub fn range_search<M: DistanceMetric<T>>(&self, query: &T, radius: f64) -> Vec<&T> {
if radius < 0.0 {
return Vec::new();
}
let query_volume = T::Volume::from_point_radius(query, radius);
let candidates = self.range_search_bbox(&query_volume);
candidates
.into_iter()
.filter(|object| M::distance_sq(query, object) <= radius * radius)
.collect()
}
}
crate::rtree_common::impl_rtree_spatial_index!(RStarTree, Point2D, Rectangle);
crate::rtree_common::impl_rtree_spatial_index!(RStarTree, Point3D, Cube);
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::{Cube, EuclideanDistance, Rectangle};
use crate::rtree_common::assert_structure;
const MAX_ENTRIES: usize = 4;
fn spiral(n: u32) -> Vec<Point2D<u32>> {
(0..n)
.map(|i| {
let a = i as f64 * 0.7;
Point2D::new(a.sin() * 100.0, a.cos() * 100.0, Some(i))
})
.collect()
}
fn check(tree: &RStarTree<Point2D<u32>>, context: &str) -> usize {
assert_structure(
&tree.root,
tree.max_entries,
Some(tree.min_entries),
context,
)
}
#[test]
fn test_structure_survives_inserts_and_deletes() {
let mut tree: RStarTree<Point2D<u32>> = RStarTree::new(MAX_ENTRIES).unwrap();
let points = spiral(400);
for (i, point) in points.iter().enumerate() {
tree.insert(point.clone());
let reachable = check(&tree, &format!("after {} inserts", i + 1));
assert_eq!(
reachable,
i + 1,
"objects went missing after {} inserts",
i + 1
);
}
assert!(
tree.height() >= 4,
"400 objects with max_entries={MAX_ENTRIES} cannot fit in {} levels",
tree.height()
);
for (i, point) in points.iter().enumerate() {
assert!(tree.delete(point), "delete of point {i} failed");
let reachable = check(&tree, &format!("after {} deletes", i + 1));
assert_eq!(reachable, points.len() - i - 1);
}
assert!(tree.root.entries.is_empty());
assert!(tree.root.is_leaf, "an emptied tree should be a leaf again");
}
#[test]
fn test_structure_survives_bulk_load() {
let points = spiral(300);
let mut packed: RStarTree<Point2D<u32>> = RStarTree::new(MAX_ENTRIES).unwrap();
packed.insert_bulk(points.clone());
let reachable = assert_structure(&packed.root, MAX_ENTRIES, None, "after bulk load");
assert_eq!(reachable, points.len());
assert!(packed.height() >= 4);
let mut mixed: RStarTree<Point2D<u32>> = RStarTree::new(MAX_ENTRIES).unwrap();
for point in points.iter().take(7) {
mixed.insert(point.clone());
}
mixed.insert_bulk(points[7..40].to_vec());
assert_eq!(
assert_structure(&mixed.root, MAX_ENTRIES, None, "bulk onto populated tree"),
40
);
for point in points.iter().skip(40).take(20) {
mixed.insert(point.clone());
}
assert_eq!(
assert_structure(&mixed.root, MAX_ENTRIES, None, "inserts after bulk"),
60
);
}
#[test]
fn test_range_search_radius_zero_2d() {
let mut tree: RStarTree<Point2D<&str>> = RStarTree::new(4).unwrap();
let target = Point2D::new(5.0, 5.0, Some("T"));
tree.insert(target.clone());
tree.insert(Point2D::new(5.0, 6.0, Some("N")));
let results = tree.range_search::<EuclideanDistance>(&target, 0.0);
assert_eq!(results.len(), 1);
assert_eq!(*results[0], target);
}
#[test]
fn test_range_search_bbox_filters_results_3d() {
let mut tree: RStarTree<Point3D<&str>> = RStarTree::new(4).unwrap();
let inside = Point3D::new(1.0, 1.0, 1.0, Some("I"));
let outside = Point3D::new(20.0, 20.0, 20.0, Some("O"));
tree.insert(inside.clone());
tree.insert(outside);
let query = Cube {
x: 0.0,
y: 0.0,
z: 0.0,
width: 5.0,
height: 5.0,
depth: 5.0,
};
let results = tree.range_search_bbox(&query);
assert_eq!(results.len(), 1);
assert_eq!(*results[0], inside);
}
#[test]
fn test_delete_removes_point_2d() {
let mut tree: RStarTree<Point2D<&str>> = RStarTree::new(4).unwrap();
let a = Point2D::new(1.0, 1.0, Some("A"));
let b = Point2D::new(2.0, 2.0, Some("B"));
tree.insert(a.clone());
tree.insert(b.clone());
assert!(tree.delete(&a));
let removed = tree.range_search::<EuclideanDistance>(&a, 0.0);
let remaining = tree.range_search::<EuclideanDistance>(&b, 0.0);
assert!(removed.is_empty());
assert_eq!(remaining.len(), 1);
assert_eq!(*remaining[0], b);
}
#[test]
fn test_forced_reinsertion_height_and_contents() {
let mut tree: RStarTree<Point2D<i32>> = RStarTree::new(4).unwrap();
let points: Vec<_> = (0..5)
.map(|i| Point2D::new(i as f64, i as f64, Some(i)))
.collect();
for p in &points {
tree.insert(p.clone());
}
assert_eq!(tree.height(), 2);
for i in 5..10 {
tree.insert(Point2D::new(i as f64, i as f64, Some(i)));
}
assert_eq!(tree.height(), 2);
let all_points = tree.range_search_bbox(&Rectangle {
x: -1.0,
y: -1.0,
width: 11.0,
height: 11.0,
});
assert_eq!(all_points.len(), 10);
}
#[test]
fn test_delete_underflow() {
let mut tree: RStarTree<Point2D<i32>> = RStarTree::new(4).unwrap();
let points: Vec<_> = (0..10)
.map(|i| Point2D::new(i as f64, i as f64, Some(i)))
.collect();
for p in &points {
tree.insert(p.clone());
}
assert!(tree.delete(&points[0]));
assert!(tree.delete(&points[1]));
assert!(tree.delete(&points[2]));
let all_points = tree.range_search_bbox(&Rectangle {
x: -1.0,
y: -1.0,
width: 12.0,
height: 12.0,
});
assert_eq!(all_points.len(), 7);
for point in points.iter().take(10).skip(3) {
assert!(tree.delete(point));
}
let all_points_after_all_deleted = tree.range_search_bbox(&Rectangle {
x: -1.0,
y: -1.0,
width: 12.0,
height: 12.0,
});
assert!(all_points_after_all_deleted.is_empty());
}
#[test]
fn test_empty_tree_queries() {
let mut tree: RStarTree<Point2D<&str>> = RStarTree::new(4).unwrap();
let target = Point2D::new(5.0, 5.0, None::<&str>);
let knn_results = tree.knn_search::<EuclideanDistance>(&target, 5);
assert!(knn_results.is_empty());
let range_results = tree.range_search::<EuclideanDistance>(&target, 10.0);
assert!(range_results.is_empty());
assert!(!tree.delete(&target));
}
#[test]
fn test_knn_edge_cases() {
let mut tree: RStarTree<Point2D<&str>> = RStarTree::new(4).unwrap();
let points = vec![
Point2D::new(1.0, 1.0, Some("A")),
Point2D::new(2.0, 2.0, Some("B")),
Point2D::new(3.0, 3.0, Some("C")),
];
let num_points = points.len();
tree.insert_bulk(points.clone());
let target = Point2D::new(1.5, 1.5, None::<&str>);
let knn_results = tree.knn_search::<EuclideanDistance>(&target, 0);
assert!(knn_results.is_empty());
let knn_results = tree.knn_search::<EuclideanDistance>(&target, num_points + 5);
assert_eq!(knn_results.len(), num_points);
}
#[test]
fn test_duplicates_delete_one() {
let mut tree: RStarTree<Point2D<&str>> = RStarTree::new(4).unwrap();
let p1 = Point2D::new(10.0, 10.0, Some("A"));
let p2 = p1.clone();
tree.insert(p1.clone());
tree.insert(p2.clone());
let results = tree.knn_search::<EuclideanDistance>(&p1, 2);
assert_eq!(results.len(), 2);
assert!(tree.delete(&p1));
let results_after_delete = tree.knn_search::<EuclideanDistance>(&p1, 2);
assert_eq!(results_after_delete.len(), 1);
}
#[test]
fn test_range_search_negative_radius_empty() {
let mut tree: RStarTree<Point2D<&str>> = RStarTree::new(4).unwrap();
let target = Point2D::new(5.0, 5.0, Some("T"));
tree.insert(target.clone());
let results = tree.range_search::<EuclideanDistance>(&target, -1.0);
assert!(results.is_empty());
}
}