use crate::shp::Error;
use crate::shp::point_z::BBoxZ;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use std::fmt;
use std::io::Read;
pub(crate) const HEADER_SIZE: i32 = 100;
const FILE_CODE: i32 = 9994;
const SIZE_OF_SKIP: usize = size_of::<i32>() * 5;
#[derive(Copy, Clone, PartialEq)]
pub struct Header {
pub file_length: i32,
pub bbox: BBoxZ,
pub shape_type: ShapeType,
pub version: i32,
}
impl Default for Header {
fn default() -> Self {
Header {
bbox: BBoxZ::default(),
shape_type: ShapeType::NullShape,
file_length: HEADER_SIZE / 2,
version: 1000,
}
}
}
impl Header {
pub fn read_from<T: Read>(mut source: &mut T) -> Result<Header, Error> {
let file_code = source.read_i32::<BigEndian>()?;
if file_code != FILE_CODE {
return Err(Error::InvalidFileCode(file_code));
}
let mut skip: [u8; SIZE_OF_SKIP] = [0; SIZE_OF_SKIP];
source.read_exact(&mut skip)?;
let file_length_16_bit = source.read_i32::<BigEndian>()?;
let version = source.read_i32::<LittleEndian>()?;
let shape_type = ShapeType::read_from(&mut source)?;
let mut hdr = Header {
shape_type,
version,
file_length: file_length_16_bit,
..Default::default()
};
hdr.bbox.min.x = source.read_f64::<LittleEndian>()?;
hdr.bbox.min.y = source.read_f64::<LittleEndian>()?;
hdr.bbox.max.x = source.read_f64::<LittleEndian>()?;
hdr.bbox.max.y = source.read_f64::<LittleEndian>()?;
hdr.bbox.min.z = source.read_f64::<LittleEndian>()?;
hdr.bbox.max.z = source.read_f64::<LittleEndian>()?;
hdr.bbox.min.m = source.read_f64::<LittleEndian>()?;
hdr.bbox.max.m = source.read_f64::<LittleEndian>()?;
Ok(hdr)
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ShapeType {
NullShape = 0,
Point = 1,
Polyline = 3,
Polygon = 5,
Multipoint = 8,
PointZ = 11,
PolylineZ = 13,
PolygonZ = 15,
MultipointZ = 18,
PointM = 21,
PolylineM = 23,
PolygonM = 25,
MultipointM = 28,
Multipatch = 31,
}
impl ShapeType {
pub(crate) fn read_from<T: Read>(source: &mut T) -> Result<ShapeType, Error> {
let code = source.read_i32::<LittleEndian>()?;
Self::from(code).ok_or(Error::InvalidShapeType(code))
}
pub fn from(code: i32) -> Option<ShapeType> {
match code {
0 => Some(ShapeType::NullShape),
1 => Some(ShapeType::Point),
3 => Some(ShapeType::Polyline),
5 => Some(ShapeType::Polygon),
8 => Some(ShapeType::Multipoint),
11 => Some(ShapeType::PointZ),
13 => Some(ShapeType::PolylineZ),
15 => Some(ShapeType::PolygonZ),
18 => Some(ShapeType::MultipointZ),
21 => Some(ShapeType::PointM),
23 => Some(ShapeType::PolylineM),
25 => Some(ShapeType::PolygonM),
28 => Some(ShapeType::MultipointM),
31 => Some(ShapeType::Multipatch),
_ => None,
}
}
pub fn has_z(self) -> bool {
matches!(
self,
ShapeType::PointZ | ShapeType::PolylineZ | ShapeType::PolygonZ | ShapeType::MultipointZ
)
}
pub fn has_m(self) -> bool {
matches!(
self,
ShapeType::PointZ
| ShapeType::PolylineZ
| ShapeType::PolygonZ
| ShapeType::MultipointZ
| ShapeType::PointM
| ShapeType::PolylineM
| ShapeType::PolygonM
| ShapeType::MultipointM
)
}
pub fn is_multipart(self) -> bool {
!matches!(
self,
ShapeType::Point
| ShapeType::PointM
| ShapeType::PointZ
| ShapeType::Multipoint
| ShapeType::MultipointM
| ShapeType::MultipointZ
)
}
}
impl fmt::Display for ShapeType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ShapeType::NullShape => write!(f, "NullShape"),
ShapeType::Point => write!(f, "Point"),
ShapeType::Polyline => write!(f, "Polyline"),
ShapeType::Polygon => write!(f, "Polygon"),
ShapeType::Multipoint => write!(f, "Multipoint"),
ShapeType::PointZ => write!(f, "PointZ"),
ShapeType::PolylineZ => write!(f, "PolylineZ"),
ShapeType::PolygonZ => write!(f, "PolygonZ"),
ShapeType::MultipointZ => write!(f, "MultipointZ"),
ShapeType::PointM => write!(f, "PointM"),
ShapeType::PolylineM => write!(f, "PolylineM"),
ShapeType::PolygonM => write!(f, "PolygonM"),
ShapeType::MultipointM => write!(f, "MultipointM"),
ShapeType::Multipatch => write!(f, "Multipatch"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use byteorder::WriteBytesExt;
use std::io::{Seek, SeekFrom};
#[test]
fn wrong_file_code() {
use std::io::Cursor;
let mut src = Cursor::new(vec![]);
src.write_i32::<BigEndian>(42).unwrap();
src.seek(SeekFrom::Start(0)).unwrap();
assert!(Header::read_from(&mut src).is_err());
}
}