use std::marker::PhantomData;
use bytemuck::cast_slice;
use crate::error::{GeoIndexError, Result};
use crate::indices::Indices;
use crate::r#type::IndexableNum;
use crate::rtree::constants::VERSION;
use crate::rtree::util::compute_num_nodes;
#[derive(Debug, Clone, PartialEq)]
pub struct RTreeMetadata<N: IndexableNum> {
node_size: u16,
num_items: u32,
num_nodes: usize,
level_bounds: Vec<usize>,
pub(crate) nodes_byte_length: usize,
pub(crate) indices_byte_length: usize,
phantom: PhantomData<N>,
}
impl<N: IndexableNum> RTreeMetadata<N> {
pub fn new(num_items: u32, node_size: u16) -> Self {
assert!((2..=65535).contains(&node_size));
let (num_nodes, level_bounds) = compute_num_nodes(num_items, node_size);
let indices_bytes_per_element = if num_nodes < 16384 { 2 } else { 4 };
let nodes_byte_length = num_nodes * 4 * N::BYTES_PER_ELEMENT;
let indices_byte_length = num_nodes * indices_bytes_per_element;
Self {
node_size,
num_items,
num_nodes,
level_bounds,
nodes_byte_length,
indices_byte_length,
phantom: PhantomData,
}
}
pub fn from_slice(data: &[u8]) -> Result<Self> {
if data.len() < 8 {
return Err(GeoIndexError::General(format!(
"Expected at least 8 bytes but received {}",
data.len()
)));
}
let magic = data[0];
if magic != 0xfb {
return Err(GeoIndexError::General(
"Data not in Flatbush format.".to_string(),
));
}
let version_and_type = data[1];
let version = version_and_type >> 4;
if version != VERSION {
return Err(GeoIndexError::General(
format!("Got v{} data when expected v{}.", version, VERSION).to_string(),
));
}
let type_ = version_and_type & 0x0f;
if type_ != N::TYPE_INDEX {
return Err(GeoIndexError::General(
format!(
"Got type {} data when expected type {}.",
type_,
N::TYPE_INDEX
)
.to_string(),
));
}
let node_size: u16 = cast_slice(&data[2..4])[0];
let num_items: u32 = cast_slice(&data[4..8])[0];
let slf = Self::new(num_items, node_size);
if slf.data_buffer_length() != data.len() {
return Err(GeoIndexError::General(format!(
"Expected {} bytes but received byte slice with {} bytes",
slf.data_buffer_length(),
data.len()
)));
}
Ok(slf)
}
pub fn node_size(&self) -> u16 {
self.node_size
}
pub fn num_items(&self) -> u32 {
self.num_items
}
pub fn num_nodes(&self) -> usize {
self.num_nodes
}
pub fn level_bounds(&self) -> &[usize] {
&self.level_bounds
}
pub fn data_buffer_length(&self) -> usize {
8 + self.nodes_byte_length + self.indices_byte_length
}
pub fn boxes_slice<'a>(&self, data: &'a [u8]) -> &'a [N] {
cast_slice(&data[8..8 + self.nodes_byte_length])
}
pub fn indices_slice<'a>(&self, data: &'a [u8]) -> Indices<'a> {
let indices_buf = &data
[8 + self.nodes_byte_length..8 + self.nodes_byte_length + self.indices_byte_length];
Indices::new(indices_buf, self.num_nodes)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RTree<N: IndexableNum> {
pub(crate) buffer: Vec<u8>,
pub(crate) metadata: RTreeMetadata<N>,
}
impl<N: IndexableNum> RTree<N> {
pub fn into_inner(self) -> Vec<u8> {
self.buffer
}
}
impl<N: IndexableNum> AsRef<[u8]> for RTree<N> {
fn as_ref(&self) -> &[u8] {
&self.buffer
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RTreeRef<'a, N: IndexableNum> {
pub(crate) boxes: &'a [N],
pub(crate) indices: Indices<'a>,
pub(crate) metadata: RTreeMetadata<N>,
}
impl<'a, N: IndexableNum> RTreeRef<'a, N> {
pub fn try_new<T: AsRef<[u8]>>(data: &'a T) -> Result<Self> {
let data = data.as_ref();
let metadata = RTreeMetadata::from_slice(data)?;
let boxes = metadata.boxes_slice(data);
let indices = metadata.indices_slice(data);
Ok(Self {
boxes,
indices,
metadata,
})
}
pub unsafe fn new_unchecked<T: AsRef<[u8]>>(
data: &'a T,
metadata: RTreeMetadata<N>,
) -> Result<Self> {
let data = data.as_ref();
let boxes = metadata.boxes_slice(data);
let indices = metadata.indices_slice(data);
Ok(Self {
boxes,
indices,
metadata,
})
}
}
#[cfg(test)]
mod tests {
use crate::rtree::sort::{HilbertSort, STRSort, Sort};
use crate::rtree::{RTreeBuilder, RTreeIndex};
use super::*;
#[test]
fn rejects_short_buffers() {
assert!(RTreeMetadata::<f64>::from_slice(&[]).is_err());
assert!(RTreeMetadata::<f64>::from_slice(&[0; 7]).is_err());
}
fn linspace(start: usize, stop: usize, num: usize, endpoint: bool) -> Vec<f64> {
let div = if endpoint { num - 1 } else { num };
let step = (stop - start) as f64 / div as f64;
(0..num).map(|i| start as f64 + step * i as f64).collect()
}
#[test]
fn quicksort_should_work_with_an_inbalanced_dataset() {
_quicksort_should_work_with_an_inbalanced_dataset::<HilbertSort>();
_quicksort_should_work_with_an_inbalanced_dataset::<STRSort>();
}
fn _quicksort_should_work_with_an_inbalanced_dataset<S: Sort<f64>>() {
let n = 15000;
let mut builder = RTreeBuilder::new(2 * n);
let items = linspace(0, 1000, n as usize, true);
let items2 = linspace(0, 1000, n as usize, true);
for item in items {
builder.add(item, 0., item, 0.);
}
for item in items2 {
builder.add(item, 0., item, 0.);
}
let index = builder.finish::<S>();
index.search(-100., -1., 15000., 1.);
}
#[test]
fn quicksort_should_not_stackoverflow_with_large_input() {
_quicksort_should_not_stackoverflow_with_large_input::<HilbertSort>();
_quicksort_should_not_stackoverflow_with_large_input::<STRSort>();
}
fn _quicksort_should_not_stackoverflow_with_large_input<S: Sort<f64> + Send + 'static>() {
let result = std::thread::Builder::new()
.stack_size(512 * 1024) .spawn(|| {
let n: u32 = 20_000;
let mut builder = RTreeBuilder::<f64>::new(n);
for i in 0..n {
let x = if i % 1000 == 0 { i as f64 * 100.0 } else { 0.0 };
builder.add(x, i as f64, x + 1.0, i as f64 + 1.0);
}
let tree = builder.finish::<S>();
let results = tree.search(-1.0, -1.0, 1.0, 20001.0);
assert!(
results.len() > 19_000,
"expected most items to fall within the query window"
);
})
.unwrap()
.join();
assert!(
result.is_ok(),
"R-tree construction overflowed the stack: {:?}",
result.unwrap_err()
);
}
}