use crate::error::{Result, ShapefileError};
use crate::shp::shapes::ShapeType;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Write};
pub const FILE_CODE: i32 = 9994;
pub const VERSION: i32 = 1000;
pub const HEADER_SIZE: usize = 100;
#[derive(Debug, Clone, PartialEq)]
pub struct ShapefileHeader {
pub file_code: i32,
pub file_length: i32,
pub version: i32,
pub shape_type: ShapeType,
pub bbox: BoundingBox,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BoundingBox {
pub x_min: f64,
pub y_min: f64,
pub x_max: f64,
pub y_max: f64,
pub z_min: Option<f64>,
pub z_max: Option<f64>,
pub m_min: Option<f64>,
pub m_max: Option<f64>,
}
impl BoundingBox {
pub fn new_2d(x_min: f64, y_min: f64, x_max: f64, y_max: f64) -> Result<Self> {
if x_min > x_max {
return Err(ShapefileError::InvalidBbox {
message: format!("x_min ({}) > x_max ({})", x_min, x_max),
});
}
if y_min > y_max {
return Err(ShapefileError::InvalidBbox {
message: format!("y_min ({}) > y_max ({})", y_min, y_max),
});
}
Ok(Self {
x_min,
y_min,
x_max,
y_max,
z_min: None,
z_max: None,
m_min: None,
m_max: None,
})
}
pub fn new_3d(
x_min: f64,
y_min: f64,
x_max: f64,
y_max: f64,
z_min: f64,
z_max: f64,
) -> Result<Self> {
let mut bbox = Self::new_2d(x_min, y_min, x_max, y_max)?;
if z_min > z_max {
return Err(ShapefileError::InvalidBbox {
message: format!("z_min ({}) > z_max ({})", z_min, z_max),
});
}
bbox.z_min = Some(z_min);
bbox.z_max = Some(z_max);
Ok(bbox)
}
#[allow(dead_code)]
fn has_valid_z(&self) -> bool {
match (self.z_min, self.z_max) {
(Some(z_min), Some(z_max)) => z_min.is_finite() && z_max.is_finite(),
(None, None) => true,
_ => false,
}
}
#[allow(dead_code)]
fn has_valid_m(&self) -> bool {
match (self.m_min, self.m_max) {
(Some(m_min), Some(m_max)) => m_min.is_finite() && m_max.is_finite(),
(None, None) => true,
_ => false,
}
}
}
impl ShapefileHeader {
pub fn new(shape_type: ShapeType, bbox: BoundingBox) -> Self {
Self {
file_code: FILE_CODE,
file_length: 50, version: VERSION,
shape_type,
bbox,
}
}
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
let file_code = reader
.read_i32::<BigEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading file code"))?;
if file_code != FILE_CODE {
return Err(ShapefileError::InvalidFileCode { actual: file_code });
}
let mut unused = [0u8; 20];
reader
.read_exact(&mut unused)
.map_err(|_| ShapefileError::unexpected_eof("reading unused header bytes"))?;
let file_length = reader
.read_i32::<BigEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading file length"))?;
let version = reader
.read_i32::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading version"))?;
if version != VERSION {
return Err(ShapefileError::InvalidVersion { version });
}
let shape_type_code = reader
.read_i32::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading shape type"))?;
let shape_type = ShapeType::from_code(shape_type_code)?;
let x_min = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading x_min"))?;
let y_min = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading y_min"))?;
let x_max = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading x_max"))?;
let y_max = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading y_max"))?;
let z_min = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading z_min"))?;
let z_max = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading z_max"))?;
let m_min = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading m_min"))?;
let m_max = reader
.read_f64::<LittleEndian>()
.map_err(|_| ShapefileError::unexpected_eof("reading m_max"))?;
let mut bbox = BoundingBox::new_2d(x_min, y_min, x_max, y_max)?;
if z_min.is_finite() && z_max.is_finite() && z_min > -1e38 {
bbox.z_min = Some(z_min);
bbox.z_max = Some(z_max);
}
if m_min.is_finite() && m_max.is_finite() && m_min > -1e38 {
bbox.m_min = Some(m_min);
bbox.m_max = Some(m_max);
}
Ok(Self {
file_code,
file_length,
version,
shape_type,
bbox,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
writer
.write_i32::<BigEndian>(self.file_code)
.map_err(ShapefileError::Io)?;
writer.write_all(&[0u8; 20]).map_err(ShapefileError::Io)?;
writer
.write_i32::<BigEndian>(self.file_length)
.map_err(ShapefileError::Io)?;
writer
.write_i32::<LittleEndian>(self.version)
.map_err(ShapefileError::Io)?;
writer
.write_i32::<LittleEndian>(self.shape_type.to_code())
.map_err(ShapefileError::Io)?;
writer
.write_f64::<LittleEndian>(self.bbox.x_min)
.map_err(ShapefileError::Io)?;
writer
.write_f64::<LittleEndian>(self.bbox.y_min)
.map_err(ShapefileError::Io)?;
writer
.write_f64::<LittleEndian>(self.bbox.x_max)
.map_err(ShapefileError::Io)?;
writer
.write_f64::<LittleEndian>(self.bbox.y_max)
.map_err(ShapefileError::Io)?;
let z_min = self.bbox.z_min.unwrap_or(0.0);
let z_max = self.bbox.z_max.unwrap_or(0.0);
writer
.write_f64::<LittleEndian>(z_min)
.map_err(ShapefileError::Io)?;
writer
.write_f64::<LittleEndian>(z_max)
.map_err(ShapefileError::Io)?;
let m_min = self.bbox.m_min.unwrap_or(0.0);
let m_max = self.bbox.m_max.unwrap_or(0.0);
writer
.write_f64::<LittleEndian>(m_min)
.map_err(ShapefileError::Io)?;
writer
.write_f64::<LittleEndian>(m_max)
.map_err(ShapefileError::Io)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_bounding_box_2d() {
let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0);
assert!(bbox.is_ok());
let bbox = bbox.expect("valid 2d bounding box");
assert_eq!(bbox.x_min, -180.0);
assert_eq!(bbox.y_max, 90.0);
assert!(bbox.z_min.is_none());
}
#[test]
fn test_bounding_box_invalid() {
let bbox = BoundingBox::new_2d(180.0, -90.0, -180.0, 90.0);
assert!(bbox.is_err());
}
#[test]
fn test_header_round_trip() {
let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0).expect("valid bounding box");
let header = ShapefileHeader::new(ShapeType::Point, bbox);
let mut buffer = Vec::new();
header.write(&mut buffer).expect("write header to buffer");
assert_eq!(buffer.len(), HEADER_SIZE);
let mut cursor = Cursor::new(buffer);
let read_header = ShapefileHeader::read(&mut cursor).expect("read header from cursor");
assert_eq!(read_header.file_code, FILE_CODE);
assert_eq!(read_header.version, VERSION);
assert_eq!(read_header.shape_type, ShapeType::Point);
assert_eq!(read_header.bbox.x_min, -180.0);
}
#[test]
fn test_invalid_file_code() {
let mut buffer = vec![0u8; HEADER_SIZE];
let mut cursor = Cursor::new(&mut buffer);
cursor
.write_i32::<BigEndian>(1234)
.expect("write invalid file code to cursor");
let mut cursor = Cursor::new(&buffer[..]);
let result = ShapefileHeader::read(&mut cursor);
assert!(result.is_err());
}
}