osm_io/osm/pbf/
writer.rs

1use std::fs::File;
2use std::io::Write;
3use std::path::PathBuf;
4
5use anyhow::{anyhow, Context};
6
7use crate::osm::model::bounding_box::BoundingBox;
8use crate::osm::model::element::Element;
9use crate::osm::pbf::compression_type::CompressionType;
10use crate::osm::pbf::element_accumulator::ElementAccumulator;
11use crate::osm::pbf::file_block::FileBlock;
12use crate::osm::pbf::file_block_metadata::FileBlockMetadata;
13use crate::osm::pbf::file_info::FileInfo;
14use crate::osm::pbf::osm_data::OsmData;
15use crate::osm::pbf::osm_header::OsmHeader;
16
17/// *.osm.pbf file reader
18///
19/// Write an ordered *.osm.pbf file split into blocks of 8000 or less elements of the same variant -
20/// Nodes, Ways, Relations.
21/// Example:
22/// ```
23///
24/// use std::path::PathBuf;
25/// use osm_io::osm::model::element::Element;
26/// use osm_io::osm::pbf;
27/// use osm_io::osm::pbf::compression_type::CompressionType;
28/// use osm_io::osm::pbf::file_info::FileInfo;
29/// fn example() -> Result<(), anyhow::Error> {
30///     let input_path = PathBuf::from("./tests/fixtures/malta-230109.osm.pbf");
31///     let output_path = PathBuf::from("./target/results/malta-230109.osm.pbf");
32///     let reader = pbf::reader::Reader::new(&input_path)?;
33///     let mut file_info = FileInfo::default();
34///     file_info.with_writingprogram_str("pbf-io-example");
35///     let mut writer = pbf::writer::Writer::from_file_info(
36///             output_path,
37///             file_info,
38///             CompressionType::Zlib,
39///     )?;
40///
41///     writer.write_header()?;
42///     for element in reader.elements()? {
43///         let mut filtered_out = false;
44///         match &element {
45///             Element::Node { node: _ } => {}
46///             Element::Way { way: _ } => {}
47///             Element::Relation { relation: _ } => {}
48///             Element::Sentinel => {
49///                 filtered_out = true;
50///             }
51///         }
52///         if !filtered_out {
53///             writer.write_element(element)?;
54///         }
55///     }
56///     writer.close()?;
57///     Ok(())
58/// }
59/// ```
60pub struct Writer {
61    path: PathBuf,
62    file_info: FileInfo,
63    compression_type: CompressionType,
64    file: File,
65    element_accumulator: ElementAccumulator,
66}
67
68impl Writer {
69    /// Create a new [Writer] from [FileInfo]
70    pub fn from_file_info(
71        path: PathBuf,
72        file_info: FileInfo,
73        compression_type: CompressionType,
74    ) -> Result<Writer, anyhow::Error> {
75        let file = File::create(path.clone())
76            .with_context(|| anyhow!("path: {}", path.display()))?;
77        Ok(
78            Writer {
79                path: path.clone(),
80                file_info,
81                compression_type,
82                file,
83                element_accumulator: ElementAccumulator::new(),
84            }
85        )
86    }
87
88    /// Create a new [Writer]
89    #[allow(clippy::too_many_arguments)]
90    pub fn new(
91        path: PathBuf,
92        program_name: &str,
93        data_source: &str,
94        osmosis_replication_timestamp: Option<i64>,
95        osmosis_replication_sequence_number: Option<i64>,
96        osmosis_replication_base_url: Option<String>,
97        compression_type: CompressionType,
98        precomputed_bounding_box: Option<BoundingBox>,
99        contains_history: bool,
100    ) -> Result<Writer, anyhow::Error> {
101        let mut required_features = vec![
102            "OsmSchema-V0.6".to_string(),
103            "DenseNodes".to_string(),
104        ];
105
106        if contains_history {
107            required_features.push("HistoricalInformation".to_string());
108        }
109
110        let optional_features = vec![
111            "Sort.Type_then_ID".to_string(),
112        ];
113
114        let writingprogram = Some(program_name.to_string());
115        let source = Some(data_source.to_string());
116
117        let file_info = FileInfo::new(
118            precomputed_bounding_box,
119            required_features,
120            optional_features,
121            writingprogram,
122            source,
123            osmosis_replication_timestamp,
124            osmosis_replication_sequence_number,
125            osmosis_replication_base_url,
126        );
127
128        Self::from_file_info(path, file_info, compression_type)
129    }
130
131    /// Write the *.osm.pbf file header.
132    ///
133    /// Must be called before writing elements. That means that all header values, specifically the
134    /// bounding box must be calculated before writing the file. I some cases that can incur a
135    /// costly additional iteration.
136    pub fn write_header(&mut self) -> Result<(), anyhow::Error> {
137        let file_block = FileBlock::from_header(
138            OsmHeader::from_file_info(self.file_info.clone())
139        );
140
141        self.write_file_block(file_block)
142    }
143
144    /// Low level API to write a [FileBlock]
145    pub fn write_file_block(&mut self, file_block: FileBlock) -> Result<(), anyhow::Error> {
146        let (blob_header, blob_body) = FileBlock::serialize(&file_block, self.compression_type.clone())?;
147        self.write_blob(blob_header, blob_body)
148    }
149
150    /// Low level API to write a bytes of a blob
151    pub fn write_blob(&mut self, blob_header: Vec<u8>, blob_body: Vec<u8>) -> Result<(), anyhow::Error> {
152        let blob_header_len: i32 = blob_header.len() as i32;
153        self.file.write_all(&blob_header_len.to_be_bytes())?;
154        self.file.write_all(&blob_header)?;
155        self.file.write_all(&blob_body)?;
156        self.file.flush()?;
157        Ok(())
158    }
159
160    /// Write element
161    ///
162    /// Elements must be ordered, that is each element must be less then or equal to the following
163    /// element
164    pub fn write_element(&mut self, element: Element) -> Result<(), anyhow::Error> {
165        let elements = self.element_accumulator.add(element);
166        match elements {
167            None => {}
168            Some(elements) => {
169                self.write_elements(elements)?;
170            }
171        }
172        Ok(())
173    }
174    /// Write elements
175    ///
176    /// Elements must be ordered, that is each element must be less then or equal to the following
177    /// element
178    pub fn write_elements(&mut self, elements: Vec<Element>) -> Result<(), anyhow::Error> {
179        let index = self.element_accumulator.index();
180        let data = FileBlock::Data {
181            metadata: FileBlockMetadata::new(
182                "OSMData".to_string(),
183                index,
184            ),
185            data: OsmData::from_elements(elements, None),
186        };
187        self.write_file_block(data)?;
188        Ok(())
189    }
190
191    /// Flush the internal buffers.
192    ///
193    /// Must be called in the end to write any elements accumulated in internal buffers
194    pub fn close(&mut self) -> Result<(), anyhow::Error> {
195        let elements = self.element_accumulator.elements();
196        if !elements.is_empty() {
197            self.write_elements(elements)?;
198        }
199        Ok(())
200    }
201
202    /// Output path
203    pub fn path(&self) -> &PathBuf {
204        &self.path
205    }
206}