Skip to main content

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}