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