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}