1#[cfg(test)]
21use nodedb_types::BoundingBox;
22use nodedb_types::geometry::Geometry;
23use nodedb_types::geometry_bbox;
24
25use crate::predicates;
26use crate::rtree::RTree;
27
28pub struct SpatialPreFilterResult {
33 pub candidate_ids: Vec<u64>,
35 pub rtree_candidates: usize,
37 pub exact_matches: usize,
39}
40
41pub fn spatial_prefilter(
51 rtree: &RTree,
52 query_geometry: &Geometry,
53 distance_meters: Option<f64>,
54 exact_geometries: &dyn Fn(u64) -> Option<Geometry>,
55) -> SpatialPreFilterResult {
56 let search_bbox = if let Some(dist) = distance_meters {
58 geometry_bbox(query_geometry).expand_meters(dist)
59 } else {
60 geometry_bbox(query_geometry)
61 };
62
63 let rtree_results = rtree.search(&search_bbox);
65 let rtree_candidates = rtree_results.len();
66
67 let mut candidate_ids = Vec::with_capacity(rtree_candidates);
69 for entry in &rtree_results {
70 if let Some(doc_geom) = exact_geometries(entry.id) {
71 let passes = if let Some(dist) = distance_meters {
72 predicates::st_dwithin(&doc_geom, query_geometry, dist)
73 } else {
74 predicates::st_intersects(&doc_geom, query_geometry)
75 };
76 if passes {
77 candidate_ids.push(entry.id);
78 }
79 }
80 }
81
82 let exact_matches = candidate_ids.len();
83 SpatialPreFilterResult {
84 candidate_ids,
85 rtree_candidates,
86 exact_matches,
87 }
88}
89
90pub fn ids_to_bitmap(candidate_ids: &[u64], max_id: u64) -> Vec<u8> {
98 let num_bytes = ((max_id + 8) / 8) as usize;
99 let mut bitmap = vec![0u8; num_bytes];
100 for &id in candidate_ids {
101 let byte_idx = (id / 8) as usize;
102 let bit_idx = (id % 8) as u32;
103 if byte_idx < bitmap.len() {
104 bitmap[byte_idx] |= 1 << bit_idx;
105 }
106 }
107 bitmap
108}
109
110pub fn bitmap_contains(bitmap: &[u8], id: u64) -> bool {
112 let byte_idx = (id / 8) as usize;
113 let bit_idx = (id % 8) as u32;
114 if byte_idx < bitmap.len() {
115 bitmap[byte_idx] & (1 << bit_idx) != 0
116 } else {
117 false
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::rtree::RTreeEntry;
125
126 fn make_tree() -> RTree {
127 let mut tree = RTree::new();
128 for i in 0..10 {
130 tree.insert(RTreeEntry {
131 id: i,
132 bbox: BoundingBox::from_point(i as f64, i as f64),
133 });
134 }
135 tree
136 }
137
138 #[test]
139 fn prefilter_with_distance() {
140 let tree = make_tree();
141 let query = Geometry::point(5.0, 5.0);
142
143 let get_geom = |id: u64| -> Option<Geometry> {
145 if id < 10 {
146 Some(Geometry::point(id as f64, id as f64))
147 } else {
148 None
149 }
150 };
151
152 let result = spatial_prefilter(&tree, &query, Some(200_000.0), &get_geom);
154 assert!(!result.candidate_ids.is_empty());
155 assert!(result.candidate_ids.contains(&5));
157 }
158
159 #[test]
160 fn prefilter_intersects_no_distance() {
161 let tree = make_tree();
162 let query = Geometry::polygon(vec![vec![
164 [3.0, 3.0],
165 [7.0, 3.0],
166 [7.0, 7.0],
167 [3.0, 7.0],
168 [3.0, 3.0],
169 ]]);
170
171 let get_geom = |id: u64| -> Option<Geometry> {
172 if id < 10 {
173 Some(Geometry::point(id as f64, id as f64))
174 } else {
175 None
176 }
177 };
178
179 let result = spatial_prefilter(&tree, &query, None, &get_geom);
180 assert!(result.candidate_ids.contains(&4));
182 assert!(result.candidate_ids.contains(&5));
183 assert!(result.candidate_ids.contains(&6));
184 assert!(!result.candidate_ids.contains(&0));
186 assert!(!result.candidate_ids.contains(&9));
187 }
188
189 #[test]
190 fn bitmap_roundtrip() {
191 let ids = vec![0, 5, 7, 63, 100];
192 let bitmap = ids_to_bitmap(&ids, 128);
193
194 for &id in &ids {
195 assert!(bitmap_contains(&bitmap, id), "id {id} should be set");
196 }
197 assert!(!bitmap_contains(&bitmap, 1));
198 assert!(!bitmap_contains(&bitmap, 64));
199 }
200
201 #[test]
202 fn empty_bitmap() {
203 let bitmap = ids_to_bitmap(&[], 0);
204 assert!(!bitmap_contains(&bitmap, 0));
205 }
206}