shapefile/lib.rs
1//! Read & Write [Shapefile](http://downloads.esri.com/support/whitepapers/mo_/shapefile.pdf) in Rust
2//!
3//! A _shapefile_ is in reality a collection of 3 mandatory files:
4//! - .shp (feature geometry aka shapes)
5//! - .shx (index of feature geometry)
6//! - .dbf (attribute information, aka records)
7//!
8//! As different shapefiles can store different type of shapes
9//! (but one shapefile can only store the same type of shapes)
10//! This library provide two ways of reading the shapes:
11//!
12//! 1) Reading as [Shape](record/enum.Shape.html) and then do a `match` to handle the different shapes
13//! 2) Reading directly as concrete shapes (ie Polyline, PolylineZ, Point, etc) this of course only
14//! works if the file actually contains shapes that matches the requested type
15//!
16//! # dBase
17//!
18//! The attributes (stored in the .dbg) files are read and written using the dbase crate
19//! which is re-exported so you can use `use shapefile::dbase`.
20//! dBase files may have different encoding which may only be supported if either one of the
21//! following features is enabled:
22//! - `encoding_rs` (notably supports GBK encoding)
23//! - `yore`
24//!
25//! # Shapefiles shapes
26//!
27//! The [`Point`], [`PointM`] and [`PointZ`] are the base data types of shapefiles,
28//! the other shapes (`Polyline, Multipoint`, ...) are collections of these type of points
29//! with different semantics (multiple parts or no, closed parts or no, ...)
30//!
31//! With the exception of the [`Multipatch`] shape, each shape as a variant for each type
32//! of point. ([`Multipatch`] always uses [`PointZ`])
33//! Eg: For the polyline, there is [`Polyline`], [`PolylineM`], [`PolylineZ`]
34//!
35//! # Reading
36//!
37//! For more details see the [reader](reader/index.html) module
38//!
39//! # Writing
40//!
41//! To write a file see the [writer](writer/index.html) module
42//!
43//! # Features
44//!
45//! The `geo-types` feature can be enabled to have access to `From` and `TryFrom`
46//! implementations allowing to convert (or try to) back and forth between shapefile's type and
47//! the one in `geo_types`
48//!
49//! The `yore` or `encoding_rs` feature can be activated to allows the dbase crate
50//! to handle files with special encodings.
51//!
52//! [`Point`]: record/point/struct.Point.html
53//! [`PointM`]: record/point/struct.PointM.html
54//! [`PointZ`]: record/point/struct.PointZ.html
55//! [`Polyline`]: record/polyline/type.Polyline.html
56//! [`PolylineM`]: record/polyline/type.PolylineM.html
57//! [`PolylineZ`]: record/polyline/type.PolylineZ.html
58//! [`Multipatch`]: record/multipatch/struct.Multipatch.html
59extern crate byteorder;
60pub extern crate dbase;
61
62pub mod header;
63pub mod reader;
64pub mod record;
65pub mod writer;
66
67#[cfg(feature = "geo-traits")]
68mod geo_traits_impl;
69
70use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
71use std::fmt;
72use std::io::{Read, Write};
73
74pub use reader::{read, read_as, read_shapes, read_shapes_as, Reader, ShapeReader};
75pub use record::Multipatch;
76pub use record::{convert_shapes_to_vec_of, HasShapeType, ReadableShape};
77pub use record::{Multipoint, MultipointM, MultipointZ};
78pub use record::{Patch, Shape, NO_DATA};
79pub use record::{Point, PointM, PointZ};
80pub use record::{Polygon, PolygonM, PolygonRing, PolygonZ};
81pub use record::{Polyline, PolylineM, PolylineZ};
82pub use writer::{ShapeWriter, Writer};
83
84extern crate core;
85#[cfg(feature = "geo-types")]
86extern crate geo_types;
87
88/// All Errors that can happen when using this library
89#[derive(Debug)]
90pub enum Error {
91 /// Wrapper around standard io::Error that might occur when reading/writing
92 IoError(std::io::Error),
93 /// The file read had an invalid File code (meaning it's not a Shapefile)
94 InvalidFileCode(i32),
95 /// The file read had an invalid [ShapeType](enum.ShapeType.html) code
96 /// (either in the file header or any record type)
97 InvalidShapeType(i32),
98 /// The Multipatch shape read from the file had an invalid [PatchType](enum.PatchType.html) code
99 InvalidPatchType(i32),
100 /// Error returned when trying to read the shape records as a certain shape type
101 /// but the actual shape type does not correspond to the one asked
102 MismatchShapeType {
103 /// The requested ShapeType
104 requested: ShapeType,
105 /// The actual type of the shape
106 actual: ShapeType,
107 },
108 InvalidShapeRecordSize,
109 DbaseError(dbase::Error),
110 MissingDbf,
111 MissingIndexFile,
112}
113
114impl From<std::io::Error> for Error {
115 fn from(error: std::io::Error) -> Error {
116 Error::IoError(error)
117 }
118}
119
120impl From<dbase::Error> for Error {
121 fn from(e: dbase::Error) -> Error {
122 Error::DbaseError(e)
123 }
124}
125
126impl fmt::Display for Error {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128 match self {
129 Error::IoError(e) => write!(f, "{}", e),
130 Error::InvalidFileCode(code) => write!(
131 f,
132 "The file code ' {} ' is invalid, is this a Shapefile ?",
133 code
134 ),
135 Error::InvalidShapeType(code) => write!(
136 f,
137 "The code ' {} ' does not correspond to any of the ShapeType code defined by ESRI",
138 code
139 ),
140 Error::MismatchShapeType { requested, actual } => write!(
141 f,
142 "The requested type: '{}' does not correspond to the actual shape type: '{}'",
143 requested, actual
144 ),
145 e => write!(f, "{:?}", e),
146 }
147 }
148}
149
150impl std::error::Error for Error {}
151
152/// The enum for the ShapeType as defined in the
153/// specification
154#[derive(Debug, PartialEq, Copy, Clone)]
155pub enum ShapeType {
156 NullShape = 0,
157 Point = 1,
158 Polyline = 3,
159 Polygon = 5,
160 Multipoint = 8,
161
162 PointZ = 11,
163 PolylineZ = 13,
164 PolygonZ = 15,
165 MultipointZ = 18,
166
167 PointM = 21,
168 PolylineM = 23,
169 PolygonM = 25,
170 MultipointM = 28,
171
172 Multipatch = 31,
173}
174
175impl ShapeType {
176 pub(crate) fn read_from<T: Read>(source: &mut T) -> Result<ShapeType, Error> {
177 let code = source.read_i32::<LittleEndian>()?;
178 Self::from(code).ok_or(Error::InvalidShapeType(code))
179 }
180
181 pub(crate) fn write_to<T: Write>(self, dest: &mut T) -> Result<(), std::io::Error> {
182 dest.write_i32::<LittleEndian>(self as i32)?;
183 Ok(())
184 }
185
186 /// Returns the ShapeType corresponding to the input code
187 /// if the code is valid
188 /// ```
189 /// use shapefile::ShapeType;
190 ///
191 /// assert_eq!(ShapeType::from(25), Some(ShapeType::PolygonM));
192 /// assert_eq!(ShapeType::from(60), None);
193 /// ```
194 pub fn from(code: i32) -> Option<ShapeType> {
195 match code {
196 0 => Some(ShapeType::NullShape),
197 1 => Some(ShapeType::Point),
198 3 => Some(ShapeType::Polyline),
199 5 => Some(ShapeType::Polygon),
200 8 => Some(ShapeType::Multipoint),
201 11 => Some(ShapeType::PointZ),
202 13 => Some(ShapeType::PolylineZ),
203 15 => Some(ShapeType::PolygonZ),
204 18 => Some(ShapeType::MultipointZ),
205 21 => Some(ShapeType::PointM),
206 23 => Some(ShapeType::PolylineM),
207 25 => Some(ShapeType::PolygonM),
208 28 => Some(ShapeType::MultipointM),
209 31 => Some(ShapeType::Multipatch),
210 _ => None,
211 }
212 }
213
214 /// Returns whether the ShapeType has the third dimension Z
215 pub fn has_z(self) -> bool {
216 matches!(
217 self,
218 ShapeType::PointZ
219 | ShapeType::PolylineZ
220 | ShapeType::PolygonZ
221 | ShapeType::MultipointZ
222 | ShapeType::Multipatch
223 )
224 }
225
226 /// Returns whether the ShapeType has the optional measure dimension
227 pub fn has_m(self) -> bool {
228 matches!(
229 self,
230 ShapeType::PointZ
231 | ShapeType::PolylineZ
232 | ShapeType::PolygonZ
233 | ShapeType::MultipointZ
234 | ShapeType::PointM
235 | ShapeType::PolylineM
236 | ShapeType::PolygonM
237 | ShapeType::MultipointM
238 )
239 }
240
241 /// Returns true if the shape may have multiple parts
242 pub fn is_multipart(self) -> bool {
243 !matches!(
244 self,
245 ShapeType::Point
246 | ShapeType::PointM
247 | ShapeType::PointZ
248 | ShapeType::Multipoint
249 | ShapeType::MultipointM
250 | ShapeType::MultipointZ
251 )
252 }
253}
254
255impl fmt::Display for ShapeType {
256 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257 match self {
258 ShapeType::NullShape => write!(f, "NullShape"),
259 ShapeType::Point => write!(f, "Point"),
260 ShapeType::Polyline => write!(f, "Polyline"),
261 ShapeType::Polygon => write!(f, "Polygon"),
262 ShapeType::Multipoint => write!(f, "Multipoint"),
263 ShapeType::PointZ => write!(f, "PointZ"),
264 ShapeType::PolylineZ => write!(f, "PolylineZ"),
265 ShapeType::PolygonZ => write!(f, "PolygonZ"),
266 ShapeType::MultipointZ => write!(f, "MultipointZ"),
267 ShapeType::PointM => write!(f, "PointM"),
268 ShapeType::PolylineM => write!(f, "PolylineM"),
269 ShapeType::PolygonM => write!(f, "PolygonM"),
270 ShapeType::MultipointM => write!(f, "MultipointM"),
271 ShapeType::Multipatch => write!(f, "Multipatch"),
272 }
273 }
274}