h3ron_polars/spatial_index/
rtree.rs1use crate::spatial_index::{finish_mask, negative_mask, RectIndexable, RectSIKind, SpatialIndex};
2use crate::{AsH3IndexChunked, IndexChunked, IndexValue};
3use geo_types::{Coord, Rect};
4use polars::export::arrow::bitmap::MutableBitmap;
5use polars::prelude::UInt64Chunked;
6use polars_core::datatypes::BooleanChunked;
7use rstar::primitives::{GeomWithData, Rectangle};
8use rstar::{RTree, AABB};
9use std::marker::PhantomData;
10
11type RTreeCoord = [f64; 2];
14type RTreeBBox = Rectangle<RTreeCoord>;
15type LocatedArrayPosition = GeomWithData<RTreeBBox, usize>;
16
17pub struct RTreeIndex<IX: IndexValue> {
19 index_phantom: PhantomData<IX>,
20 chunked_array: UInt64Chunked,
21 pub rtree: RTree<LocatedArrayPosition>,
22}
23
24#[inline]
25fn to_coord(coord: Coord) -> RTreeCoord {
26 [coord.x, coord.y]
27}
28
29#[inline]
30fn to_bbox(rect: &Rect) -> RTreeBBox {
31 RTreeBBox::from_corners(to_coord(rect.min()), to_coord(rect.max()))
32}
33
34pub trait BuildRTreeIndex<'a, IX>
35where
36 IX: IndexValue + RectIndexable,
37{
38 fn rtree_index(&self) -> RTreeIndex<IX>;
39}
40
41impl<'a, IX: IndexValue> BuildRTreeIndex<'a, IX> for IndexChunked<'a, IX>
42where
43 IX: RectIndexable,
44{
45 fn rtree_index(&self) -> RTreeIndex<IX> {
75 let entries: Vec<_> = self
76 .iter_indexes_nonvalidated()
77 .enumerate()
78 .filter_map(|(pos, maybe_index)| match maybe_index {
79 Some(index) => index.spatial_index_rect().ok().and_then(|maybe_rect| {
80 maybe_rect.map(|rect| LocatedArrayPosition::new(to_bbox(&rect), pos))
81 }),
82 _ => None,
83 })
84 .collect();
85
86 RTreeIndex {
87 index_phantom: PhantomData::<IX>,
88 chunked_array: self.chunked_array.clone(),
89 rtree: RTree::bulk_load(entries),
90 }
91 }
92}
93
94impl<IX: IndexValue> SpatialIndex<IX, RectSIKind> for RTreeIndex<IX>
95where
96 IX: RectIndexable,
97{
98 fn h3indexchunked(&self) -> IndexChunked<IX> {
99 self.chunked_array.h3indexchunked()
100 }
101
102 fn envelopes_intersect_impl(&self, rect: &Rect) -> MutableBitmap {
103 let mut mask = negative_mask(&self.chunked_array);
104 let envelope = AABB::from_corners(to_coord(rect.min()), to_coord(rect.max()));
105 let locator = self.rtree.locate_in_envelope_intersecting(&envelope);
106 for located_array_position in locator {
107 mask.set(located_array_position.data, true);
108 }
109 mask
110 }
111
112 fn envelopes_within_distance(&self, coord: Coord, distance: f64) -> BooleanChunked {
113 let mut mask = negative_mask(&self.chunked_array);
114
115 let locator = self.rtree.locate_within_distance(to_coord(coord), distance);
116 for located_array_position in locator {
117 mask.set(located_array_position.data, true);
118 }
119
120 finish_mask(mask.into(), &self.h3indexchunked())
121 }
122}
123
124#[cfg(test)]
125mod test {
126 use crate::spatial_index::{BuildRTreeIndex, RTreeIndex};
127 use crate::IndexChunked;
128 use h3ron::H3Cell;
129
130 fn build_index(cc: &IndexChunked<H3Cell>) -> RTreeIndex<H3Cell> {
131 cc.rtree_index()
132 }
133 crate::spatial_index::tests::impl_std_tests!(build_index);
134}