rosewood/
reader.rs

1// reader.rs
2//
3// Copyright (c) 2021-2025  Douglas P Lau
4//
5use crate::gis::Gis;
6use crate::node::{M_NODE, Node, Root};
7use loam::{Error, Id, Reader, Result};
8use pointy::{BBox, Bounded, Float};
9use serde::de::DeserializeOwned;
10use std::marker::PhantomData;
11use std::path::{Path, PathBuf};
12
13/// RTree reader
14///
15/// Reads a `.loam` file containing [Gis] data.
16///
17/// [Gis]: gis/trait.Gis.html
18pub struct RTree<F, G>
19where
20    F: Float + DeserializeOwned,
21    G: Gis<F> + DeserializeOwned,
22{
23    /// Path for file
24    path: PathBuf,
25
26    _float: PhantomData<F>,
27    _geom: PhantomData<G>,
28}
29
30/// Query iterator for RTree
31struct RTreeQuery<D, F, G>
32where
33    F: Float + DeserializeOwned,
34    G: Gis<F, Data = D> + DeserializeOwned,
35{
36    /// RTree reader
37    reader: Option<Reader>,
38
39    /// Query bounding box
40    bbox: BBox<F>,
41
42    /// Work list of Id / height tuples in bounding box
43    work: Vec<(Id, usize)>,
44
45    /// Error, if any
46    error: Option<Error>,
47
48    _data: PhantomData<D>,
49    _geom: PhantomData<G>,
50}
51
52impl<D, F, G> Iterator for RTreeQuery<D, F, G>
53where
54    F: Float + DeserializeOwned,
55    G: Gis<F, Data = D> + DeserializeOwned,
56{
57    type Item = Result<G>;
58
59    fn next(&mut self) -> Option<Self::Item> {
60        if let Some(err) = self.error.take() {
61            return Some(Err(err));
62        }
63        let reader = self.reader.as_ref()?;
64        while let Some((id, height)) = self.work.pop() {
65            if height > 1 {
66                match reader.lookup::<Node<F>>(id) {
67                    Ok(node) => {
68                        let children = node.into_entries();
69                        for child in children {
70                            log::trace!("{height}: {:?}", child.bbox());
71                            if child.bounded_by(self.bbox) {
72                                self.work.push((child.id(), height - 1));
73                            }
74                        }
75                    }
76                    Err(e) => return Some(Err(e)),
77                }
78            } else {
79                match reader.lookup::<G>(id) {
80                    Ok(geom) => return Some(Ok(geom)),
81                    Err(e) => return Some(Err(e)),
82                }
83            }
84        }
85        None
86    }
87}
88
89impl<D, F, G> RTreeQuery<D, F, G>
90where
91    F: Float + DeserializeOwned,
92    G: Gis<F, Data = D> + DeserializeOwned,
93{
94    /// Create a new RTree query
95    fn new(tree: &RTree<F, G>, bbox: BBox<F>) -> Self {
96        match Self::build(tree.path.as_path(), bbox) {
97            Ok(query) => query,
98            Err(e) => Self {
99                reader: None,
100                bbox,
101                work: Vec::new(),
102                error: Some(e),
103                _data: PhantomData,
104                _geom: PhantomData,
105            },
106        }
107    }
108
109    /// Build query
110    fn build(path: &Path, bbox: BBox<F>) -> Result<Self> {
111        let mut work = Vec::new();
112        let reader = Reader::new(path)?;
113        let id = reader.root()?;
114        let root = reader.lookup::<Root<F>>(id)?;
115        let height = Node::<F>::height(root.n_elem());
116        log::trace!("root: {height}");
117        let node = root.into_node();
118        let children = node.into_entries();
119        work.reserve(height * M_NODE);
120        for child in children {
121            log::trace!("query: {bbox:?}");
122            if child.bounded_by(bbox) {
123                log::trace!("child: {:?}", child.bbox());
124                work.push((child.id(), height));
125            }
126        }
127        Ok(Self {
128            reader: Some(reader),
129            bbox,
130            work,
131            error: None,
132            _data: PhantomData,
133            _geom: PhantomData,
134        })
135    }
136}
137
138impl<D, F, G> RTree<F, G>
139where
140    F: Float + DeserializeOwned,
141    G: Gis<F, Data = D> + DeserializeOwned,
142{
143    /// Open an RTree `.loam` file for reading
144    pub fn new<P>(path: P) -> Self
145    where
146        P: AsRef<Path>,
147    {
148        let mut tmp = PathBuf::new();
149        tmp.push(path);
150        let path = tmp;
151        Self {
152            path,
153            _float: PhantomData,
154            _geom: PhantomData,
155        }
156    }
157
158    /// Query a bounding box
159    ///
160    /// Returns an iterator of all [Gis] items within the bounds.
161    ///
162    /// [Gis]: gis/trait.Gis.html
163    pub fn query<'a>(
164        &'a self,
165        bbox: BBox<F>,
166    ) -> impl Iterator<Item = Result<G>> + 'a
167    where
168        D: 'a,
169    {
170        RTreeQuery::new(self, bbox)
171    }
172}