shapefile/writer.rs
1//! Module with the definition of the [Writer] that allows writing shapefile
2//!
3//! # Writer
4//!
5//! [Writer] is the struct that writes a complete shapefile (_.shp_, _.shx_, _.dbf_).
6//!
7//! # ShapeWriter
8//!
9//! The [ShapeWriter] can be used if you only want to write the .shp
10//! and .shx files, however since it does not write the .dbf file, it is not recommended.
11use std::io::{BufWriter, Seek, SeekFrom, Write};
12
13use super::{header, ShapeType};
14use super::{Error, PointZ};
15use crate::record::{BBoxZ, EsriShape, RecordHeader};
16use std::fs::File;
17use std::path::Path;
18
19use crate::reader::ShapeIndex;
20use dbase::TableWriterBuilder;
21
22pub(crate) fn f64_min(a: f64, b: f64) -> f64 {
23 if a < b {
24 a
25 } else {
26 b
27 }
28}
29
30pub(crate) fn f64_max(a: f64, b: f64) -> f64 {
31 if a > b {
32 a
33 } else {
34 b
35 }
36}
37
38/// struct that handles the writing of the .shp
39/// and (optionally) the .idx
40///
41/// The recommended way to create a ShapeWriter by using [ShapeWriter::from_path]
42///
43/// # Important
44///
45/// As this writer does not write the _.dbf_, it does not write what is considered
46/// a complete (thus valid) shapefile.
47pub struct ShapeWriter<T: Write + Seek> {
48 shp_dest: T,
49 shx_dest: Option<T>,
50 header: header::Header,
51 rec_num: u32,
52 dirty: bool,
53}
54
55impl<T: Write + Seek> ShapeWriter<T> {
56 /// Creates a writer that can be used to write a new shapefile.
57 ///
58 /// The `dest` argument is only for the .shp
59 pub fn new(shp_dest: T) -> Self {
60 Self {
61 shp_dest,
62 shx_dest: None,
63 header: header::Header::default(),
64 rec_num: 1,
65 dirty: true,
66 }
67 }
68
69 pub fn with_shx(shp_dest: T, shx_dest: T) -> Self {
70 Self {
71 shp_dest,
72 shx_dest: Some(shx_dest),
73 header: Default::default(),
74 rec_num: 1,
75 dirty: true,
76 }
77 }
78
79 /// Write the shape to the file
80 ///
81 /// # Examples
82 ///
83 /// ```
84 /// # fn main() -> Result<(), shapefile::Error> {
85 /// use shapefile::Point;
86 /// let mut writer = shapefile::ShapeWriter::from_path("points.shp")?;
87 ///
88 /// writer.write_shape(&Point::new(0.0, 0.0))?;
89 /// writer.write_shape(&Point::new(1.0, 0.0))?;
90 /// writer.write_shape(&Point::new(2.0, 0.0))?;
91 ///
92 /// # std::fs::remove_file("points.shp")?;
93 /// # std::fs::remove_file("points.shx")?;
94 /// # Ok(())
95 /// # }
96 /// ```
97 pub fn write_shape<S: EsriShape>(&mut self, shape: &S) -> Result<(), Error> {
98 match (self.header.shape_type, S::shapetype()) {
99 // This is the first call to write shape, we shall write the header
100 // to reserve it space in the file.
101 (ShapeType::NullShape, t) => {
102 self.header.shape_type = t;
103 self.header.bbox = BBoxZ {
104 max: PointZ::new(f64::MIN, f64::MIN, f64::MIN, f64::MIN),
105 min: PointZ::new(f64::MAX, f64::MAX, f64::MAX, f64::MAX),
106 };
107 self.header.write_to(&mut self.shp_dest)?;
108 if let Some(shx_dest) = &mut self.shx_dest {
109 self.header.write_to(shx_dest)?;
110 }
111 }
112 (t1, t2) if t1 != t2 => {
113 return Err(Error::MismatchShapeType {
114 requested: t1,
115 actual: t2,
116 });
117 }
118 _ => {}
119 }
120
121 let record_size = (shape.size_in_bytes() + std::mem::size_of::<i32>()) / 2;
122
123 RecordHeader {
124 record_number: self.rec_num as i32,
125 record_size: record_size as i32,
126 }
127 .write_to(&mut self.shp_dest)?;
128 self.header.shape_type.write_to(&mut self.shp_dest)?;
129 shape.write_to(&mut self.shp_dest)?;
130
131 if let Some(shx_dest) = &mut self.shx_dest {
132 ShapeIndex {
133 offset: self.header.file_length,
134 record_size: record_size as i32,
135 }
136 .write_to(shx_dest)?;
137 }
138
139 self.header.file_length += record_size as i32 + RecordHeader::SIZE as i32 / 2;
140 self.header.bbox.grow_from_shape(shape);
141 self.rec_num += 1;
142 self.dirty = true;
143
144 Ok(())
145 }
146
147 /// Writes a collection of shapes to the file
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// # fn main() -> Result<(), shapefile::Error> {
153 /// use shapefile::Point;
154 /// let mut writer = shapefile::ShapeWriter::from_path("points.shp")?;
155 /// let points = vec![Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(2.0, 0.0)];
156 ///
157 /// writer.write_shapes(&points)?;
158 /// # std::fs::remove_file("points.shp")?;
159 /// # std::fs::remove_file("points.shx")?;
160 /// # Ok(())
161 /// # }
162 /// ```
163 ///
164 /// ```
165 /// # fn main() -> Result<(), shapefile::Error> {
166 /// use shapefile::{Point, Polyline};
167 /// let mut writer = shapefile::ShapeWriter::from_path("polylines.shp")?;
168 /// let points = vec![Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(2.0, 0.0)];
169 /// let polyline = Polyline::new(points);
170 ///
171 /// writer.write_shapes(&vec![polyline])?;
172 /// # std::fs::remove_file("polylines.shp")?;
173 /// # std::fs::remove_file("polylines.shx")?;
174 /// # Ok(())
175 /// # }
176 /// ```
177 pub fn write_shapes<'a, S: EsriShape + 'a, C: IntoIterator<Item = &'a S>>(
178 mut self,
179 container: C,
180 ) -> Result<(), Error> {
181 for shape in container {
182 self.write_shape(shape)?;
183 }
184 Ok(())
185 }
186
187 /// Finalizes the file by updating the header
188 ///
189 /// * Also flushes the destinations
190 pub fn finalize(&mut self) -> Result<(), Error> {
191 if !self.dirty {
192 return Ok(());
193 }
194
195 if self.header.bbox.max.m == f64::MIN && self.header.bbox.min.m == f64::MAX {
196 self.header.bbox.max.m = 0.0;
197 self.header.bbox.min.m = 0.0;
198 }
199
200 if self.header.bbox.max.z == f64::MIN && self.header.bbox.min.z == f64::MAX {
201 self.header.bbox.max.z = 0.0;
202 self.header.bbox.min.z = 0.0;
203 }
204
205 self.shp_dest.seek(SeekFrom::Start(0))?;
206 self.header.write_to(&mut self.shp_dest)?;
207 self.shp_dest.seek(SeekFrom::End(0))?;
208 self.shp_dest.flush()?;
209
210 if let Some(shx_dest) = &mut self.shx_dest {
211 let mut shx_header = self.header;
212 shx_header.file_length = header::HEADER_SIZE / 2
213 + ((self.rec_num - 1) as i32 * 2 * size_of::<i32>() as i32 / 2);
214 shx_dest.seek(SeekFrom::Start(0))?;
215 shx_header.write_to(shx_dest)?;
216 shx_dest.seek(SeekFrom::End(0))?;
217 shx_dest.flush()?;
218 }
219 self.dirty = false;
220 Ok(())
221 }
222}
223
224impl<T: Write + Seek> Drop for ShapeWriter<T> {
225 fn drop(&mut self) {
226 let _ = self.finalize();
227 }
228}
229
230impl ShapeWriter<BufWriter<File>> {
231 /// Creates a new writer from a path.
232 /// Creates both a .shp and .shx files
233 ///
234 ///
235 /// # Examples
236 ///
237 /// ```no_run
238 /// let writer = shapefile::ShapeWriter::from_path("new_file.shp");
239 /// ```
240 pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
241 let shp_path = path.as_ref().to_path_buf();
242 let shx_path = shp_path.with_extension("shx");
243
244 let shp_file = BufWriter::new(File::create(shp_path)?);
245 let shx_file = BufWriter::new(File::create(shx_path)?);
246
247 Ok(Self::with_shx(shp_file, shx_file))
248 }
249}
250
251/// The Writer writes a complete shapefile that is, it
252/// writes the 3 mandatory files (.shp, .shx, .dbf)
253///
254/// The recommended way to create a new shapefile is via the
255/// [Writer::from_path] or [Writer::from_path_with_info] associated functions.
256///
257/// # Examples
258///
259/// To create a Writer that writes a .dbf file that has the same
260/// structure as .dbf read earlier you will have to do:
261///
262/// ```
263/// # fn main() -> Result<(), shapefile::Error> {
264/// let mut reader = shapefile::Reader::from_path("tests/data/multipatch.shp")?;
265/// let shape_records = reader.read()?;
266/// let table_info = reader.into_table_info();
267///
268/// let writer = shapefile::Writer::from_path_with_info("new_multipatch.shp", table_info);
269///
270/// # std::fs::remove_file("new_multipatch.shp")?;
271/// # std::fs::remove_file("new_multipatch.shx")?;
272/// # std::fs::remove_file("new_multipatch.dbf")?;
273/// # Ok(())
274/// # }
275/// ```
276pub struct Writer<T: Write + Seek> {
277 shape_writer: ShapeWriter<T>,
278 dbase_writer: dbase::TableWriter<T>,
279}
280
281impl<T: Write + Seek> Writer<T> {
282 /// Creates a new writer using the provided ShapeWriter and TableWriter
283 ///
284 /// # Example
285 ///
286 /// Creating a Writer that writes to in memory buffers.
287 ///
288 /// ```
289 /// # fn main() -> Result<(), shapefile::Error> {
290 /// use std::convert::TryInto;
291 /// let mut shp_dest = std::io::Cursor::new(Vec::<u8>::new());
292 /// let mut shx_dest = std::io::Cursor::new(Vec::<u8>::new());
293 /// let mut dbf_dest = std::io::Cursor::new(Vec::<u8>::new());
294 ///
295 /// let shape_writer = shapefile::ShapeWriter::with_shx(&mut shp_dest, &mut shx_dest);
296 /// let dbase_writer = dbase::TableWriterBuilder::new()
297 /// .add_character_field("Name".try_into().unwrap(), 50)
298 /// .build_with_dest(&mut dbf_dest);
299 ///
300 /// let shape_writer = shapefile::Writer::new(shape_writer, dbase_writer);
301 /// # Ok(())
302 /// # }
303 /// ```
304 pub fn new(shape_writer: ShapeWriter<T>, dbase_writer: dbase::TableWriter<T>) -> Self {
305 Self {
306 shape_writer,
307 dbase_writer,
308 }
309 }
310
311 pub fn write_shape_and_record<S: EsriShape, R: dbase::WritableRecord>(
312 &mut self,
313 shape: &S,
314 record: &R,
315 ) -> Result<(), Error> {
316 self.shape_writer.write_shape(shape)?;
317 self.dbase_writer.write_record(record)?;
318 Ok(())
319 }
320
321 pub fn write_shapes_and_records<
322 'a,
323 S: EsriShape + 'a,
324 R: dbase::WritableRecord + 'a,
325 C: IntoIterator<Item = (&'a S, &'a R)>,
326 >(
327 mut self,
328 container: C,
329 ) -> Result<(), Error> {
330 for (shape, record) in container.into_iter() {
331 self.write_shape_and_record(shape, record)?;
332 }
333 Ok(())
334 }
335}
336
337impl Writer<BufWriter<File>> {
338 /// Creates all the files needed for the shapefile to be complete (.shp, .shx, .dbf)
339 ///
340 /// ```
341 /// # fn main() -> Result<(), shapefile::Error> {
342 /// use std::convert::TryInto;
343 /// let table_builder = dbase::TableWriterBuilder::new()
344 /// .add_character_field("name".try_into().unwrap(), 50);
345 /// let writer = shapefile::Writer::from_path("new_cities.shp", table_builder)?;
346 /// # std::fs::remove_file("new_cities.shp")?;
347 /// # std::fs::remove_file("new_cities.shx")?;
348 /// # std::fs::remove_file("new_cities.dbf")?;
349 /// # Ok(())
350 /// # }
351 /// ```
352 pub fn from_path<P: AsRef<Path>>(
353 path: P,
354 table_builder: TableWriterBuilder,
355 ) -> Result<Self, Error> {
356 Ok(Self {
357 shape_writer: ShapeWriter::from_path(path.as_ref())?,
358 dbase_writer: table_builder
359 .build_with_file_dest(path.as_ref().with_extension("dbf"))?,
360 })
361 }
362
363 pub fn from_path_with_info<P: AsRef<Path>>(
364 path: P,
365 table_info: dbase::TableInfo,
366 ) -> Result<Self, Error> {
367 Ok(Self {
368 shape_writer: ShapeWriter::from_path(path.as_ref())?,
369 dbase_writer: dbase::TableWriterBuilder::from_table_info(table_info)
370 .build_with_file_dest(path.as_ref().with_extension("dbf"))?,
371 })
372 }
373}