geonative_shapefile/
header.rs1use crate::bytes::Cursor;
15use crate::error::{Result, ShpError};
16
17pub const SHP_FILE_CODE: i32 = 9994;
18pub const SHP_VERSION: i32 = 1000;
19pub const SHP_HEADER_BYTES: usize = 100;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ShapeType {
27 Null = 0,
28 Point = 1,
29 Polyline = 3,
30 Polygon = 5,
31 Multipoint = 8,
32 PointZ = 11,
33 PolylineZ = 13,
34 PolygonZ = 15,
35 MultipointZ = 18,
36 PointM = 21,
37 PolylineM = 23,
38 PolygonM = 25,
39 MultipointM = 28,
40 Multipatch = 31,
41}
42
43impl ShapeType {
44 pub fn from_i32(v: i32) -> Result<Self> {
45 Ok(match v {
46 0 => Self::Null,
47 1 => Self::Point,
48 3 => Self::Polyline,
49 5 => Self::Polygon,
50 8 => Self::Multipoint,
51 11 => Self::PointZ,
52 13 => Self::PolylineZ,
53 15 => Self::PolygonZ,
54 18 => Self::MultipointZ,
55 21 => Self::PointM,
56 23 => Self::PolylineM,
57 25 => Self::PolygonM,
58 28 => Self::MultipointM,
59 31 => Self::Multipatch,
60 other => return Err(ShpError::malformed(format!("unknown shape type {other}"))),
61 })
62 }
63}
64
65#[derive(Debug, Clone)]
68pub struct ShpHeader {
69 pub file_length_words: i32,
70 pub shape_type: ShapeType,
71 pub bbox_xy: [f64; 4],
73 pub bbox_z: [f64; 2],
75 pub bbox_m: [f64; 2],
77}
78
79pub fn parse(bytes: &[u8]) -> Result<ShpHeader> {
80 if bytes.len() < SHP_HEADER_BYTES {
81 return Err(ShpError::malformed(format!(
82 "file shorter than 100-byte header (got {})",
83 bytes.len()
84 )));
85 }
86 let mut c = Cursor::new(&bytes[..SHP_HEADER_BYTES]);
87 let code = c.read_i32_be()?;
88 if code != SHP_FILE_CODE {
89 return Err(ShpError::malformed(format!(
90 "bad file code {code:#x} (expected {SHP_FILE_CODE:#x})"
91 )));
92 }
93 c.seek(24)?;
95 let file_length_words = c.read_i32_be()?;
96 let version = c.read_i32_le()?;
97 if version != SHP_VERSION {
98 return Err(ShpError::malformed(format!(
99 "bad version {version} (expected {SHP_VERSION})"
100 )));
101 }
102 let shape_type = ShapeType::from_i32(c.read_i32_le()?)?;
103 let bbox_xy = [
104 c.read_f64_le()?,
105 c.read_f64_le()?,
106 c.read_f64_le()?,
107 c.read_f64_le()?,
108 ];
109 let bbox_z = [c.read_f64_le()?, c.read_f64_le()?];
110 let bbox_m = [c.read_f64_le()?, c.read_f64_le()?];
111 Ok(ShpHeader {
112 file_length_words,
113 shape_type,
114 bbox_xy,
115 bbox_z,
116 bbox_m,
117 })
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn synth_polygon_header() {
126 let mut h = vec![0u8; 100];
127 h[0..4].copy_from_slice(&SHP_FILE_CODE.to_be_bytes());
128 h[24..28].copy_from_slice(&500i32.to_be_bytes()); h[28..32].copy_from_slice(&SHP_VERSION.to_le_bytes());
130 h[32..36].copy_from_slice(&5i32.to_le_bytes()); h[36..44].copy_from_slice(&0.0f64.to_le_bytes());
132 h[44..52].copy_from_slice(&0.0f64.to_le_bytes());
133 h[52..60].copy_from_slice(&10.0f64.to_le_bytes());
134 h[60..68].copy_from_slice(&10.0f64.to_le_bytes());
135
136 let parsed = parse(&h).unwrap();
137 assert_eq!(parsed.file_length_words, 500);
138 assert_eq!(parsed.shape_type, ShapeType::Polygon);
139 assert_eq!(parsed.bbox_xy, [0.0, 0.0, 10.0, 10.0]);
140 }
141
142 #[test]
143 fn bad_magic_errors() {
144 let mut h = vec![0u8; 100];
145 h[0..4].copy_from_slice(&1234i32.to_be_bytes());
146 h[28..32].copy_from_slice(&SHP_VERSION.to_le_bytes());
147 assert!(parse(&h).is_err());
148 }
149
150 #[test]
151 fn short_input_errors() {
152 let h = vec![0u8; 50];
153 assert!(parse(&h).is_err());
154 }
155}