Skip to main content

pbf_craft/writers/
raw_writer.rs

1use std::fs::File;
2use std::io::{BufWriter, Write};
3use std::mem;
4use std::path::Path;
5
6use anyhow;
7use byteorder::{self, WriteBytesExt};
8use flate2::write::ZlibEncoder;
9use flate2::Compression;
10use protobuf::Message;
11
12use crate::codecs::block_builder::PrimitiveBuilder;
13use crate::models::{Bound, Element};
14use crate::proto::{fileformat, osmformat};
15
16const MAX_BLOCK_ITEM_LENGTH: usize = 8000;
17
18/// A writer for creating PBF files.
19///
20/// The `PbfWriter` struct provides functionality to write PBF data to an underlying writer.
21/// It supports writing elements in either dense or non-dense format and can include optional
22/// bounding box information.
23///
24/// Elements are buffered and flushed as blocks of 8000; the output blobs are compressed with
25/// zlib. Elements marked `visible = false` that are seen before the header is emitted (the
26/// first automatic flush or `finish()`) cause the header to declare the required
27/// `HistoricalInformation` feature; callers streaming historical data whose invisible
28/// elements may arrive after the first block must declare it up front with
29/// [`PbfWriter::set_historical_data`]. `finish()` must be called to flush the last partial
30/// block; dropping the writer flushes it best-effort (errors are only surfaced by
31/// `finish()`).
32///
33/// Please note: the PBF format does not require sorted elements, but the conventional layout
34/// (all nodes by id, then all ways by id, then all relations by id) is assumed by
35/// `IndexedReader` and most other tools. `PbfWriter` stores elements in the order in which
36/// `write` is called, so it is up to the caller to provide them in the desired order.
37///
38/// # Type Parameters
39///
40/// * `W` - A type that implements the `Write` trait, which is used to write the PBF data.
41///
42/// # Example
43///
44/// ```rust
45/// use pbf_craft::models::{Element, Node};
46/// use pbf_craft::writers::PbfWriter;
47///
48/// let mut writer = PbfWriter::from_path(std::env::temp_dir().join("output.pbf"), true).unwrap();
49/// writer.write(Element::Node(Node::default())).unwrap();
50/// writer.finish().unwrap();
51/// ```
52pub struct PbfWriter<W: Write> {
53    writer: W,
54    use_dense: bool,
55    bbox: Option<Bound>,
56    cache: Vec<Element>,
57    has_written_header: bool,
58    has_invisible_elements: bool,
59    declared_historical_data: bool,
60}
61
62impl PbfWriter<BufWriter<File>> {
63    /// Creates a new `PbfWriter` from a file path.
64    ///
65    /// # Parameters
66    ///
67    /// * `path` - The path to the file to write the PBF data to.
68    /// * `use_dense` - A boolean value indicating whether to use dense format for writing nodes.
69    ///
70    pub fn from_path<P: AsRef<Path>>(path: P, use_dense: bool) -> anyhow::Result<Self> {
71        let f = File::create(path)?;
72        let writer = BufWriter::new(f);
73        Ok(Self::new(writer, use_dense))
74    }
75}
76
77impl<W: Write> PbfWriter<W> {
78    /// Creates a new `PbfWriter` from an existing writer.
79    ///
80    /// # Parameters
81    ///
82    /// * `writer` - The writer to use for writing the PBF data. It should implement the `Write`
83    ///   trait, which is used to write the PBF data.
84    /// * `use_dense` - A boolean value indicating whether to use dense format for writing nodes.
85    ///
86    pub fn new(writer: W, use_dense: bool) -> PbfWriter<W> {
87        Self {
88            writer,
89            use_dense,
90            bbox: None,
91            cache: Vec::new(),
92            has_written_header: false,
93            has_invisible_elements: false,
94            declared_historical_data: false,
95        }
96    }
97
98    fn build_raw_blob(&mut self, raw: Vec<u8>) -> anyhow::Result<fileformat::Blob> {
99        let raw_size = raw.len();
100        let mut zlib_encoder = ZlibEncoder::new(Vec::new(), Compression::default());
101        zlib_encoder.write_all(raw.as_slice())?;
102        let compressed = zlib_encoder.finish()?;
103
104        let mut blob = fileformat::Blob::new();
105        blob.set_zlib_data(compressed);
106        blob.set_raw_size(raw_size as i32);
107        Ok(blob)
108    }
109
110    /// Sets the bounding box for the PBF file.
111    ///
112    /// If you want to include a bounding box in the PBF file, you set it before writing any elements.
113    ///
114    pub fn set_bbox(&mut self, bbox: Bound) {
115        self.bbox = Some(bbox);
116    }
117
118    /// Declares that the data being written is historical, i.e. contains elements with
119    /// `visible = false`, so the header block declares the required `HistoricalInformation`
120    /// feature.
121    ///
122    /// The writer also auto-detects `visible = false` elements, but only those seen before
123    /// the header is emitted — the header is written together with the first flushed block
124    /// (8000 elements) or at `finish()` and can never be amended afterwards. Callers
125    /// streaming historical data that cannot guarantee an invisible element inside the first
126    /// block must call this before writing, like [`PbfWriter::set_bbox`].
127    ///
128    /// Returns an error if the header has already been written (i.e. the first block was
129    /// already flushed) and the declaration can no longer take effect.
130    pub fn set_historical_data(&mut self, historical: bool) -> anyhow::Result<()> {
131        if self.has_written_header {
132            bail!(
133                "cannot declare historical data after the header has already been written \
134                 (the first block was flushed)"
135            );
136        }
137        self.declared_historical_data = historical;
138        Ok(())
139    }
140
141    fn write_header(&mut self) -> anyhow::Result<()> {
142        let mut header_block = osmformat::HeaderBlock::new();
143        header_block
144            .required_features
145            .push("OsmSchema-V0.6".to_string());
146        if self.use_dense {
147            header_block
148                .required_features
149                .push("DenseNodes".to_string());
150        }
151        // Per the PBF spec, a writer that emits `visible = false` (historical data) MUST
152        // declare the HistoricalInformation feature. The flag combines every element seen so
153        // far (auto-detected) with any explicit up-front declaration made via
154        // `set_historical_data`; once the header is flushed it can never be amended, so
155        // callers whose invisible elements may arrive after the first block must declare the
156        // data historical before writing.
157        if self.has_invisible_elements || self.declared_historical_data {
158            header_block
159                .required_features
160                .push("HistoricalInformation".to_string());
161        }
162
163        if let Some(bbox) = &self.bbox {
164            let mut header_bbox = osmformat::HeaderBBox::new();
165            header_bbox.set_left(bbox.left);
166            header_bbox.set_right(bbox.right);
167            header_bbox.set_top(bbox.top);
168            header_bbox.set_bottom(bbox.bottom);
169            header_block.set_bbox(header_bbox);
170            header_block.set_source(bbox.origin.clone());
171        }
172
173        let blob = self.build_raw_blob(header_block.write_to_bytes()?)?;
174        self.write_blob(blob, "OSMHeader")?;
175        self.has_written_header = true;
176        Ok(())
177    }
178
179    /// Writes an element.
180    ///
181    /// Please note: the PBF format does not require sorted elements, but `IndexedReader` and
182    /// most other tools assume the conventional ordering (all nodes by id, then all ways by
183    /// id, then all relations by id). The writer stores elements in the order they are
184    /// written — the caller is responsible for providing them in the desired order.
185    ///
186    pub fn write(&mut self, element: Element) -> anyhow::Result<()> {
187        // Track whether any element is marked invisible so the header can declare the
188        // required HistoricalInformation feature (see `write_header`).
189        match &element {
190            Element::Node(node) => self.has_invisible_elements |= !node.visible,
191            Element::Way(way) => self.has_invisible_elements |= !way.visible,
192            Element::Relation(relation) => self.has_invisible_elements |= !relation.visible,
193        }
194        self.cache.push(element);
195        if self.cache.len() >= MAX_BLOCK_ITEM_LENGTH {
196            self.write_to_block()?;
197        }
198        Ok(())
199    }
200
201    fn write_to_block(&mut self) -> anyhow::Result<()> {
202        if !self.has_written_header {
203            self.write_header()?;
204        }
205        if self.cache.is_empty() {
206            // Nothing buffered: emit no empty data block (the header alone already forms a
207            // valid file for a writer with no elements).
208            return Ok(());
209        }
210        let block_builder = PrimitiveBuilder::new();
211        let cache = mem::take(&mut self.cache);
212        let block = block_builder.build(cache, self.use_dense);
213
214        let blob = self.build_raw_blob(block.write_to_bytes()?)?;
215        self.write_blob(blob, "OSMData")?;
216        Ok(())
217    }
218
219    fn write_blob(&mut self, blob: fileformat::Blob, blob_type: &str) -> anyhow::Result<()> {
220        let blob_bytes = blob.write_to_bytes()?;
221
222        let mut header = fileformat::BlobHeader::new();
223        header.set_datasize(blob_bytes.len() as i32);
224        header.set_field_type(blob_type.to_owned());
225        let header_bytes = header.write_to_bytes()?;
226
227        self.writer
228            .write_u32::<byteorder::BigEndian>(header_bytes.len() as u32)?;
229        self.writer.write_all(header_bytes.as_slice())?;
230        self.writer.write_all(blob_bytes.as_slice())?;
231
232        Ok(())
233    }
234
235    /// Finishes writing the PBF file.
236    ///
237    /// This method should be called after writing all elements to the PBF file. It writes the
238    /// header (even for an empty file) and flushes any buffered elements.
239    ///
240    pub fn finish(&mut self) -> anyhow::Result<()> {
241        self.write_to_block()?;
242        self.writer.flush()?;
243        Ok(())
244    }
245}
246
247impl<W: Write> Drop for PbfWriter<W> {
248    fn drop(&mut self) {
249        // Best-effort flush of buffered elements so a forgotten `finish()` does not silently
250        // produce an empty file. Errors cannot be returned from `drop`; call `finish()`
251        // explicitly to surface them.
252        if !self.cache.is_empty() {
253            let _ = self.write_to_block();
254        }
255        let _ = self.writer.flush();
256    }
257}