mod leaf_store;
mod node;
mod persistence;
mod search;
use crate::types::{BoundingBox3D, Geometry, Point3D};
use crate::{Result, StorageError};
use leaf_store::LeafStore;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub use node::{IndexedPoint3D, Octant};
const DEFAULT_LEAF_CACHE_CAPACITY: usize = 4096;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IOctreeConfig {
pub bucket_size: usize,
pub min_extent: f64,
pub down_size: bool,
pub data_dir: Option<PathBuf>,
pub leaf_cache_capacity: Option<usize>,
}
impl Default for IOctreeConfig {
fn default() -> Self {
Self {
bucket_size: 32,
min_extent: 0.01,
down_size: false,
data_dir: None,
leaf_cache_capacity: None,
}
}
}
impl IOctreeConfig {
pub fn cache_capacity(&self) -> usize {
self.leaf_cache_capacity
.unwrap_or(DEFAULT_LEAF_CACHE_CAPACITY)
}
}
pub struct IOctreeIndex {
root: Octant,
config: IOctreeConfig,
size: usize,
world_bounds: BoundingBox3D,
name: String,
leaf_store: LeafStore,
}
impl IOctreeIndex {
pub fn new(config: IOctreeConfig, name: String) -> Result<Self> {
let world_bounds = BoundingBox3D::new(-500.0, -500.0, -500.0, 500.0, 500.0, 500.0);
let center = {
let c = world_bounds.center();
[c.x, c.y, c.z]
};
let extent = world_bounds.extent() ;
let work_dir = config
.data_dir
.as_ref()
.map(|p| {
if p.extension().map(|e| e == "bin").unwrap_or(false) {
p.parent().unwrap_or(p).to_path_buf()
} else {
p.clone()
}
})
.unwrap_or_else(|| std::env::temp_dir().join(format!("motedb_ioctree_{}", name)));
let leaf_store = LeafStore::open(&work_dir, config.cache_capacity()).map_err(|e| {
crate::StorageError::Index(format!("Failed to create LeafStore: {}", e))
})?;
let root_leaf_id = leaf_store.create_leaf(vec![]).map_err(|e| {
crate::StorageError::Index(format!("Failed to create root leaf: {}", e))
})?;
Ok(Self {
root: Octant::new_leaf(center, extent, root_leaf_id),
config,
size: 0,
world_bounds,
name,
leaf_store,
})
}
pub fn insert(&mut self, row_id: u64, geometry: &Geometry) -> Result<()> {
let owned;
let point = match geometry {
Geometry::Point3D(p) => p,
Geometry::Point(p) => {
owned = Point3D::new(p.x, p.y, 0.0);
&owned
}
_ => {
return Err(StorageError::InvalidData(
"i-Octree only accepts point geometry".into(),
))
}
};
let indexed = IndexedPoint3D::from_point3d(point, row_id);
self.world_bounds.expand(point);
let p = [
point.x,
point.y,
point.z,
];
while !self.root_contains(&p) {
self.expand_root();
}
self.insert_into_tree(indexed)?;
self.size += 1;
Ok(())
}
fn insert_into_tree(&mut self, point: IndexedPoint3D) -> Result<()> {
let bucket_size = self.config.bucket_size;
let min_extent = self.config.min_extent ;
tree_insert(
&self.leaf_store,
&mut self.root,
point,
bucket_size,
min_extent,
)
}
pub fn delete(&mut self, row_id: u64) -> bool {
let removed = tree_delete(&self.leaf_store, &mut self.root, row_id);
if removed {
self.size = self.size.saturating_sub(1);
}
removed
}
pub fn range_query(&self, bbox: &BoundingBox3D) -> Vec<u64> {
let min = [
bbox.min_x,
bbox.min_y,
bbox.min_z,
];
let max = [
bbox.max_x,
bbox.max_y,
bbox.max_z,
];
search::range_search(&self.root, &min, &max, &self.leaf_store)
}
pub fn knn_query(&self, point: &Point3D, k: usize) -> Vec<(u64, f64)> {
let query = [
point.x,
point.y,
point.z,
];
search::knn_search(&self.root, &query, k, &self.leaf_store)
}
pub fn radius_search(&self, center: &Point3D, radius: f64) -> Vec<(u64, f64)> {
let c = [
center.x,
center.y,
center.z,
];
search::radius_search(&self.root, &c, radius , &self.leaf_store)
}
pub fn len(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn save(&self, path: &std::path::Path) -> Result<()> {
persistence::save(self, path)
}
pub fn load_from_path(path: &std::path::Path) -> Result<Self> {
let config = IOctreeConfig::default();
let name = String::new();
persistence::load(path, config, name)
}
pub fn flush(&mut self) -> Result<()> {
self.leaf_store.flush()?;
if let Some(ref path) = self.config.data_dir {
let save_path = if path.extension().map(|e| e == "bin").unwrap_or(false) {
path.clone()
} else {
path.join("ioctree.bin")
};
self.save(&save_path)?;
}
Ok(())
}
fn root_contains(&self, p: &[f64; 3]) -> bool {
let (center, extent) = match &self.root {
Octant::Inner { center, extent, .. } => (center, extent),
Octant::Leaf { center, extent, .. } => (center, extent),
};
let e = *extent;
p[0] >= center[0] - e
&& p[0] <= center[0] + e
&& p[1] >= center[1] - e
&& p[1] <= center[1] + e
&& p[2] >= center[2] - e
&& p[2] <= center[2] + e
}
fn expand_root(&mut self) {
let (center, extent) = match &self.root {
Octant::Inner { center, extent, .. } => (*center, *extent * 2.0),
Octant::Leaf { center, extent, .. } => (*center, *extent * 2.0),
};
let old_root = std::mem::replace(&mut self.root, Octant::new_inner(center, extent));
if let Octant::Inner {
ref mut children, ..
} = self.root
{
let code = node::octant_code(¢er, ¢er);
children[code] = Some(Box::new(old_root));
}
self.root.recount_size();
}
}
fn tree_insert(
store: &LeafStore,
octant: &mut Octant,
point: IndexedPoint3D,
bucket_size: usize,
min_extent: f64,
) -> Result<()> {
match octant {
Octant::Leaf {
center: _,
extent,
leaf_id,
point_count,
} => {
let added = store.add_point(*leaf_id, point)?;
if !added {
if *extent > 2.0 * min_extent {
split_leaf(store, octant)?;
return tree_insert(store, octant, point, bucket_size, min_extent);
}
store.add_point(*leaf_id, point)?;
}
*point_count = store.point_count(*leaf_id)? as u32;
if *point_count as usize > bucket_size && *extent > 2.0 * min_extent {
split_leaf(store, octant)?;
}
}
Octant::Inner {
center,
extent,
children,
size,
} => {
*size += 1;
let code = node::octant_code(center, &point.as_array());
let child_ctr = node::child_center(center, *extent, code);
if children[code].is_none() {
let new_leaf_id = store.create_leaf(vec![])?;
let child_ext = *extent / 2.0;
children[code] = Some(Box::new(Octant::new_leaf(
child_ctr,
child_ext,
new_leaf_id,
)));
}
if let Some(ref mut child) = children[code] {
tree_insert(store, child, point, bucket_size, min_extent)?;
}
}
}
Ok(())
}
fn split_leaf(store: &LeafStore, octant: &mut Octant) -> Result<()> {
let (center, extent, old_leaf_id) = match octant {
Octant::Leaf {
center,
extent,
leaf_id,
..
} => (*center, *extent, *leaf_id),
_ => unreachable!(),
};
let old_points = store.get_points(old_leaf_id)?;
let child_extent = extent / 2.0;
*octant = Octant::new_inner(center, extent);
if let Octant::Inner { children, size, .. } = octant {
for point in old_points {
*size += 1;
let code = node::octant_code(¢er, &point.as_array());
if children[code].is_none() {
let child_ctr = node::child_center(¢er, extent, code);
let new_leaf_id = store.create_leaf(vec![])?;
children[code] = Some(Box::new(Octant::new_leaf(
child_ctr,
child_extent,
new_leaf_id,
)));
}
if let Some(ref mut child) = children[code] {
if let Octant::Leaf {
leaf_id,
point_count,
..
} = child.as_mut()
{
store.add_point(*leaf_id, point)?;
*point_count = store.point_count(*leaf_id)? as u32;
}
}
}
}
store.flush()?;
store.free_leaf(old_leaf_id)?;
Ok(())
}
fn tree_delete(store: &LeafStore, octant: &mut Octant, row_id: u64) -> bool {
match octant {
Octant::Leaf {
leaf_id,
point_count,
..
} => match store.remove_point(*leaf_id, row_id) {
Ok(true) => {
*point_count = store.point_count(*leaf_id).unwrap_or(0) as u32;
true
}
_ => false,
},
Octant::Inner { children, size, .. } => {
for child_slot in children.iter_mut() {
if let Some(ref mut c) = child_slot {
if tree_delete(store, c, row_id) {
*size = size.saturating_sub(1);
if c.size() == 0 {
if let Some(lid) = c.leaf_id() {
let _ = store.free_leaf(lid);
}
*child_slot = None;
}
return true;
}
}
}
false
}
}
}
pub use node::child_center;