Skip to main content

qualia_core_db/domains/geospatial/
spatial.rs

1#[derive(Debug, Clone)]
2pub struct SpatialElement {
3    pub h3_index: u64,
4    pub bounds: (f64, f64, f64, f64), // (min_x, min_y, max_x, max_y)
5    pub t0: u64,
6    pub t1: u64,
7}
8
9#[derive(Debug, Clone)]
10enum QuadTreeNode {
11    Leaf { elements: Vec<SpatialElement> },
12    Internal { children: Box<[QuadTreeNode; 4]> },
13}
14
15#[derive(Debug, Clone)]
16pub struct SpatiotemporalQuadTree {
17    pub root_bounds: (f64, f64, f64, f64),
18    root: QuadTreeNode,
19    max_elements: usize,
20    max_depth: u32,
21}
22
23impl SpatiotemporalQuadTree {
24    pub fn new(bounds: (f64, f64, f64, f64)) -> Self {
25        Self {
26            root_bounds: bounds,
27            root: QuadTreeNode::Leaf {
28                elements: Vec::new(),
29            },
30            max_elements: 16,
31            max_depth: 8,
32        }
33    }
34
35    pub fn insert(&mut self, element: SpatialElement) {
36        Self::insert_into(
37            &mut self.root,
38            self.root_bounds,
39            element,
40            0,
41            self.max_elements,
42            self.max_depth,
43        );
44    }
45
46    fn insert_into(
47        node: &mut QuadTreeNode,
48        bounds: (f64, f64, f64, f64),
49        element: SpatialElement,
50        depth: u32,
51        max_elements: usize,
52        max_depth: u32,
53    ) {
54        match node {
55            QuadTreeNode::Leaf { elements } => {
56                elements.push(element);
57                if elements.len() > max_elements && depth < max_depth {
58                    // Split the node
59                    let (min_x, min_y, max_x, max_y) = bounds;
60                    let mid_x = (min_x + max_x) / 2.0;
61                    let mid_y = (min_y + max_y) / 2.0;
62
63                    let mut top_left = Vec::new();
64                    let mut top_right = Vec::new();
65                    let mut bottom_left = Vec::new();
66                    let mut bottom_right = Vec::new();
67
68                    for el in elements.drain(..) {
69                        let (el_min_x, el_min_y, el_max_x, el_max_y) = el.bounds;
70                        // Determine which quadrants the element belongs to
71                        let left = el_min_x <= mid_x;
72                        let right = el_max_x >= mid_x;
73                        let bottom = el_min_y <= mid_y;
74                        let top = el_max_y >= mid_y;
75
76                        if left && top {
77                            top_left.push(el.clone());
78                        }
79                        if right && top {
80                            top_right.push(el.clone());
81                        }
82                        if left && bottom {
83                            bottom_left.push(el.clone());
84                        }
85                        if right && bottom {
86                            bottom_right.push(el.clone());
87                        }
88                    }
89
90                    *node = QuadTreeNode::Internal {
91                        children: Box::new([
92                            QuadTreeNode::Leaf { elements: top_left },
93                            QuadTreeNode::Leaf {
94                                elements: top_right,
95                            },
96                            QuadTreeNode::Leaf {
97                                elements: bottom_left,
98                            },
99                            QuadTreeNode::Leaf {
100                                elements: bottom_right,
101                            },
102                        ]),
103                    };
104                }
105            }
106            QuadTreeNode::Internal { children } => {
107                let (min_x, min_y, max_x, max_y) = bounds;
108                let mid_x = (min_x + max_x) / 2.0;
109                let mid_y = (min_y + max_y) / 2.0;
110
111                let (el_min_x, el_min_y, el_max_x, el_max_y) = element.bounds;
112                let left = el_min_x <= mid_x;
113                let right = el_max_x >= mid_x;
114                let bottom = el_min_y <= mid_y;
115                let top = el_max_y >= mid_y;
116
117                if left && top {
118                    Self::insert_into(
119                        &mut children[0],
120                        (min_x, mid_y, mid_x, max_y),
121                        element.clone(),
122                        depth + 1,
123                        max_elements,
124                        max_depth,
125                    );
126                }
127                if right && top {
128                    Self::insert_into(
129                        &mut children[1],
130                        (mid_x, mid_y, max_x, max_y),
131                        element.clone(),
132                        depth + 1,
133                        max_elements,
134                        max_depth,
135                    );
136                }
137                if left && bottom {
138                    Self::insert_into(
139                        &mut children[2],
140                        (min_x, min_y, mid_x, mid_y),
141                        element.clone(),
142                        depth + 1,
143                        max_elements,
144                        max_depth,
145                    );
146                }
147                if right && bottom {
148                    Self::insert_into(
149                        &mut children[3],
150                        (mid_x, min_y, max_x, mid_y),
151                        element.clone(),
152                        depth + 1,
153                        max_elements,
154                        max_depth,
155                    );
156                }
157            }
158        }
159    }
160
161    /// Queries the quadtree for elements that intersect the given spatial bounding box
162    /// and are valid within the given temporal range [t0, t1].
163    pub fn query_region(&self, x1: f64, y1: f64, x2: f64, y2: f64, t0: u64, t1: u64) -> Vec<u64> {
164        let mut results = Vec::new();
165        Self::query_node(
166            &self.root,
167            self.root_bounds,
168            (x1, y1, x2, y2),
169            t0,
170            t1,
171            &mut results,
172        );
173
174        // Remove duplicates because elements might span multiple quadrants
175        results.sort_unstable();
176        results.dedup();
177        results
178    }
179
180    fn query_node(
181        node: &QuadTreeNode,
182        bounds: (f64, f64, f64, f64),
183        query_bounds: (f64, f64, f64, f64),
184        t0: u64,
185        t1: u64,
186        results: &mut Vec<u64>,
187    ) {
188        let (qx1, qy1, qx2, qy2) = query_bounds;
189        let (bx1, by1, bx2, by2) = bounds;
190
191        // If the query region doesn't intersect the node's bounds, skip
192        if qx1 > bx2 || qx2 < bx1 || qy1 > by2 || qy2 < by1 {
193            return;
194        }
195
196        match node {
197            QuadTreeNode::Leaf { elements } => {
198                for el in elements {
199                    let overlap_x = el.bounds.0 <= qx2 && el.bounds.2 >= qx1;
200                    let overlap_y = el.bounds.1 <= qy2 && el.bounds.3 >= qy1;
201                    let overlap_t = el.t0 <= t1 && el.t1 >= t0;
202
203                    if overlap_x && overlap_y && overlap_t {
204                        results.push(el.h3_index);
205                    }
206                }
207            }
208            QuadTreeNode::Internal { children } => {
209                let mid_x = (bx1 + bx2) / 2.0;
210                let mid_y = (by1 + by2) / 2.0;
211
212                Self::query_node(
213                    &children[0],
214                    (bx1, mid_y, mid_x, by2),
215                    query_bounds,
216                    t0,
217                    t1,
218                    results,
219                );
220                Self::query_node(
221                    &children[1],
222                    (mid_x, mid_y, bx2, by2),
223                    query_bounds,
224                    t0,
225                    t1,
226                    results,
227                );
228                Self::query_node(
229                    &children[2],
230                    (bx1, by1, mid_x, mid_y),
231                    query_bounds,
232                    t0,
233                    t1,
234                    results,
235                );
236                Self::query_node(
237                    &children[3],
238                    (mid_x, by1, bx2, mid_y),
239                    query_bounds,
240                    t0,
241                    t1,
242                    results,
243                );
244            }
245        }
246    }
247}
248
249/// Embeds an H3 index into a unified 64-bit coordinate space index, commonly used in MORTON codes
250/// or spatial hashing systems where bit packing includes resolution or other contextual data.
251pub fn embed_h3_context(index: u64, resolution: u8, base_cell: u8) -> u64 {
252    // 64-bit layout: [1 bit reserved][4 bits resolution][7 bits base_cell][52 bits H3 internal]
253    let res_bits = ((resolution & 0x0F) as u64) << 59;
254    let base_bits = ((base_cell & 0x7F) as u64) << 52;
255    let index_bits = index & 0x000F_FFFF_FFFF_FFFF;
256    res_bits | base_bits | index_bits
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_query_region() {
265        let mut qt = SpatiotemporalQuadTree::new((-180.0, -90.0, 180.0, 90.0));
266
267        qt.insert(SpatialElement {
268            h3_index: 101,
269            bounds: (10.0, 10.0, 20.0, 20.0),
270            t0: 1000,
271            t1: 2000,
272        });
273
274        qt.insert(SpatialElement {
275            h3_index: 202,
276            bounds: (50.0, 50.0, 60.0, 60.0),
277            t0: 1500,
278            t1: 2500,
279        });
280
281        // Intersects first element spatially and temporally
282        let res1 = qt.query_region(15.0, 15.0, 25.0, 25.0, 1200, 1800);
283        assert_eq!(res1, vec![101]);
284
285        // Spatially intersects but temporally disjoint
286        let res2 = qt.query_region(15.0, 15.0, 25.0, 25.0, 3000, 4000);
287        assert!(res2.is_empty());
288
289        // Intersects both temporally, only second spatially
290        let res3 = qt.query_region(40.0, 40.0, 55.0, 55.0, 1500, 2000);
291        assert_eq!(res3, vec![202]);
292    }
293
294    #[test]
295    fn test_quadtree_split() {
296        let mut qt = SpatiotemporalQuadTree::new((0.0, 0.0, 100.0, 100.0));
297        qt.max_elements = 2; // Force early split
298
299        for i in 0..5 {
300            qt.insert(SpatialElement {
301                h3_index: i,
302                bounds: (
303                    10.0 + (i as f64),
304                    10.0 + (i as f64),
305                    15.0 + (i as f64),
306                    15.0 + (i as f64),
307                ),
308                t0: 100,
309                t1: 200,
310            });
311        }
312
313        let res = qt.query_region(0.0, 0.0, 50.0, 50.0, 50, 250);
314        assert_eq!(res.len(), 5);
315    }
316}