use std::marker::PhantomData;
use bytemuck::cast_slice;
use crate::error::{GeoIndexError, Result};
use crate::indices::Indices;
use crate::kdtree::constants::{KDBUSH_HEADER_SIZE, KDBUSH_MAGIC, KDBUSH_VERSION};
use crate::r#type::IndexableNum;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct KDTreeMetadata<N: IndexableNum> {
node_size: u16,
num_items: u32,
phantom: PhantomData<N>,
pub(crate) indices_byte_size: usize,
pub(crate) pad_coords_byte_size: usize,
pub(crate) coords_byte_size: usize,
}
impl<N: IndexableNum> KDTreeMetadata<N> {
pub fn new(num_items: u32, node_size: u16) -> Self {
assert!((2..=65535).contains(&node_size));
let coords_byte_size = (num_items as usize) * 2 * N::BYTES_PER_ELEMENT;
let indices_bytes_per_element = if num_items < 65536 { 2 } else { 4 };
let indices_byte_size = (num_items as usize) * indices_bytes_per_element;
let pad_coords_byte_size = (8 - (indices_byte_size % 8)) % 8;
Self {
node_size,
num_items,
phantom: PhantomData,
indices_byte_size,
pad_coords_byte_size,
coords_byte_size,
}
}
pub fn from_slice(data: &[u8]) -> Result<Self> {
if data.len() < KDBUSH_HEADER_SIZE {
return Err(GeoIndexError::General(format!(
"Expected at least {} bytes but received {}",
KDBUSH_HEADER_SIZE,
data.len()
)));
}
if data[0] != KDBUSH_MAGIC {
return Err(GeoIndexError::General(
"Data not in Kdbush format.".to_string(),
));
}
let version_and_type = data[1];
let version = version_and_type >> 4;
if version != KDBUSH_VERSION {
return Err(GeoIndexError::General(
format!("Got v{} data when expected v{}.", version, KDBUSH_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 data_buffer_length(&self) -> usize {
KDBUSH_HEADER_SIZE
+ self.coords_byte_size
+ self.indices_byte_size
+ self.pad_coords_byte_size
}
pub fn coords_slice<'a>(&self, data: &'a [u8]) -> &'a [N] {
let coords_byte_start =
KDBUSH_HEADER_SIZE + self.indices_byte_size + self.pad_coords_byte_size;
let coords_byte_end = KDBUSH_HEADER_SIZE
+ self.indices_byte_size
+ self.pad_coords_byte_size
+ self.coords_byte_size;
cast_slice(&data[coords_byte_start..coords_byte_end])
}
pub fn indices_slice<'a>(&self, data: &'a [u8]) -> Indices<'a> {
let indices_buf = &data[KDBUSH_HEADER_SIZE..KDBUSH_HEADER_SIZE + self.indices_byte_size];
if self.num_items < 65536 {
Indices::U16(cast_slice(indices_buf))
} else {
Indices::U32(cast_slice(indices_buf))
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct KDTree<N: IndexableNum> {
pub(crate) buffer: Vec<u8>,
pub(crate) metadata: KDTreeMetadata<N>,
}
impl<N: IndexableNum> KDTree<N> {
pub fn into_inner(self) -> Vec<u8> {
self.buffer
}
}
impl<N: IndexableNum> AsRef<[u8]> for KDTree<N> {
fn as_ref(&self) -> &[u8] {
&self.buffer
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct KDTreeRef<'a, N: IndexableNum> {
pub(crate) coords: &'a [N],
pub(crate) indices: Indices<'a>,
pub(crate) metadata: KDTreeMetadata<N>,
}
impl<'a, N: IndexableNum> KDTreeRef<'a, N> {
pub fn try_new<T: AsRef<[u8]>>(data: &'a T) -> Result<Self> {
let data = data.as_ref();
let metadata = KDTreeMetadata::from_slice(data)?;
let coords = metadata.coords_slice(data);
let indices = metadata.indices_slice(data);
Ok(Self {
coords,
indices,
metadata,
})
}
pub unsafe fn new_unchecked<T: AsRef<[u8]>>(
data: &'a T,
metadata: KDTreeMetadata<N>,
) -> Result<Self> {
let data = data.as_ref();
let coords = metadata.coords_slice(data);
let indices = metadata.indices_slice(data);
Ok(Self {
coords,
indices,
metadata,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_short_buffers() {
assert!(KDTreeMetadata::<f64>::from_slice(&[]).is_err());
assert!(KDTreeMetadata::<f64>::from_slice(&[0; 7]).is_err());
}
}