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}