1use crate::error::{Result, ShapefileError};
20use crate::shp::shapes::ShapeType;
21use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
22use std::io::{Read, Write};
23
24pub const FILE_CODE: i32 = 9994;
26
27pub const VERSION: i32 = 1000;
29
30pub const HEADER_SIZE: usize = 100;
32
33#[derive(Debug, Clone, PartialEq)]
35pub struct ShapefileHeader {
36 pub file_code: i32,
38 pub file_length: i32,
40 pub version: i32,
42 pub shape_type: ShapeType,
44 pub bbox: BoundingBox,
46}
47
48#[derive(Debug, Clone, PartialEq)]
50pub struct BoundingBox {
51 pub x_min: f64,
53 pub y_min: f64,
55 pub x_max: f64,
57 pub y_max: f64,
59 pub z_min: Option<f64>,
61 pub z_max: Option<f64>,
63 pub m_min: Option<f64>,
65 pub m_max: Option<f64>,
67}
68
69impl BoundingBox {
70 pub fn new_2d(x_min: f64, y_min: f64, x_max: f64, y_max: f64) -> Result<Self> {
72 if x_min > x_max {
73 return Err(ShapefileError::InvalidBbox {
74 message: format!("x_min ({}) > x_max ({})", x_min, x_max),
75 });
76 }
77 if y_min > y_max {
78 return Err(ShapefileError::InvalidBbox {
79 message: format!("y_min ({}) > y_max ({})", y_min, y_max),
80 });
81 }
82
83 Ok(Self {
84 x_min,
85 y_min,
86 x_max,
87 y_max,
88 z_min: None,
89 z_max: None,
90 m_min: None,
91 m_max: None,
92 })
93 }
94
95 pub fn new_3d(
97 x_min: f64,
98 y_min: f64,
99 x_max: f64,
100 y_max: f64,
101 z_min: f64,
102 z_max: f64,
103 ) -> Result<Self> {
104 let mut bbox = Self::new_2d(x_min, y_min, x_max, y_max)?;
105 if z_min > z_max {
106 return Err(ShapefileError::InvalidBbox {
107 message: format!("z_min ({}) > z_max ({})", z_min, z_max),
108 });
109 }
110 bbox.z_min = Some(z_min);
111 bbox.z_max = Some(z_max);
112 Ok(bbox)
113 }
114
115 #[allow(dead_code)]
117 fn has_valid_z(&self) -> bool {
118 match (self.z_min, self.z_max) {
119 (Some(z_min), Some(z_max)) => z_min.is_finite() && z_max.is_finite(),
120 (None, None) => true,
121 _ => false,
122 }
123 }
124
125 #[allow(dead_code)]
127 fn has_valid_m(&self) -> bool {
128 match (self.m_min, self.m_max) {
129 (Some(m_min), Some(m_max)) => m_min.is_finite() && m_max.is_finite(),
130 (None, None) => true,
131 _ => false,
132 }
133 }
134}
135
136impl ShapefileHeader {
137 pub fn new(shape_type: ShapeType, bbox: BoundingBox) -> Self {
139 Self {
140 file_code: FILE_CODE,
141 file_length: 50, version: VERSION,
143 shape_type,
144 bbox,
145 }
146 }
147
148 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
150 let file_code = reader
152 .read_i32::<BigEndian>()
153 .map_err(|_| ShapefileError::unexpected_eof("reading file code"))?;
154
155 if file_code != FILE_CODE {
156 return Err(ShapefileError::InvalidFileCode { actual: file_code });
157 }
158
159 let mut unused = [0u8; 20];
161 reader
162 .read_exact(&mut unused)
163 .map_err(|_| ShapefileError::unexpected_eof("reading unused header bytes"))?;
164
165 let file_length = reader
167 .read_i32::<BigEndian>()
168 .map_err(|_| ShapefileError::unexpected_eof("reading file length"))?;
169
170 let version = reader
172 .read_i32::<LittleEndian>()
173 .map_err(|_| ShapefileError::unexpected_eof("reading version"))?;
174
175 if version != VERSION {
176 return Err(ShapefileError::InvalidVersion { version });
177 }
178
179 let shape_type_code = reader
181 .read_i32::<LittleEndian>()
182 .map_err(|_| ShapefileError::unexpected_eof("reading shape type"))?;
183
184 let shape_type = ShapeType::from_code(shape_type_code)?;
185
186 let x_min = reader
188 .read_f64::<LittleEndian>()
189 .map_err(|_| ShapefileError::unexpected_eof("reading x_min"))?;
190 let y_min = reader
191 .read_f64::<LittleEndian>()
192 .map_err(|_| ShapefileError::unexpected_eof("reading y_min"))?;
193 let x_max = reader
194 .read_f64::<LittleEndian>()
195 .map_err(|_| ShapefileError::unexpected_eof("reading x_max"))?;
196 let y_max = reader
197 .read_f64::<LittleEndian>()
198 .map_err(|_| ShapefileError::unexpected_eof("reading y_max"))?;
199
200 let z_min = reader
202 .read_f64::<LittleEndian>()
203 .map_err(|_| ShapefileError::unexpected_eof("reading z_min"))?;
204 let z_max = reader
205 .read_f64::<LittleEndian>()
206 .map_err(|_| ShapefileError::unexpected_eof("reading z_max"))?;
207
208 let m_min = reader
210 .read_f64::<LittleEndian>()
211 .map_err(|_| ShapefileError::unexpected_eof("reading m_min"))?;
212 let m_max = reader
213 .read_f64::<LittleEndian>()
214 .map_err(|_| ShapefileError::unexpected_eof("reading m_max"))?;
215
216 let mut bbox = BoundingBox::new_2d(x_min, y_min, x_max, y_max)?;
218
219 if z_min.is_finite() && z_max.is_finite() && z_min > -1e38 {
221 bbox.z_min = Some(z_min);
222 bbox.z_max = Some(z_max);
223 }
224
225 if m_min.is_finite() && m_max.is_finite() && m_min > -1e38 {
227 bbox.m_min = Some(m_min);
228 bbox.m_max = Some(m_max);
229 }
230
231 Ok(Self {
232 file_code,
233 file_length,
234 version,
235 shape_type,
236 bbox,
237 })
238 }
239
240 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
242 writer
244 .write_i32::<BigEndian>(self.file_code)
245 .map_err(ShapefileError::Io)?;
246
247 writer.write_all(&[0u8; 20]).map_err(ShapefileError::Io)?;
249
250 writer
252 .write_i32::<BigEndian>(self.file_length)
253 .map_err(ShapefileError::Io)?;
254
255 writer
257 .write_i32::<LittleEndian>(self.version)
258 .map_err(ShapefileError::Io)?;
259
260 writer
262 .write_i32::<LittleEndian>(self.shape_type.to_code())
263 .map_err(ShapefileError::Io)?;
264
265 writer
267 .write_f64::<LittleEndian>(self.bbox.x_min)
268 .map_err(ShapefileError::Io)?;
269 writer
270 .write_f64::<LittleEndian>(self.bbox.y_min)
271 .map_err(ShapefileError::Io)?;
272 writer
273 .write_f64::<LittleEndian>(self.bbox.x_max)
274 .map_err(ShapefileError::Io)?;
275 writer
276 .write_f64::<LittleEndian>(self.bbox.y_max)
277 .map_err(ShapefileError::Io)?;
278
279 let z_min = self.bbox.z_min.unwrap_or(0.0);
281 let z_max = self.bbox.z_max.unwrap_or(0.0);
282 writer
283 .write_f64::<LittleEndian>(z_min)
284 .map_err(ShapefileError::Io)?;
285 writer
286 .write_f64::<LittleEndian>(z_max)
287 .map_err(ShapefileError::Io)?;
288
289 let m_min = self.bbox.m_min.unwrap_or(0.0);
291 let m_max = self.bbox.m_max.unwrap_or(0.0);
292 writer
293 .write_f64::<LittleEndian>(m_min)
294 .map_err(ShapefileError::Io)?;
295 writer
296 .write_f64::<LittleEndian>(m_max)
297 .map_err(ShapefileError::Io)?;
298
299 Ok(())
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use std::io::Cursor;
307
308 #[test]
309 fn test_bounding_box_2d() {
310 let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0);
311 assert!(bbox.is_ok());
312 let bbox = bbox.expect("valid 2d bounding box");
313 assert_eq!(bbox.x_min, -180.0);
314 assert_eq!(bbox.y_max, 90.0);
315 assert!(bbox.z_min.is_none());
316 }
317
318 #[test]
319 fn test_bounding_box_invalid() {
320 let bbox = BoundingBox::new_2d(180.0, -90.0, -180.0, 90.0);
321 assert!(bbox.is_err());
322 }
323
324 #[test]
325 fn test_header_round_trip() {
326 let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0).expect("valid bounding box");
327 let header = ShapefileHeader::new(ShapeType::Point, bbox);
328
329 let mut buffer = Vec::new();
330 header.write(&mut buffer).expect("write header to buffer");
331
332 assert_eq!(buffer.len(), HEADER_SIZE);
333
334 let mut cursor = Cursor::new(buffer);
335 let read_header = ShapefileHeader::read(&mut cursor).expect("read header from cursor");
336
337 assert_eq!(read_header.file_code, FILE_CODE);
338 assert_eq!(read_header.version, VERSION);
339 assert_eq!(read_header.shape_type, ShapeType::Point);
340 assert_eq!(read_header.bbox.x_min, -180.0);
341 }
342
343 #[test]
344 fn test_invalid_file_code() {
345 let mut buffer = vec![0u8; HEADER_SIZE];
346 let mut cursor = Cursor::new(&mut buffer);
348 cursor
349 .write_i32::<BigEndian>(1234)
350 .expect("write invalid file code to cursor");
351
352 let mut cursor = Cursor::new(&buffer[..]);
353 let result = ShapefileHeader::read(&mut cursor);
354 assert!(result.is_err());
355 }
356}