1use crate::error::{Result, ShapefileError};
7use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
8use std::io::{Read, Write};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum ShapeType {
13 Null,
15 Point,
17 PolyLine,
19 Polygon,
21 MultiPoint,
23 PointZ,
25 PolyLineZ,
27 PolygonZ,
29 MultiPointZ,
31 PointM,
33 PolyLineM,
35 PolygonM,
37 MultiPointM,
39 MultiPatch,
41}
42
43impl ShapeType {
44 pub fn from_code(code: i32) -> Result<Self> {
46 match code {
47 0 => Ok(Self::Null),
48 1 => Ok(Self::Point),
49 3 => Ok(Self::PolyLine),
50 5 => Ok(Self::Polygon),
51 8 => Ok(Self::MultiPoint),
52 11 => Ok(Self::PointZ),
53 13 => Ok(Self::PolyLineZ),
54 15 => Ok(Self::PolygonZ),
55 18 => Ok(Self::MultiPointZ),
56 21 => Ok(Self::PointM),
57 23 => Ok(Self::PolyLineM),
58 25 => Ok(Self::PolygonM),
59 28 => Ok(Self::MultiPointM),
60 31 => Ok(Self::MultiPatch),
61 _ => Err(ShapefileError::InvalidShapeType { shape_type: code }),
62 }
63 }
64
65 pub fn to_code(self) -> i32 {
67 match self {
68 Self::Null => 0,
69 Self::Point => 1,
70 Self::PolyLine => 3,
71 Self::Polygon => 5,
72 Self::MultiPoint => 8,
73 Self::PointZ => 11,
74 Self::PolyLineZ => 13,
75 Self::PolygonZ => 15,
76 Self::MultiPointZ => 18,
77 Self::PointM => 21,
78 Self::PolyLineM => 23,
79 Self::PolygonM => 25,
80 Self::MultiPointM => 28,
81 Self::MultiPatch => 31,
82 }
83 }
84
85 pub fn has_z(self) -> bool {
87 matches!(
88 self,
89 Self::PointZ | Self::PolyLineZ | Self::PolygonZ | Self::MultiPointZ | Self::MultiPatch
90 )
91 }
92
93 pub fn has_m(self) -> bool {
95 matches!(
96 self,
97 Self::PointM
98 | Self::PolyLineM
99 | Self::PolygonM
100 | Self::MultiPointM
101 | Self::PointZ
102 | Self::PolyLineZ
103 | Self::PolygonZ
104 | Self::MultiPointZ
105 | Self::MultiPatch
106 )
107 }
108
109 pub fn name(self) -> &'static str {
111 match self {
112 Self::Null => "Null",
113 Self::Point => "Point",
114 Self::PolyLine => "PolyLine",
115 Self::Polygon => "Polygon",
116 Self::MultiPoint => "MultiPoint",
117 Self::PointZ => "PointZ",
118 Self::PolyLineZ => "PolyLineZ",
119 Self::PolygonZ => "PolygonZ",
120 Self::MultiPointZ => "MultiPointZ",
121 Self::PointM => "PointM",
122 Self::PolyLineM => "PolyLineM",
123 Self::PolygonM => "PolygonM",
124 Self::MultiPointM => "MultiPointM",
125 Self::MultiPatch => "MultiPatch",
126 }
127 }
128}
129
130#[derive(Debug, Clone, PartialEq)]
132pub struct Point {
133 pub x: f64,
135 pub y: f64,
137}
138
139impl Point {
140 pub fn new(x: f64, y: f64) -> Self {
142 Self { x, y }
143 }
144
145 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
147 let x = reader
148 .read_f64::<LittleEndian>()
149 .map_err(|_| ShapefileError::unexpected_eof("reading point x"))?;
150 let y = reader
151 .read_f64::<LittleEndian>()
152 .map_err(|_| ShapefileError::unexpected_eof("reading point y"))?;
153
154 if !x.is_finite() || !y.is_finite() {
155 return Err(ShapefileError::invalid_coordinates(
156 "point coordinates must be finite",
157 ));
158 }
159
160 Ok(Self { x, y })
161 }
162
163 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
165 writer
166 .write_f64::<LittleEndian>(self.x)
167 .map_err(ShapefileError::Io)?;
168 writer
169 .write_f64::<LittleEndian>(self.y)
170 .map_err(ShapefileError::Io)?;
171 Ok(())
172 }
173}
174
175#[derive(Debug, Clone, PartialEq)]
177pub struct PointZ {
178 pub x: f64,
180 pub y: f64,
182 pub z: f64,
184 pub m: Option<f64>,
186}
187
188impl PointZ {
189 pub fn new(x: f64, y: f64, z: f64) -> Self {
191 Self { x, y, z, m: None }
192 }
193
194 pub fn new_with_m(x: f64, y: f64, z: f64, m: f64) -> Self {
196 Self {
197 x,
198 y,
199 z,
200 m: Some(m),
201 }
202 }
203
204 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
206 let x = reader
207 .read_f64::<LittleEndian>()
208 .map_err(|_| ShapefileError::unexpected_eof("reading pointz x"))?;
209 let y = reader
210 .read_f64::<LittleEndian>()
211 .map_err(|_| ShapefileError::unexpected_eof("reading pointz y"))?;
212 let z = reader
213 .read_f64::<LittleEndian>()
214 .map_err(|_| ShapefileError::unexpected_eof("reading pointz z"))?;
215
216 if !x.is_finite() || !y.is_finite() || !z.is_finite() {
217 return Err(ShapefileError::invalid_coordinates(
218 "pointz coordinates must be finite",
219 ));
220 }
221
222 let m = match reader.read_f64::<LittleEndian>() {
224 Ok(m_val) if m_val.is_finite() => Some(m_val),
225 _ => None,
226 };
227
228 Ok(Self { x, y, z, m })
229 }
230
231 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
233 writer
234 .write_f64::<LittleEndian>(self.x)
235 .map_err(ShapefileError::Io)?;
236 writer
237 .write_f64::<LittleEndian>(self.y)
238 .map_err(ShapefileError::Io)?;
239 writer
240 .write_f64::<LittleEndian>(self.z)
241 .map_err(ShapefileError::Io)?;
242 writer
243 .write_f64::<LittleEndian>(self.m.unwrap_or(0.0))
244 .map_err(ShapefileError::Io)?;
245 Ok(())
246 }
247}
248
249#[derive(Debug, Clone, PartialEq)]
251pub struct PointM {
252 pub x: f64,
254 pub y: f64,
256 pub m: f64,
258}
259
260impl PointM {
261 pub fn new(x: f64, y: f64, m: f64) -> Self {
263 Self { x, y, m }
264 }
265
266 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
268 let x = reader
269 .read_f64::<LittleEndian>()
270 .map_err(|_| ShapefileError::unexpected_eof("reading pointm x"))?;
271 let y = reader
272 .read_f64::<LittleEndian>()
273 .map_err(|_| ShapefileError::unexpected_eof("reading pointm y"))?;
274 let m = reader
275 .read_f64::<LittleEndian>()
276 .map_err(|_| ShapefileError::unexpected_eof("reading pointm m"))?;
277
278 if !x.is_finite() || !y.is_finite() {
279 return Err(ShapefileError::invalid_coordinates(
280 "pointm coordinates must be finite",
281 ));
282 }
283
284 Ok(Self { x, y, m })
285 }
286
287 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
289 writer
290 .write_f64::<LittleEndian>(self.x)
291 .map_err(ShapefileError::Io)?;
292 writer
293 .write_f64::<LittleEndian>(self.y)
294 .map_err(ShapefileError::Io)?;
295 writer
296 .write_f64::<LittleEndian>(self.m)
297 .map_err(ShapefileError::Io)?;
298 Ok(())
299 }
300}
301
302#[derive(Debug, Clone, PartialEq)]
304pub struct Box2D {
305 pub x_min: f64,
307 pub y_min: f64,
309 pub x_max: f64,
311 pub y_max: f64,
313}
314
315impl Box2D {
316 pub fn new(x_min: f64, y_min: f64, x_max: f64, y_max: f64) -> Result<Self> {
318 if x_min > x_max {
319 return Err(ShapefileError::InvalidBbox {
320 message: format!("x_min ({}) > x_max ({})", x_min, x_max),
321 });
322 }
323 if y_min > y_max {
324 return Err(ShapefileError::InvalidBbox {
325 message: format!("y_min ({}) > y_max ({})", y_min, y_max),
326 });
327 }
328 Ok(Self {
329 x_min,
330 y_min,
331 x_max,
332 y_max,
333 })
334 }
335
336 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
338 let x_min = reader
339 .read_f64::<LittleEndian>()
340 .map_err(|_| ShapefileError::unexpected_eof("reading bbox x_min"))?;
341 let y_min = reader
342 .read_f64::<LittleEndian>()
343 .map_err(|_| ShapefileError::unexpected_eof("reading bbox y_min"))?;
344 let x_max = reader
345 .read_f64::<LittleEndian>()
346 .map_err(|_| ShapefileError::unexpected_eof("reading bbox x_max"))?;
347 let y_max = reader
348 .read_f64::<LittleEndian>()
349 .map_err(|_| ShapefileError::unexpected_eof("reading bbox y_max"))?;
350
351 Self::new(x_min, y_min, x_max, y_max)
352 }
353
354 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
356 writer
357 .write_f64::<LittleEndian>(self.x_min)
358 .map_err(ShapefileError::Io)?;
359 writer
360 .write_f64::<LittleEndian>(self.y_min)
361 .map_err(ShapefileError::Io)?;
362 writer
363 .write_f64::<LittleEndian>(self.x_max)
364 .map_err(ShapefileError::Io)?;
365 writer
366 .write_f64::<LittleEndian>(self.y_max)
367 .map_err(ShapefileError::Io)?;
368 Ok(())
369 }
370
371 pub fn from_points(points: &[Point]) -> Result<Self> {
373 if points.is_empty() {
374 return Err(ShapefileError::invalid_geometry(
375 "cannot compute bbox from empty points",
376 ));
377 }
378
379 let mut x_min = points[0].x;
380 let mut y_min = points[0].y;
381 let mut x_max = points[0].x;
382 let mut y_max = points[0].y;
383
384 for point in &points[1..] {
385 x_min = x_min.min(point.x);
386 y_min = y_min.min(point.y);
387 x_max = x_max.max(point.x);
388 y_max = y_max.max(point.y);
389 }
390
391 Self::new(x_min, y_min, x_max, y_max)
392 }
393}
394
395#[derive(Debug, Clone, PartialEq)]
397pub struct MultiPartShape {
398 pub bbox: Box2D,
400 pub num_parts: i32,
402 pub num_points: i32,
404 pub parts: Vec<i32>,
406 pub points: Vec<Point>,
408}
409
410impl MultiPartShape {
411 pub fn new(parts: Vec<i32>, points: Vec<Point>) -> Result<Self> {
413 if parts.is_empty() {
414 return Err(ShapefileError::invalid_geometry("parts cannot be empty"));
415 }
416 if points.is_empty() {
417 return Err(ShapefileError::invalid_geometry("points cannot be empty"));
418 }
419
420 let bbox = Box2D::from_points(&points)?;
421
422 Ok(Self {
423 bbox,
424 num_parts: parts.len() as i32,
425 num_points: points.len() as i32,
426 parts,
427 points,
428 })
429 }
430
431 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
433 let bbox = Box2D::read(reader)?;
434
435 let num_parts = reader
436 .read_i32::<LittleEndian>()
437 .map_err(|_| ShapefileError::unexpected_eof("reading num_parts"))?;
438 let num_points = reader
439 .read_i32::<LittleEndian>()
440 .map_err(|_| ShapefileError::unexpected_eof("reading num_points"))?;
441
442 if !(0..=1_000_000).contains(&num_parts) {
443 return Err(ShapefileError::limit_exceeded(
444 "num_parts out of range",
445 1_000_000,
446 num_parts as usize,
447 ));
448 }
449
450 if !(0..=100_000_000).contains(&num_points) {
451 return Err(ShapefileError::limit_exceeded(
452 "num_points out of range",
453 100_000_000,
454 num_points as usize,
455 ));
456 }
457
458 let mut parts = Vec::with_capacity(num_parts as usize);
459 for _ in 0..num_parts {
460 let part = reader
461 .read_i32::<LittleEndian>()
462 .map_err(|_| ShapefileError::unexpected_eof("reading part index"))?;
463 parts.push(part);
464 }
465
466 let mut points = Vec::with_capacity(num_points as usize);
467 for _ in 0..num_points {
468 let point = Point::read(reader)?;
469 points.push(point);
470 }
471
472 Ok(Self {
473 bbox,
474 num_parts,
475 num_points,
476 parts,
477 points,
478 })
479 }
480
481 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
483 self.bbox.write(writer)?;
484
485 writer
486 .write_i32::<LittleEndian>(self.num_parts)
487 .map_err(ShapefileError::Io)?;
488 writer
489 .write_i32::<LittleEndian>(self.num_points)
490 .map_err(ShapefileError::Io)?;
491
492 for part in &self.parts {
493 writer
494 .write_i32::<LittleEndian>(*part)
495 .map_err(ShapefileError::Io)?;
496 }
497
498 for point in &self.points {
499 point.write(writer)?;
500 }
501
502 Ok(())
503 }
504}
505
506#[derive(Debug, Clone, PartialEq)]
519pub struct MultiPartShapeZ {
520 pub base: MultiPartShape,
522 pub z_range: (f64, f64),
524 pub z_values: Vec<f64>,
526 pub m_range: Option<(f64, f64)>,
528 pub m_values: Option<Vec<f64>>,
530}
531
532impl MultiPartShapeZ {
533 pub fn new(
535 parts: Vec<i32>,
536 points: Vec<Point>,
537 z_values: Vec<f64>,
538 m_values: Option<Vec<f64>>,
539 ) -> Result<Self> {
540 if z_values.len() != points.len() {
541 return Err(ShapefileError::invalid_geometry(format!(
542 "z_values length ({}) must match points length ({})",
543 z_values.len(),
544 points.len()
545 )));
546 }
547 if let Some(ref mv) = m_values {
548 if mv.len() != points.len() {
549 return Err(ShapefileError::invalid_geometry(format!(
550 "m_values length ({}) must match points length ({})",
551 mv.len(),
552 points.len()
553 )));
554 }
555 }
556
557 let base = MultiPartShape::new(parts, points)?;
558
559 let z_min = z_values.iter().copied().fold(f64::INFINITY, f64::min);
560 let z_max = z_values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
561
562 let m_range = m_values.as_ref().map(|mv| {
563 let m_min = mv.iter().copied().fold(f64::INFINITY, f64::min);
564 let m_max = mv.iter().copied().fold(f64::NEG_INFINITY, f64::max);
565 (m_min, m_max)
566 });
567
568 Ok(Self {
569 base,
570 z_range: (z_min, z_max),
571 z_values,
572 m_range,
573 m_values,
574 })
575 }
576
577 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
579 let base = MultiPartShape::read(reader)?;
581
582 let z_min = reader
584 .read_f64::<LittleEndian>()
585 .map_err(|_| ShapefileError::unexpected_eof("reading z range min"))?;
586 let z_max = reader
587 .read_f64::<LittleEndian>()
588 .map_err(|_| ShapefileError::unexpected_eof("reading z range max"))?;
589
590 let num_points = base.num_points as usize;
592 let mut z_values = Vec::with_capacity(num_points);
593 for _ in 0..num_points {
594 let z = reader
595 .read_f64::<LittleEndian>()
596 .map_err(|_| ShapefileError::unexpected_eof("reading z value"))?;
597 z_values.push(z);
598 }
599
600 let (m_range, m_values) = match reader.read_f64::<LittleEndian>() {
602 Ok(m_min) => {
603 let m_max = reader
604 .read_f64::<LittleEndian>()
605 .map_err(|_| ShapefileError::unexpected_eof("reading m range max"))?;
606
607 let mut mv = Vec::with_capacity(num_points);
608 for _ in 0..num_points {
609 let m = reader
610 .read_f64::<LittleEndian>()
611 .map_err(|_| ShapefileError::unexpected_eof("reading m value"))?;
612 mv.push(m);
613 }
614
615 if m_min < -1e38 {
617 (None, None)
618 } else {
619 (Some((m_min, m_max)), Some(mv))
620 }
621 }
622 Err(_) => (None, None),
623 };
624
625 Ok(Self {
626 base,
627 z_range: (z_min, z_max),
628 z_values,
629 m_range,
630 m_values,
631 })
632 }
633
634 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
636 self.base.write(writer)?;
638
639 writer
641 .write_f64::<LittleEndian>(self.z_range.0)
642 .map_err(ShapefileError::Io)?;
643 writer
644 .write_f64::<LittleEndian>(self.z_range.1)
645 .map_err(ShapefileError::Io)?;
646
647 for z in &self.z_values {
649 writer
650 .write_f64::<LittleEndian>(*z)
651 .map_err(ShapefileError::Io)?;
652 }
653
654 if let (Some((m_min, m_max)), Some(m_values)) = (self.m_range, &self.m_values) {
656 writer
657 .write_f64::<LittleEndian>(m_min)
658 .map_err(ShapefileError::Io)?;
659 writer
660 .write_f64::<LittleEndian>(m_max)
661 .map_err(ShapefileError::Io)?;
662 for m in m_values {
663 writer
664 .write_f64::<LittleEndian>(*m)
665 .map_err(ShapefileError::Io)?;
666 }
667 }
668
669 Ok(())
670 }
671
672 pub fn content_length_words(&self) -> i32 {
674 let base_bytes = 32 + 4 + 4 + (self.base.num_parts * 4) + (self.base.num_points * 16);
676 let z_bytes = 16 + (self.base.num_points * 8);
678 let m_bytes = if self.m_values.is_some() {
680 16 + (self.base.num_points * 8)
681 } else {
682 0
683 };
684 (base_bytes + z_bytes + m_bytes) / 2
685 }
686}
687
688#[derive(Debug, Clone, PartialEq)]
699pub struct MultiPartShapeM {
700 pub base: MultiPartShape,
702 pub m_range: (f64, f64),
704 pub m_values: Vec<f64>,
706}
707
708impl MultiPartShapeM {
709 pub fn new(parts: Vec<i32>, points: Vec<Point>, m_values: Vec<f64>) -> Result<Self> {
711 if m_values.len() != points.len() {
712 return Err(ShapefileError::invalid_geometry(format!(
713 "m_values length ({}) must match points length ({})",
714 m_values.len(),
715 points.len()
716 )));
717 }
718
719 let base = MultiPartShape::new(parts, points)?;
720
721 let m_min = m_values.iter().copied().fold(f64::INFINITY, f64::min);
722 let m_max = m_values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
723
724 Ok(Self {
725 base,
726 m_range: (m_min, m_max),
727 m_values,
728 })
729 }
730
731 pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
733 let base = MultiPartShape::read(reader)?;
735
736 let m_min = reader
738 .read_f64::<LittleEndian>()
739 .map_err(|_| ShapefileError::unexpected_eof("reading m range min"))?;
740 let m_max = reader
741 .read_f64::<LittleEndian>()
742 .map_err(|_| ShapefileError::unexpected_eof("reading m range max"))?;
743
744 let num_points = base.num_points as usize;
746 let mut m_values = Vec::with_capacity(num_points);
747 for _ in 0..num_points {
748 let m = reader
749 .read_f64::<LittleEndian>()
750 .map_err(|_| ShapefileError::unexpected_eof("reading m value"))?;
751 m_values.push(m);
752 }
753
754 Ok(Self {
755 base,
756 m_range: (m_min, m_max),
757 m_values,
758 })
759 }
760
761 pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
763 self.base.write(writer)?;
765
766 writer
768 .write_f64::<LittleEndian>(self.m_range.0)
769 .map_err(ShapefileError::Io)?;
770 writer
771 .write_f64::<LittleEndian>(self.m_range.1)
772 .map_err(ShapefileError::Io)?;
773
774 for m in &self.m_values {
776 writer
777 .write_f64::<LittleEndian>(*m)
778 .map_err(ShapefileError::Io)?;
779 }
780
781 Ok(())
782 }
783
784 pub fn content_length_words(&self) -> i32 {
786 let base_bytes = 32 + 4 + 4 + (self.base.num_parts * 4) + (self.base.num_points * 16);
788 let m_bytes = 16 + (self.base.num_points * 8);
790 (base_bytes + m_bytes) / 2
791 }
792}
793
794#[cfg(test)]
795mod tests {
796 use super::*;
797 use std::io::Cursor;
798
799 #[test]
800 fn test_shape_type_conversion() {
801 assert_eq!(
802 ShapeType::from_code(1).expect("valid shape type code 1"),
803 ShapeType::Point
804 );
805 assert_eq!(ShapeType::Point.to_code(), 1);
806 assert_eq!(
807 ShapeType::from_code(11).expect("valid shape type code 11"),
808 ShapeType::PointZ
809 );
810 assert!(ShapeType::from_code(999).is_err());
811 }
812
813 #[test]
814 fn test_shape_type_properties() {
815 assert!(ShapeType::PointZ.has_z());
816 assert!(!ShapeType::Point.has_z());
817 assert!(ShapeType::PointZ.has_m());
818 assert!(ShapeType::PointM.has_m());
819 assert!(!ShapeType::Point.has_m());
820 }
821
822 #[test]
823 fn test_point_round_trip() {
824 let point = Point::new(10.5, 20.3);
825 let mut buffer = Vec::new();
826 point.write(&mut buffer).expect("write point");
827
828 let mut cursor = Cursor::new(buffer);
829 let read_point = Point::read(&mut cursor).expect("read point");
830
831 assert_eq!(read_point, point);
832 }
833
834 #[test]
835 fn test_pointz_round_trip() {
836 let point = PointZ::new_with_m(10.5, 20.3, 30.7, 100.0);
837 let mut buffer = Vec::new();
838 point.write(&mut buffer).expect("write pointz");
839
840 let mut cursor = Cursor::new(buffer);
841 let read_point = PointZ::read(&mut cursor).expect("read pointz");
842
843 assert_eq!(read_point, point);
844 }
845
846 #[test]
847 fn test_box2d_from_points() {
848 let points = vec![
849 Point::new(0.0, 0.0),
850 Point::new(10.0, 20.0),
851 Point::new(-5.0, 15.0),
852 ];
853
854 let bbox = Box2D::from_points(&points).expect("compute bbox from points");
855 assert_eq!(bbox.x_min, -5.0);
856 assert_eq!(bbox.y_min, 0.0);
857 assert_eq!(bbox.x_max, 10.0);
858 assert_eq!(bbox.y_max, 20.0);
859 }
860
861 #[test]
862 fn test_invalid_coordinates() {
863 let mut buffer = Vec::new();
864 buffer
865 .write_f64::<LittleEndian>(f64::NAN)
866 .expect("write NAN coordinate");
867 buffer
868 .write_f64::<LittleEndian>(10.0)
869 .expect("write valid coordinate");
870
871 let mut cursor = Cursor::new(buffer);
872 let result = Point::read(&mut cursor);
873 assert!(result.is_err());
874 }
875}