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
17pub 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 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 #[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 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 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 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 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 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 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 pub fn path(&self) -> &PathBuf {
204 &self.path
205 }
206}