Skip to main content

oxigdal_shapefile/shp/
mod.rs

1//! Shapefile (.shp) binary geometry file handling
2//!
3//! This module handles reading and writing the main Shapefile (.shp) file,
4//! which contains the binary geometry data.
5
6pub mod header;
7pub mod shapes;
8
9pub use header::{BoundingBox, HEADER_SIZE, ShapefileHeader};
10pub use shapes::{
11    Box2D, MultiPartShape, MultiPartShapeM, MultiPartShapeZ, Point, PointM, PointZ, ShapeType,
12};
13
14use crate::error::{Result, ShapefileError};
15use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
16use std::io::{Read, Seek, Write};
17
18/// Record header size in bytes
19pub const RECORD_HEADER_SIZE: usize = 8;
20
21/// A Shapefile record
22#[derive(Debug, Clone)]
23pub struct ShapeRecord {
24    /// Record number (1-based)
25    pub record_number: i32,
26    /// Shape geometry
27    pub shape: Shape,
28}
29
30/// Shape geometry variants
31#[derive(Debug, Clone, PartialEq)]
32pub enum Shape {
33    /// Null shape (no geometry)
34    Null,
35    /// 2D point
36    Point(Point),
37    /// 3D point with Z
38    PointZ(PointZ),
39    /// Point with M value
40    PointM(PointM),
41    /// PolyLine (one or more line strings)
42    PolyLine(MultiPartShape),
43    /// Polygon (one or more rings)
44    Polygon(MultiPartShape),
45    /// MultiPoint (collection of points)
46    MultiPoint(MultiPartShape),
47    /// PolyLine with Z coordinates
48    PolyLineZ(MultiPartShapeZ),
49    /// Polygon with Z coordinates
50    PolygonZ(MultiPartShapeZ),
51    /// MultiPoint with Z coordinates
52    MultiPointZ(MultiPartShapeZ),
53    /// PolyLine with M values
54    PolyLineM(MultiPartShapeM),
55    /// Polygon with M values
56    PolygonM(MultiPartShapeM),
57    /// MultiPoint with M values
58    MultiPointM(MultiPartShapeM),
59}
60
61impl Shape {
62    /// Returns the shape type
63    pub fn shape_type(&self) -> ShapeType {
64        match self {
65            Self::Null => ShapeType::Null,
66            Self::Point(_) => ShapeType::Point,
67            Self::PointZ(_) => ShapeType::PointZ,
68            Self::PointM(_) => ShapeType::PointM,
69            Self::PolyLine(_) => ShapeType::PolyLine,
70            Self::Polygon(_) => ShapeType::Polygon,
71            Self::MultiPoint(_) => ShapeType::MultiPoint,
72            Self::PolyLineZ(_) => ShapeType::PolyLineZ,
73            Self::PolygonZ(_) => ShapeType::PolygonZ,
74            Self::MultiPointZ(_) => ShapeType::MultiPointZ,
75            Self::PolyLineM(_) => ShapeType::PolyLineM,
76            Self::PolygonM(_) => ShapeType::PolygonM,
77            Self::MultiPointM(_) => ShapeType::MultiPointM,
78        }
79    }
80
81    /// Reads a shape from a reader
82    pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
83        let shape_type_code = reader
84            .read_i32::<LittleEndian>()
85            .map_err(|_| ShapefileError::unexpected_eof("reading shape type"))?;
86
87        let shape_type = ShapeType::from_code(shape_type_code)?;
88
89        match shape_type {
90            ShapeType::Null => Ok(Self::Null),
91            ShapeType::Point => {
92                let point = Point::read(reader)?;
93                Ok(Self::Point(point))
94            }
95            ShapeType::PointZ => {
96                let point = PointZ::read(reader)?;
97                Ok(Self::PointZ(point))
98            }
99            ShapeType::PointM => {
100                let point = PointM::read(reader)?;
101                Ok(Self::PointM(point))
102            }
103            ShapeType::PolyLine => {
104                let shape = MultiPartShape::read(reader)?;
105                Ok(Self::PolyLine(shape))
106            }
107            ShapeType::Polygon => {
108                let shape = MultiPartShape::read(reader)?;
109                Ok(Self::Polygon(shape))
110            }
111            ShapeType::MultiPoint => {
112                let shape = MultiPartShape::read(reader)?;
113                Ok(Self::MultiPoint(shape))
114            }
115            ShapeType::PolyLineZ => {
116                let shape = MultiPartShapeZ::read(reader)?;
117                Ok(Self::PolyLineZ(shape))
118            }
119            ShapeType::PolygonZ => {
120                let shape = MultiPartShapeZ::read(reader)?;
121                Ok(Self::PolygonZ(shape))
122            }
123            ShapeType::MultiPointZ => {
124                let shape = MultiPartShapeZ::read(reader)?;
125                Ok(Self::MultiPointZ(shape))
126            }
127            ShapeType::PolyLineM => {
128                let shape = MultiPartShapeM::read(reader)?;
129                Ok(Self::PolyLineM(shape))
130            }
131            ShapeType::PolygonM => {
132                let shape = MultiPartShapeM::read(reader)?;
133                Ok(Self::PolygonM(shape))
134            }
135            ShapeType::MultiPointM => {
136                let shape = MultiPartShapeM::read(reader)?;
137                Ok(Self::MultiPointM(shape))
138            }
139            _ => Err(ShapefileError::UnsupportedShapeType {
140                shape_type: shape_type_code,
141            }),
142        }
143    }
144
145    /// Writes a shape to a writer
146    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
147        let shape_type = self.shape_type();
148        writer
149            .write_i32::<LittleEndian>(shape_type.to_code())
150            .map_err(ShapefileError::Io)?;
151
152        match self {
153            Self::Null => Ok(()),
154            Self::Point(point) => point.write(writer),
155            Self::PointZ(point) => point.write(writer),
156            Self::PointM(point) => point.write(writer),
157            Self::PolyLine(shape) => shape.write(writer),
158            Self::Polygon(shape) => shape.write(writer),
159            Self::MultiPoint(shape) => shape.write(writer),
160            Self::PolyLineZ(shape) | Self::PolygonZ(shape) | Self::MultiPointZ(shape) => {
161                shape.write(writer)
162            }
163            Self::PolyLineM(shape) | Self::PolygonM(shape) | Self::MultiPointM(shape) => {
164                shape.write(writer)
165            }
166        }
167    }
168
169    /// Calculates the content length in 16-bit words (excluding shape type)
170    pub fn content_length(&self) -> i32 {
171        match self {
172            Self::Null => 0,
173            // Point: 2 doubles = 16 bytes = 8 words
174            Self::Point(_) => 8,
175            // PointZ: 4 doubles = 32 bytes = 16 words
176            Self::PointZ(_) => 16,
177            // PointM: 3 doubles = 24 bytes = 12 words
178            Self::PointM(_) => 12,
179            // MultiPartShape: bbox (4*8) + num_parts (4) + num_points (4) + parts + points
180            Self::PolyLine(shape) | Self::Polygon(shape) | Self::MultiPoint(shape) => {
181                let bbox_bytes = 32; // 4 * 8 bytes (4 doubles)
182                let counts_bytes = 8; // num_parts + num_points
183                let parts_bytes = shape.num_parts * 4;
184                let points_bytes = shape.num_points * 16; // 2 doubles per point
185                (bbox_bytes + counts_bytes + parts_bytes + points_bytes) / 2
186            }
187            // Z variants: base + z_range + z_values + optional m_range + m_values
188            Self::PolyLineZ(shape) | Self::PolygonZ(shape) | Self::MultiPointZ(shape) => {
189                shape.content_length_words()
190            }
191            // M variants: base + m_range + m_values
192            Self::PolyLineM(shape) | Self::PolygonM(shape) | Self::MultiPointM(shape) => {
193                shape.content_length_words()
194            }
195        }
196    }
197}
198
199impl ShapeRecord {
200    /// Creates a new shape record
201    pub fn new(record_number: i32, shape: Shape) -> Self {
202        Self {
203            record_number,
204            shape,
205        }
206    }
207
208    /// Reads a shape record from a reader
209    pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
210        // Read record header (big endian)
211        let record_number = reader
212            .read_i32::<BigEndian>()
213            .map_err(|_| ShapefileError::unexpected_eof("reading record number"))?;
214
215        let _content_length = reader
216            .read_i32::<BigEndian>()
217            .map_err(|_| ShapefileError::unexpected_eof("reading content length"))?;
218
219        // Read shape (little endian)
220        let shape = Shape::read(reader)?;
221
222        Ok(Self {
223            record_number,
224            shape,
225        })
226    }
227
228    /// Writes a shape record to a writer
229    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
230        // Write record header (big endian)
231        writer
232            .write_i32::<BigEndian>(self.record_number)
233            .map_err(ShapefileError::Io)?;
234
235        // Calculate and write content length (in 16-bit words)
236        let content_length = 2 + self.shape.content_length(); // +2 for shape type (4 bytes = 2 words)
237        writer
238            .write_i32::<BigEndian>(content_length)
239            .map_err(ShapefileError::Io)?;
240
241        // Write shape (little endian)
242        self.shape.write(writer)?;
243
244        Ok(())
245    }
246}
247
248/// Shapefile (.shp) reader
249pub struct ShpReader<R: Read> {
250    reader: R,
251    header: ShapefileHeader,
252}
253
254impl<R: Read> ShpReader<R> {
255    /// Creates a new Shapefile reader
256    pub fn new(mut reader: R) -> Result<Self> {
257        let header = ShapefileHeader::read(&mut reader)?;
258        Ok(Self { reader, header })
259    }
260
261    /// Returns the header
262    pub fn header(&self) -> &ShapefileHeader {
263        &self.header
264    }
265
266    /// Reads the next record
267    pub fn read_record(&mut self) -> Result<Option<ShapeRecord>> {
268        match ShapeRecord::read(&mut self.reader) {
269            Ok(record) => Ok(Some(record)),
270            Err(ShapefileError::Io(ref e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
271                Ok(None)
272            }
273            Err(ShapefileError::UnexpectedEof { .. }) => {
274                // EOF when reading record is expected at end of file
275                Ok(None)
276            }
277            Err(e) => Err(e),
278        }
279    }
280
281    /// Reads all records
282    pub fn read_all_records(&mut self) -> Result<Vec<ShapeRecord>> {
283        let mut records = Vec::new();
284        while let Some(record) = self.read_record()? {
285            records.push(record);
286        }
287        Ok(records)
288    }
289}
290
291/// Shapefile (.shp) writer
292pub struct ShpWriter<W: Write> {
293    writer: W,
294    header: ShapefileHeader,
295    record_count: i32,
296    /// Total file size in 16-bit words (for updating header)
297    file_length_words: i32,
298}
299
300impl<W: Write> ShpWriter<W> {
301    /// Creates a new Shapefile writer
302    pub fn new(writer: W, shape_type: ShapeType, bbox: BoundingBox) -> Self {
303        let header = ShapefileHeader::new(shape_type, bbox);
304        Self {
305            writer,
306            header,
307            record_count: 0,
308            file_length_words: 50, // Header is 100 bytes = 50 words
309        }
310    }
311
312    /// Writes the header (should be called first)
313    pub fn write_header(&mut self) -> Result<()> {
314        self.header.write(&mut self.writer)
315    }
316
317    /// Writes a shape record
318    pub fn write_record(&mut self, shape: Shape) -> Result<()> {
319        self.record_count += 1;
320
321        // Calculate record size before moving shape: header (4 words) + content
322        let content_length = 2 + shape.content_length(); // +2 for shape type
323        self.file_length_words += 4 + content_length; // +4 for record header
324
325        let record = ShapeRecord::new(self.record_count, shape);
326        record.write(&mut self.writer)
327    }
328
329    /// Flushes the internal writer to ensure all data is written
330    pub fn flush(&mut self) -> Result<()> {
331        self.writer.flush().map_err(ShapefileError::Io)
332    }
333
334    /// Finalizes the file (updates header with correct file length)
335    pub fn finalize<S: Write + Seek>(self, _seekable_writer: S) -> Result<()> {
336        // Calculate total file length in 16-bit words
337        // This would require tracking all written bytes
338        // For simplicity, we'll skip this optimization and assume the caller
339        // will handle it if needed
340        Ok(())
341    }
342}
343
344impl<W: Write + Seek> ShpWriter<W> {
345    /// Updates the file length in the header (for seekable writers)
346    pub fn update_file_length(&mut self) -> Result<()> {
347        // Seek to file length position in header (byte 24)
348        self.writer
349            .seek(std::io::SeekFrom::Start(24))
350            .map_err(ShapefileError::Io)?;
351
352        // Write file length (big endian)
353        self.writer
354            .write_i32::<BigEndian>(self.file_length_words)
355            .map_err(ShapefileError::Io)?;
356
357        // Seek back to end of file
358        self.writer
359            .seek(std::io::SeekFrom::End(0))
360            .map_err(ShapefileError::Io)?;
361
362        Ok(())
363    }
364}
365
366#[cfg(test)]
367#[allow(clippy::panic)]
368mod tests {
369    use super::*;
370    use std::io::Cursor;
371
372    #[test]
373    fn test_shape_content_length() {
374        let null_shape = Shape::Null;
375        assert_eq!(null_shape.content_length(), 0);
376
377        let point_shape = Shape::Point(Point::new(10.0, 20.0));
378        assert_eq!(point_shape.content_length(), 8);
379    }
380
381    #[test]
382    fn test_record_round_trip() {
383        let shape = Shape::Point(Point::new(10.5, 20.3));
384        let record = ShapeRecord::new(1, shape.clone());
385
386        let mut buffer = Vec::new();
387        record.write(&mut buffer).expect("write record to buffer");
388
389        let mut cursor = Cursor::new(buffer);
390        let read_record = ShapeRecord::read(&mut cursor).expect("read record from cursor");
391
392        assert_eq!(read_record.record_number, 1);
393        assert_eq!(read_record.shape, shape);
394    }
395
396    #[test]
397    fn test_shp_reader_writer() {
398        let bbox = BoundingBox::new_2d(-180.0, -90.0, 180.0, 90.0).expect("valid bbox");
399        let mut buffer = Cursor::new(Vec::new());
400
401        // Write
402        {
403            let mut writer = ShpWriter::new(&mut buffer, ShapeType::Point, bbox);
404            writer.write_header().expect("write header");
405            writer
406                .write_record(Shape::Point(Point::new(10.0, 20.0)))
407                .expect("write record 1");
408            writer
409                .write_record(Shape::Point(Point::new(30.0, 40.0)))
410                .expect("write record 2");
411        }
412
413        // Read
414        buffer.set_position(0);
415        let mut reader = ShpReader::new(buffer).expect("create reader");
416
417        assert_eq!(reader.header().shape_type, ShapeType::Point);
418
419        let records = reader.read_all_records().expect("read records");
420        assert_eq!(records.len(), 2);
421        assert_eq!(records[0].record_number, 1);
422        assert_eq!(records[1].record_number, 2);
423    }
424
425    #[test]
426    fn test_polyline_z_round_trip() {
427        let points = vec![
428            Point::new(0.0, 0.0),
429            Point::new(10.0, 10.0),
430            Point::new(20.0, 5.0),
431        ];
432        let z_values = vec![100.0, 200.0, 150.0];
433        let m_values = Some(vec![0.0, 0.5, 1.0]);
434
435        let shape_z = MultiPartShapeZ::new(vec![0], points, z_values.clone(), m_values.clone())
436            .expect("valid shape");
437        let shape = Shape::PolyLineZ(shape_z);
438
439        let mut buffer = Vec::new();
440        let record = ShapeRecord::new(1, shape.clone());
441        record.write(&mut buffer).expect("write record");
442
443        let mut cursor = Cursor::new(buffer);
444        let read_record = ShapeRecord::read(&mut cursor).expect("read record");
445
446        assert_eq!(read_record.record_number, 1);
447        if let Shape::PolyLineZ(ref sz) = read_record.shape {
448            assert_eq!(sz.base.num_points, 3);
449            assert_eq!(sz.z_values.len(), 3);
450            assert!((sz.z_values[0] - 100.0).abs() < f64::EPSILON);
451            assert!((sz.z_values[1] - 200.0).abs() < f64::EPSILON);
452            assert!((sz.z_values[2] - 150.0).abs() < f64::EPSILON);
453            assert!(sz.m_values.is_some());
454            let mv = sz.m_values.as_ref().expect("m_values");
455            assert!((mv[0] - 0.0).abs() < f64::EPSILON);
456            assert!((mv[1] - 0.5).abs() < f64::EPSILON);
457            assert!((mv[2] - 1.0).abs() < f64::EPSILON);
458        } else {
459            panic!("Expected PolyLineZ shape");
460        }
461    }
462
463    #[test]
464    fn test_polygon_m_round_trip() {
465        let points = vec![
466            Point::new(0.0, 0.0),
467            Point::new(10.0, 0.0),
468            Point::new(10.0, 10.0),
469            Point::new(0.0, 0.0),
470        ];
471        let m_values = vec![0.0, 1.0, 2.0, 0.0];
472
473        let shape_m = MultiPartShapeM::new(vec![0], points, m_values.clone()).expect("valid shape");
474        let shape = Shape::PolygonM(shape_m);
475
476        let mut buffer = Vec::new();
477        let record = ShapeRecord::new(1, shape.clone());
478        record.write(&mut buffer).expect("write record");
479
480        let mut cursor = Cursor::new(buffer);
481        let read_record = ShapeRecord::read(&mut cursor).expect("read record");
482
483        assert_eq!(read_record.record_number, 1);
484        if let Shape::PolygonM(ref sm) = read_record.shape {
485            assert_eq!(sm.base.num_points, 4);
486            assert_eq!(sm.m_values.len(), 4);
487            assert!((sm.m_values[0] - 0.0).abs() < f64::EPSILON);
488            assert!((sm.m_values[1] - 1.0).abs() < f64::EPSILON);
489        } else {
490            panic!("Expected PolygonM shape");
491        }
492    }
493
494    #[test]
495    fn test_z_content_length() {
496        let points = vec![Point::new(0.0, 0.0), Point::new(10.0, 10.0)];
497        let z_values = vec![100.0, 200.0];
498
499        let shape_z = MultiPartShapeZ::new(vec![0], points, z_values, None).expect("valid shape");
500        let shape = Shape::PolyLineZ(shape_z);
501        // content_length should include base + z_range + z_values but NOT m
502        let cl = shape.content_length();
503        assert!(cl > 0);
504    }
505}