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}