pbf_craft/readers/raw_reader.rs
1use rayon::prelude::*;
2
3use std::fs::File;
4use std::io::{BufReader, Read};
5use std::path::Path;
6use std::rc::Rc;
7
8use super::traits::{BlobData, PbfRandomRead};
9use crate::codecs::blob::{BlobReader, DecodedBlob};
10use crate::codecs::block_decorators::{HeaderReader, PrimitiveReader};
11use crate::models::{Element, ElementType};
12
13/// A snapshot of a reader's consumption progress, obtained via [`PbfReader::progress`]
14/// (or `IterableReader::progress` / any `Deref`-inheriting reader with sequential
15/// semantics). Callers can poll it at their own rate (e.g. every 250 ms) to render a
16/// progress bar.
17///
18/// Progress is byte-based: `bytes_read` is the stream position and is only meaningful for
19/// sequential reads — random-access readers (`CachedReader` after a `seek`) report the
20/// position of the current seek, not a global progress.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct ReaderProgress {
23 /// Bytes consumed from the stream so far (equals the total length at EOF).
24 pub bytes_read: u64,
25 /// Total stream length; `None` when unknown (e.g. non-file streams).
26 pub total_bytes: Option<u64>,
27}
28
29impl ReaderProgress {
30 /// Fraction of the stream consumed, `None` when the total length is unknown.
31 pub fn fraction(&self) -> Option<f64> {
32 self.total_bytes
33 .map(|total| self.bytes_read as f64 / total as f64)
34 }
35
36 /// Percentage of the stream consumed, `None` when the total length is unknown.
37 pub fn percent(&self) -> Option<f64> {
38 self.fraction().map(|f| f * 100.0)
39 }
40}
41
42/// A fundamental reader for PBF data.
43///
44/// The `PbfReader` struct provides functionality to read and process PBF files,
45/// which are commonly used for storing OpenStreetMap (OSM) data. It wraps around
46/// a `BlobReader` to handle the low-level reading of blobs from the input source.
47///
48/// # Type Parameters
49///
50/// * `R` - A type that implements the `Read` and `Send` traits. This is typically
51/// a file or a network stream from which the PBF data is read.
52///
53/// # Example
54///
55/// ```rust
56/// use pbf_craft::readers::PbfReader;
57///
58/// let mut reader = PbfReader::from_path("resources/andorra-latest.osm.pbf").unwrap();
59/// reader.read(|header, element| {
60/// if let Some(header_reader) = header {
61/// // Process header
62/// }
63/// if let Some(element) = element {
64/// // Process element
65/// }
66/// }).unwrap();
67/// ```
68pub struct PbfReader<R: Read + Send> {
69 blob_reader: BlobReader<R>,
70 total_bytes: Option<u64>,
71}
72
73impl<R: Read + Send> PbfReader<R> {
74 /// Creates a new `PbfReader` instance with the specified reader which implements `Read` and `Send` traits.
75 ///
76 /// The total stream length is unknown for an arbitrary `R`; use `from_path` to enable
77 /// percentage reporting.
78 pub fn new(reader: R) -> PbfReader<R> {
79 Self {
80 blob_reader: BlobReader::new(reader),
81 total_bytes: None,
82 }
83 }
84
85 /// Reports the reader's consumption progress (bytes consumed vs total length, if known).
86 pub fn progress(&self) -> ReaderProgress {
87 ReaderProgress {
88 bytes_read: self.blob_reader.offset,
89 total_bytes: self.total_bytes,
90 }
91 }
92
93 /// Reads and decodes the next blob, returning its elements.
94 ///
95 /// Returns `Ok(None)` at a clean end of stream and `Err` on any malformed or truncated
96 /// input (headers are validated for supported required features, blocks for structural
97 /// consistency).
98 pub fn read_next_blob(&mut self) -> anyhow::Result<Option<BlobData>> {
99 if self.blob_reader.eof {
100 return Ok(None);
101 }
102 let offset = self.blob_reader.offset;
103 match self.blob_reader.next_blob()? {
104 Some(blob) => {
105 let data = match blob.decode()? {
106 Some(DecodedBlob::OsmHeader(header)) => {
107 HeaderReader::new(header).validate_features()?;
108 BlobData {
109 nodes: Vec::with_capacity(0),
110 ways: Vec::with_capacity(0),
111 relations: Vec::with_capacity(0),
112 offset,
113 }
114 }
115 Some(DecodedBlob::OsmData(data)) => {
116 let decorator = PrimitiveReader::new(data)?;
117 let (nodes, ways, relations) = decorator.get_all_elements()?;
118 BlobData {
119 nodes,
120 ways,
121 relations,
122 offset,
123 }
124 }
125 None => BlobData {
126 nodes: Vec::with_capacity(0),
127 ways: Vec::with_capacity(0),
128 relations: Vec::with_capacity(0),
129 offset,
130 },
131 };
132 Ok(Some(data))
133 }
134 None => Ok(None),
135 }
136 }
137
138 /// Reads and processes header and elements using the provided callback function.
139 ///
140 /// This is a single-threaded method where all elements are iterated over one by one
141 /// in the order recorded in the PBF file.
142 ///
143 /// # Arguments
144 ///
145 /// * `callback` - A mutable closure that takes two optional arguments:
146 /// - `Option<HeaderReader>`: Some if a header is decoded, None otherwise.
147 /// - `Option<Element>`: Some if an element is decoded, None otherwise.
148 ///
149 /// # Returns
150 ///
151 /// * `anyhow::Result<()>` - Returns an Ok result if all blobs are processed successfully,
152 /// or an error if any blob decoding fails (including unsupported required features).
153 ///
154 /// # Errors
155 ///
156 /// This function will return an error if any PBF decoding fails.
157 ///
158 /// # Example
159 ///
160 /// ```rust
161 /// use pbf_craft::readers::PbfReader;
162 ///
163 /// let mut reader = PbfReader::from_path("resources/andorra-latest.osm.pbf").unwrap();
164 /// reader.read(|header, element| {
165 /// if let Some(header_reader) = header {
166 /// // Process header
167 /// }
168 /// if let Some(element) = element {
169 /// // Process element
170 /// }
171 /// }).unwrap();
172 /// ```
173 pub fn read<F>(&mut self, mut callback: F) -> anyhow::Result<()>
174 where
175 F: FnMut(Option<HeaderReader>, Option<Element>),
176 {
177 while let Some(blob) = self.blob_reader.next_blob()? {
178 match blob.decode()? {
179 Some(DecodedBlob::OsmHeader(b)) => {
180 let header_reader = HeaderReader::new(b);
181 header_reader.validate_features()?;
182 callback(Some(header_reader), None);
183 }
184 Some(DecodedBlob::OsmData(data)) => {
185 let decorator = PrimitiveReader::new(data)?;
186 decorator.for_each_element(|el| callback(None, Some(el)))?;
187 }
188 None => {}
189 }
190 }
191 Ok(())
192 }
193
194 /// Finds elements in parallel.
195 ///
196 /// # Arguments
197 ///
198 /// * `inclination` - An optional reference to an `ElementType` that specifies the type of elements to find.
199 /// If `None`, all element types are considered.
200 /// * `callback` - A closure that takes a reference to an `Element` and returns a boolean indicating
201 /// whether the element should be included in the result. The closure must be `Send` and `Sync`.
202 ///
203 /// # Returns
204 ///
205 /// * `anyhow::Result<Vec<Element>>` - Returns a vector of elements that match the criteria specified
206 /// by the callback function. If an error occurs during PBF decoding, an error is returned.
207 ///
208 /// # Errors
209 ///
210 /// This function will return an error if any PBF decoding fails.
211 ///
212 /// # Example
213 ///
214 /// ```rust
215 /// use pbf_craft::models::ElementType;
216 /// use pbf_craft::readers::PbfReader;
217 ///
218 /// let mut reader = PbfReader::from_path("resources/andorra-latest.osm.pbf").unwrap();
219 /// let elements = reader.par_find(Some(&ElementType::Node), |element| {
220 /// // Filter logic for nodes
221 /// true
222 /// }).unwrap();
223 /// ```
224 pub fn par_find<F>(
225 self,
226 inclination: Option<&ElementType>,
227 callback: F,
228 ) -> anyhow::Result<Vec<Element>>
229 where
230 F: Fn(&Element) -> bool + Send + Sync,
231 {
232 // Single streaming pipeline: `par_bridge` pulls one blob per worker at a time, each
233 // blob is decoded, filtered and merged incrementally, so memory stays bounded by the
234 // worker count plus the final result — collecting all decoded blocks up front would
235 // hold the whole (planet-sized) file's uncompressed data in memory. Decode errors
236 // flow through the Result items and the reduce instead of panicking.
237 let result = self
238 .blob_reader
239 .par_bridge()
240 .map(|blob| -> anyhow::Result<Vec<Element>> {
241 let decoded = match blob?.decode()? {
242 Some(DecodedBlob::OsmData(b)) => Some(PrimitiveReader::new(b)?),
243 _ => None,
244 };
245 let Some(p) = decoded else {
246 return Ok(Vec::new());
247 };
248 if let Some(element_type) = inclination {
249 let result = match element_type {
250 ElementType::Node => p
251 .get_nodes()?
252 .into_iter()
253 .map(Element::Node)
254 .filter(&callback)
255 .collect::<Vec<Element>>(),
256 ElementType::Way => p
257 .get_ways()?
258 .into_iter()
259 .map(Element::Way)
260 .filter(&callback)
261 .collect::<Vec<Element>>(),
262 ElementType::Relation => p
263 .get_relations()?
264 .into_iter()
265 .map(Element::Relation)
266 .filter(&callback)
267 .collect::<Vec<Element>>(),
268 };
269 Ok(result)
270 } else {
271 let (nodes, ways, relations) = p.get_all_elements()?;
272 let mut result: Vec<Element> = nodes
273 .into_iter()
274 .map(Element::Node)
275 .filter(&callback)
276 .collect();
277 result.extend(ways.into_iter().map(Element::Way).filter(&callback));
278 result.extend(
279 relations
280 .into_iter()
281 .map(Element::Relation)
282 .filter(&callback),
283 );
284 Ok(result)
285 }
286 })
287 .reduce(
288 || Ok(Vec::new()),
289 |acc: anyhow::Result<Vec<Element>>, item: anyhow::Result<Vec<Element>>| match (
290 acc, item,
291 ) {
292 (Ok(mut a), Ok(b)) => {
293 a.extend(b);
294 Ok(a)
295 }
296 (Err(e), _) | (_, Err(e)) => Err(e),
297 },
298 )?;
299 Ok(result)
300 }
301}
302
303impl PbfReader<BufReader<File>> {
304 /// Creates a new `PbfReader` instance with the specified file path. The file length is
305 /// recorded so [`ReaderProgress::percent`] can be reported.
306 pub fn from_path<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
307 let total_bytes = std::fs::metadata(path.as_ref())?.len();
308 let f = File::open(path)?;
309 let reader = BufReader::new(f);
310 Ok(Self {
311 blob_reader: BlobReader::new(reader),
312 total_bytes: Some(total_bytes),
313 })
314 }
315
316 /// Rewinds the reader to the beginning of the file.
317 pub fn rewind(&mut self) -> anyhow::Result<()> {
318 self.blob_reader.rewind()
319 }
320}
321
322impl PbfRandomRead for PbfReader<BufReader<File>> {
323 fn read_blob_by_offset(&mut self, offset: u64) -> anyhow::Result<Rc<BlobData>> {
324 self.blob_reader.seek(offset)?;
325 let data = self
326 .read_next_blob()?
327 .ok_or(anyhow!("no blob data found."))?;
328 Ok(Rc::new(data))
329 }
330}