#[cfg(test)]
use nodedb_types::BoundingBox;
use nodedb_types::geometry::Geometry;
use nodedb_types::geometry_bbox;
use crate::predicates;
use crate::rtree::RTree;
pub struct SpatialPreFilterResult {
pub candidate_ids: Vec<u64>,
pub rtree_candidates: usize,
pub exact_matches: usize,
}
pub fn spatial_prefilter(
rtree: &RTree,
query_geometry: &Geometry,
distance_meters: Option<f64>,
exact_geometries: &dyn Fn(u64) -> Option<Geometry>,
) -> SpatialPreFilterResult {
let search_bbox = if let Some(dist) = distance_meters {
geometry_bbox(query_geometry).expand_meters(dist)
} else {
geometry_bbox(query_geometry)
};
let rtree_results = rtree.search(&search_bbox);
let rtree_candidates = rtree_results.len();
let mut candidate_ids = Vec::with_capacity(rtree_candidates);
for entry in &rtree_results {
if let Some(doc_geom) = exact_geometries(entry.id) {
let passes = if let Some(dist) = distance_meters {
predicates::st_dwithin(&doc_geom, query_geometry, dist)
} else {
predicates::st_intersects(&doc_geom, query_geometry)
};
if passes {
candidate_ids.push(entry.id);
}
}
}
let exact_matches = candidate_ids.len();
SpatialPreFilterResult {
candidate_ids,
rtree_candidates,
exact_matches,
}
}
pub fn ids_to_bitmap(candidate_ids: &[u64], max_id: u64) -> Vec<u8> {
let num_bytes = ((max_id + 8) / 8) as usize;
let mut bitmap = vec![0u8; num_bytes];
for &id in candidate_ids {
let byte_idx = (id / 8) as usize;
let bit_idx = (id % 8) as u32;
if byte_idx < bitmap.len() {
bitmap[byte_idx] |= 1 << bit_idx;
}
}
bitmap
}
pub fn bitmap_contains(bitmap: &[u8], id: u64) -> bool {
let byte_idx = (id / 8) as usize;
let bit_idx = (id % 8) as u32;
if byte_idx < bitmap.len() {
bitmap[byte_idx] & (1 << bit_idx) != 0
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rtree::RTreeEntry;
fn make_tree() -> RTree {
let mut tree = RTree::new();
for i in 0..10 {
tree.insert(RTreeEntry {
id: i,
bbox: BoundingBox::from_point(i as f64, i as f64),
});
}
tree
}
#[test]
fn prefilter_with_distance() {
let tree = make_tree();
let query = Geometry::point(5.0, 5.0);
let get_geom = |id: u64| -> Option<Geometry> {
if id < 10 {
Some(Geometry::point(id as f64, id as f64))
} else {
None
}
};
let result = spatial_prefilter(&tree, &query, Some(200_000.0), &get_geom);
assert!(!result.candidate_ids.is_empty());
assert!(result.candidate_ids.contains(&5));
}
#[test]
fn prefilter_intersects_no_distance() {
let tree = make_tree();
let query = Geometry::polygon(vec![vec![
[3.0, 3.0],
[7.0, 3.0],
[7.0, 7.0],
[3.0, 7.0],
[3.0, 3.0],
]]);
let get_geom = |id: u64| -> Option<Geometry> {
if id < 10 {
Some(Geometry::point(id as f64, id as f64))
} else {
None
}
};
let result = spatial_prefilter(&tree, &query, None, &get_geom);
assert!(result.candidate_ids.contains(&4));
assert!(result.candidate_ids.contains(&5));
assert!(result.candidate_ids.contains(&6));
assert!(!result.candidate_ids.contains(&0));
assert!(!result.candidate_ids.contains(&9));
}
#[test]
fn bitmap_roundtrip() {
let ids = vec![0, 5, 7, 63, 100];
let bitmap = ids_to_bitmap(&ids, 128);
for &id in &ids {
assert!(bitmap_contains(&bitmap, id), "id {id} should be set");
}
assert!(!bitmap_contains(&bitmap, 1));
assert!(!bitmap_contains(&bitmap, 64));
}
#[test]
fn empty_bitmap() {
let bitmap = ids_to_bitmap(&[], 0);
assert!(!bitmap_contains(&bitmap, 0));
}
}