Skip to main content

oxigdal_core/vector/
geometry.rs

1//! Geometry types for vector data
2//!
3//! This module provides geometry types compatible with Simple Features specification.
4
5use crate::error::{OxiGdalError, Result};
6use core::fmt;
7use serde::{Deserialize, Serialize};
8
9#[cfg(feature = "std")]
10use std::vec::Vec;
11
12#[cfg(all(not(feature = "std"), feature = "alloc"))]
13use alloc::vec::Vec;
14
15/// Coordinate in 2D, 3D, or 4D space
16#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
17pub struct Coordinate {
18    /// X coordinate (longitude)
19    pub x: f64,
20    /// Y coordinate (latitude)
21    pub y: f64,
22    /// Z coordinate (elevation) - optional
23    pub z: Option<f64>,
24    /// M coordinate (measure) - optional
25    pub m: Option<f64>,
26}
27
28impl Coordinate {
29    /// Creates a new 2D coordinate
30    #[must_use]
31    pub const fn new_2d(x: f64, y: f64) -> Self {
32        Self {
33            x,
34            y,
35            z: None,
36            m: None,
37        }
38    }
39
40    /// Creates a new 3D coordinate
41    #[must_use]
42    pub const fn new_3d(x: f64, y: f64, z: f64) -> Self {
43        Self {
44            x,
45            y,
46            z: Some(z),
47            m: None,
48        }
49    }
50
51    /// Creates a new coordinate with measure
52    #[must_use]
53    pub const fn new_2dm(x: f64, y: f64, m: f64) -> Self {
54        Self {
55            x,
56            y,
57            z: None,
58            m: Some(m),
59        }
60    }
61
62    /// Creates a new 3D coordinate with measure
63    #[must_use]
64    pub const fn new_3dm(x: f64, y: f64, z: f64, m: f64) -> Self {
65        Self {
66            x,
67            y,
68            z: Some(z),
69            m: Some(m),
70        }
71    }
72
73    /// Returns true if this coordinate has Z dimension
74    #[must_use]
75    pub const fn has_z(&self) -> bool {
76        self.z.is_some()
77    }
78
79    /// Returns true if this coordinate has M dimension
80    #[must_use]
81    pub const fn has_m(&self) -> bool {
82        self.m.is_some()
83    }
84
85    /// Returns the number of dimensions (2, 3, or 4)
86    #[must_use]
87    pub const fn dimensions(&self) -> u8 {
88        let mut dims = 2;
89        if self.z.is_some() {
90            dims += 1;
91        }
92        if self.m.is_some() {
93            dims += 1;
94        }
95        dims
96    }
97
98    /// Returns the X coordinate
99    #[must_use]
100    pub const fn x(&self) -> f64 {
101        self.x
102    }
103
104    /// Returns the Y coordinate
105    #[must_use]
106    pub const fn y(&self) -> f64 {
107        self.y
108    }
109
110    /// Returns the Z coordinate if present
111    #[must_use]
112    pub const fn z(&self) -> Option<f64> {
113        self.z
114    }
115
116    /// Returns the M coordinate if present
117    #[must_use]
118    pub const fn m(&self) -> Option<f64> {
119        self.m
120    }
121}
122
123/// Geometry type enumeration
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub enum Geometry {
126    /// Point geometry
127    Point(Point),
128    /// `LineString` geometry
129    LineString(LineString),
130    /// Polygon geometry
131    Polygon(Polygon),
132    /// `MultiPoint` geometry
133    MultiPoint(MultiPoint),
134    /// `MultiLineString` geometry
135    MultiLineString(MultiLineString),
136    /// `MultiPolygon` geometry
137    MultiPolygon(MultiPolygon),
138    /// `GeometryCollection`
139    GeometryCollection(GeometryCollection),
140}
141
142impl Geometry {
143    /// Returns true if any coordinate in this geometry has a Z value.
144    #[must_use]
145    pub fn has_z(&self) -> bool {
146        match self {
147            Self::Point(p) => p.coord.has_z(),
148            Self::LineString(ls) => ls.coords.iter().any(|c| c.has_z()),
149            Self::Polygon(poly) => {
150                poly.exterior.coords.iter().any(|c| c.has_z())
151                    || poly
152                        .interiors
153                        .iter()
154                        .any(|r| r.coords.iter().any(|c| c.has_z()))
155            }
156            Self::MultiPoint(mp) => mp.points.iter().any(|p| p.coord.has_z()),
157            Self::MultiLineString(mls) => mls
158                .line_strings
159                .iter()
160                .any(|ls| ls.coords.iter().any(|c| c.has_z())),
161            Self::MultiPolygon(mpoly) => mpoly.polygons.iter().any(|poly| {
162                poly.exterior.coords.iter().any(|c| c.has_z())
163                    || poly
164                        .interiors
165                        .iter()
166                        .any(|r| r.coords.iter().any(|c| c.has_z()))
167            }),
168            Self::GeometryCollection(gc) => gc.geometries.iter().any(|g| g.has_z()),
169        }
170    }
171
172    /// Returns true if any coordinate in this geometry has an M (measure) value.
173    #[must_use]
174    pub fn has_m(&self) -> bool {
175        match self {
176            Self::Point(p) => p.coord.has_m(),
177            Self::LineString(ls) => ls.coords.iter().any(|c| c.has_m()),
178            Self::Polygon(poly) => {
179                poly.exterior.coords.iter().any(|c| c.has_m())
180                    || poly
181                        .interiors
182                        .iter()
183                        .any(|r| r.coords.iter().any(|c| c.has_m()))
184            }
185            Self::MultiPoint(mp) => mp.points.iter().any(|p| p.coord.has_m()),
186            Self::MultiLineString(mls) => mls
187                .line_strings
188                .iter()
189                .any(|ls| ls.coords.iter().any(|c| c.has_m())),
190            Self::MultiPolygon(mpoly) => mpoly.polygons.iter().any(|poly| {
191                poly.exterior.coords.iter().any(|c| c.has_m())
192                    || poly
193                        .interiors
194                        .iter()
195                        .any(|r| r.coords.iter().any(|c| c.has_m()))
196            }),
197            Self::GeometryCollection(gc) => gc.geometries.iter().any(|g| g.has_m()),
198        }
199    }
200
201    /// Collects all Z values from the geometry's coordinates in order.
202    ///
203    /// Returns `None` if this geometry has no Z coordinates.
204    /// For coordinates without Z, substitutes `0.0`.
205    #[must_use]
206    pub fn z_values(&self) -> Option<Vec<f64>> {
207        if !self.has_z() {
208            return None;
209        }
210        let mut out = Vec::new();
211        Self::collect_z_from(self, &mut out);
212        Some(out)
213    }
214
215    /// Collects all M values from the geometry's coordinates in order.
216    ///
217    /// Returns `None` if this geometry has no M coordinates.
218    /// For coordinates without M, substitutes `0.0`.
219    #[must_use]
220    pub fn m_values(&self) -> Option<Vec<f64>> {
221        if !self.has_m() {
222            return None;
223        }
224        let mut out = Vec::new();
225        Self::collect_m_from(self, &mut out);
226        Some(out)
227    }
228
229    fn collect_z_from(geom: &Geometry, out: &mut Vec<f64>) {
230        match geom {
231            Self::Point(p) => out.push(p.coord.z.unwrap_or(0.0)),
232            Self::LineString(ls) => {
233                for c in &ls.coords {
234                    out.push(c.z.unwrap_or(0.0));
235                }
236            }
237            Self::Polygon(poly) => {
238                for c in &poly.exterior.coords {
239                    out.push(c.z.unwrap_or(0.0));
240                }
241                for ring in &poly.interiors {
242                    for c in &ring.coords {
243                        out.push(c.z.unwrap_or(0.0));
244                    }
245                }
246            }
247            Self::MultiPoint(mp) => {
248                for p in &mp.points {
249                    out.push(p.coord.z.unwrap_or(0.0));
250                }
251            }
252            Self::MultiLineString(mls) => {
253                for ls in &mls.line_strings {
254                    for c in &ls.coords {
255                        out.push(c.z.unwrap_or(0.0));
256                    }
257                }
258            }
259            Self::MultiPolygon(mpoly) => {
260                for poly in &mpoly.polygons {
261                    for c in &poly.exterior.coords {
262                        out.push(c.z.unwrap_or(0.0));
263                    }
264                    for ring in &poly.interiors {
265                        for c in &ring.coords {
266                            out.push(c.z.unwrap_or(0.0));
267                        }
268                    }
269                }
270            }
271            Self::GeometryCollection(gc) => {
272                for g in &gc.geometries {
273                    Self::collect_z_from(g, out);
274                }
275            }
276        }
277    }
278
279    fn collect_m_from(geom: &Geometry, out: &mut Vec<f64>) {
280        match geom {
281            Self::Point(p) => out.push(p.coord.m.unwrap_or(0.0)),
282            Self::LineString(ls) => {
283                for c in &ls.coords {
284                    out.push(c.m.unwrap_or(0.0));
285                }
286            }
287            Self::Polygon(poly) => {
288                for c in &poly.exterior.coords {
289                    out.push(c.m.unwrap_or(0.0));
290                }
291                for ring in &poly.interiors {
292                    for c in &ring.coords {
293                        out.push(c.m.unwrap_or(0.0));
294                    }
295                }
296            }
297            Self::MultiPoint(mp) => {
298                for p in &mp.points {
299                    out.push(p.coord.m.unwrap_or(0.0));
300                }
301            }
302            Self::MultiLineString(mls) => {
303                for ls in &mls.line_strings {
304                    for c in &ls.coords {
305                        out.push(c.m.unwrap_or(0.0));
306                    }
307                }
308            }
309            Self::MultiPolygon(mpoly) => {
310                for poly in &mpoly.polygons {
311                    for c in &poly.exterior.coords {
312                        out.push(c.m.unwrap_or(0.0));
313                    }
314                    for ring in &poly.interiors {
315                        for c in &ring.coords {
316                            out.push(c.m.unwrap_or(0.0));
317                        }
318                    }
319                }
320            }
321            Self::GeometryCollection(gc) => {
322                for g in &gc.geometries {
323                    Self::collect_m_from(g, out);
324                }
325            }
326        }
327    }
328
329    /// Returns the geometry type as a string
330    #[must_use]
331    pub const fn geometry_type(&self) -> &'static str {
332        match self {
333            Self::Point(_) => "Point",
334            Self::LineString(_) => "LineString",
335            Self::Polygon(_) => "Polygon",
336            Self::MultiPoint(_) => "MultiPoint",
337            Self::MultiLineString(_) => "MultiLineString",
338            Self::MultiPolygon(_) => "MultiPolygon",
339            Self::GeometryCollection(_) => "GeometryCollection",
340        }
341    }
342
343    /// Returns true if the geometry is empty
344    #[must_use]
345    pub fn is_empty(&self) -> bool {
346        match self {
347            Self::Point(p) => p.coord.x.is_nan() || p.coord.y.is_nan(),
348            Self::LineString(ls) => ls.coords.is_empty(),
349            Self::Polygon(p) => p.exterior.coords.is_empty(),
350            Self::MultiPoint(mp) => mp.points.is_empty(),
351            Self::MultiLineString(mls) => mls.line_strings.is_empty(),
352            Self::MultiPolygon(mp) => mp.polygons.is_empty(),
353            Self::GeometryCollection(gc) => gc.geometries.is_empty(),
354        }
355    }
356
357    /// Computes the bounding box of the geometry
358    #[must_use]
359    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
360        match self {
361            Self::Point(p) => {
362                if p.coord.x.is_nan() || p.coord.y.is_nan() {
363                    None
364                } else {
365                    Some((p.coord.x, p.coord.y, p.coord.x, p.coord.y))
366                }
367            }
368            Self::LineString(ls) => ls.bounds(),
369            Self::Polygon(p) => p.bounds(),
370            Self::MultiPoint(mp) => mp.bounds(),
371            Self::MultiLineString(mls) => mls.bounds(),
372            Self::MultiPolygon(mp) => mp.bounds(),
373            Self::GeometryCollection(gc) => gc.bounds(),
374        }
375    }
376}
377
378/// Point geometry
379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
380pub struct Point {
381    /// Coordinate of the point
382    pub coord: Coordinate,
383}
384
385impl Point {
386    /// Creates a new 2D point
387    #[must_use]
388    pub const fn new(x: f64, y: f64) -> Self {
389        Self {
390            coord: Coordinate::new_2d(x, y),
391        }
392    }
393
394    /// Creates a new 3D point
395    #[must_use]
396    pub const fn new_3d(x: f64, y: f64, z: f64) -> Self {
397        Self {
398            coord: Coordinate::new_3d(x, y, z),
399        }
400    }
401
402    /// Creates a point from a coordinate
403    #[must_use]
404    pub const fn from_coord(coord: Coordinate) -> Self {
405        Self { coord }
406    }
407
408    /// Returns the X coordinate
409    #[must_use]
410    pub const fn x(&self) -> f64 {
411        self.coord.x
412    }
413
414    /// Returns the Y coordinate
415    #[must_use]
416    pub const fn y(&self) -> f64 {
417        self.coord.y
418    }
419
420    /// Returns the Z coordinate if present
421    #[must_use]
422    pub const fn z(&self) -> Option<f64> {
423        self.coord.z
424    }
425}
426
427/// `LineString` geometry
428#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
429pub struct LineString {
430    /// Coordinates of the line string
431    pub coords: Vec<Coordinate>,
432}
433
434impl LineString {
435    /// Creates a new line string
436    pub fn new(coords: Vec<Coordinate>) -> Result<Self> {
437        if coords.len() < 2 {
438            return Err(OxiGdalError::InvalidParameter {
439                parameter: "coords",
440                message: "LineString must have at least 2 coordinates".to_string(),
441            });
442        }
443        Ok(Self { coords })
444    }
445
446    /// Creates a new empty line string (for building)
447    #[must_use]
448    pub const fn empty() -> Self {
449        Self { coords: Vec::new() }
450    }
451
452    /// Adds a coordinate to the line string
453    pub fn push(&mut self, coord: Coordinate) {
454        self.coords.push(coord);
455    }
456
457    /// Returns the number of coordinates
458    #[must_use]
459    pub fn len(&self) -> usize {
460        self.coords.len()
461    }
462
463    /// Returns true if the line string is empty
464    #[must_use]
465    pub fn is_empty(&self) -> bool {
466        self.coords.is_empty()
467    }
468
469    /// Computes the bounding box
470    #[must_use]
471    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
472        if self.coords.is_empty() {
473            return None;
474        }
475
476        let mut min_x = f64::INFINITY;
477        let mut min_y = f64::INFINITY;
478        let mut max_x = f64::NEG_INFINITY;
479        let mut max_y = f64::NEG_INFINITY;
480
481        for coord in &self.coords {
482            min_x = min_x.min(coord.x);
483            min_y = min_y.min(coord.y);
484            max_x = max_x.max(coord.x);
485            max_y = max_y.max(coord.y);
486        }
487
488        Some((min_x, min_y, max_x, max_y))
489    }
490
491    /// Returns a reference to the coordinates as a slice
492    #[must_use]
493    pub fn coords(&self) -> &[Coordinate] {
494        &self.coords
495    }
496
497    /// Returns an iterator over the coordinates
498    pub fn points(&self) -> impl Iterator<Item = &Coordinate> {
499        self.coords.iter()
500    }
501}
502
503/// Polygon geometry
504#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
505pub struct Polygon {
506    /// Exterior ring
507    pub exterior: LineString,
508    /// Interior rings (holes)
509    pub interiors: Vec<LineString>,
510}
511
512impl Polygon {
513    /// Creates a new polygon
514    pub fn new(exterior: LineString, interiors: Vec<LineString>) -> Result<Self> {
515        if exterior.coords.len() < 4 {
516            return Err(OxiGdalError::InvalidParameter {
517                parameter: "exterior",
518                message: "Polygon exterior ring must have at least 4 coordinates".to_string(),
519            });
520        }
521
522        // Check if ring is closed
523        let first = &exterior.coords[0];
524        let last = &exterior.coords[exterior.coords.len() - 1];
525        if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
526            return Err(OxiGdalError::InvalidParameter {
527                parameter: "exterior",
528                message: "Polygon exterior ring must be closed".to_string(),
529            });
530        }
531
532        // Validate interior rings
533        for interior in &interiors {
534            if interior.coords.len() < 4 {
535                return Err(OxiGdalError::InvalidParameter {
536                    parameter: "interiors",
537                    message: "Polygon interior ring must have at least 4 coordinates".to_string(),
538                });
539            }
540
541            let first = &interior.coords[0];
542            let last = &interior.coords[interior.coords.len() - 1];
543            if (first.x - last.x).abs() > f64::EPSILON || (first.y - last.y).abs() > f64::EPSILON {
544                return Err(OxiGdalError::InvalidParameter {
545                    parameter: "interiors",
546                    message: "Polygon interior ring must be closed".to_string(),
547                });
548            }
549        }
550
551        Ok(Self {
552            exterior,
553            interiors,
554        })
555    }
556
557    /// Creates a new empty polygon
558    #[must_use]
559    pub const fn empty() -> Self {
560        Self {
561            exterior: LineString::empty(),
562            interiors: Vec::new(),
563        }
564    }
565
566    /// Computes the bounding box
567    #[must_use]
568    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
569        self.exterior.bounds()
570    }
571
572    /// Returns a reference to the exterior ring
573    #[must_use]
574    pub fn exterior(&self) -> &LineString {
575        &self.exterior
576    }
577
578    /// Returns a reference to the interior rings (holes)
579    #[must_use]
580    pub fn interiors(&self) -> &[LineString] {
581        &self.interiors
582    }
583}
584
585/// `MultiPoint` geometry
586#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
587pub struct MultiPoint {
588    /// Points in the collection
589    pub points: Vec<Point>,
590}
591
592impl MultiPoint {
593    /// Creates a new multi-point
594    #[must_use]
595    pub const fn new(points: Vec<Point>) -> Self {
596        Self { points }
597    }
598
599    /// Creates an empty multi-point
600    #[must_use]
601    pub const fn empty() -> Self {
602        Self { points: Vec::new() }
603    }
604
605    /// Adds a point
606    pub fn push(&mut self, point: Point) {
607        self.points.push(point);
608    }
609
610    /// Computes the bounding box
611    #[must_use]
612    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
613        if self.points.is_empty() {
614            return None;
615        }
616
617        let mut min_x = f64::INFINITY;
618        let mut min_y = f64::INFINITY;
619        let mut max_x = f64::NEG_INFINITY;
620        let mut max_y = f64::NEG_INFINITY;
621
622        for point in &self.points {
623            min_x = min_x.min(point.coord.x);
624            min_y = min_y.min(point.coord.y);
625            max_x = max_x.max(point.coord.x);
626            max_y = max_y.max(point.coord.y);
627        }
628
629        Some((min_x, min_y, max_x, max_y))
630    }
631}
632
633/// `MultiLineString` geometry
634#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
635pub struct MultiLineString {
636    /// Line strings in the collection
637    pub line_strings: Vec<LineString>,
638}
639
640impl MultiLineString {
641    /// Creates a new multi-line-string
642    #[must_use]
643    pub const fn new(line_strings: Vec<LineString>) -> Self {
644        Self { line_strings }
645    }
646
647    /// Creates an empty multi-line-string
648    #[must_use]
649    pub const fn empty() -> Self {
650        Self {
651            line_strings: Vec::new(),
652        }
653    }
654
655    /// Adds a line string
656    pub fn push(&mut self, line_string: LineString) {
657        self.line_strings.push(line_string);
658    }
659
660    /// Computes the bounding box
661    #[must_use]
662    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
663        if self.line_strings.is_empty() {
664            return None;
665        }
666
667        let mut min_x = f64::INFINITY;
668        let mut min_y = f64::INFINITY;
669        let mut max_x = f64::NEG_INFINITY;
670        let mut max_y = f64::NEG_INFINITY;
671
672        for ls in &self.line_strings {
673            if let Some((x_min, y_min, x_max, y_max)) = ls.bounds() {
674                min_x = min_x.min(x_min);
675                min_y = min_y.min(y_min);
676                max_x = max_x.max(x_max);
677                max_y = max_y.max(y_max);
678            }
679        }
680
681        if min_x.is_infinite() {
682            None
683        } else {
684            Some((min_x, min_y, max_x, max_y))
685        }
686    }
687}
688
689/// `MultiPolygon` geometry
690#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
691pub struct MultiPolygon {
692    /// Polygons in the collection
693    pub polygons: Vec<Polygon>,
694}
695
696impl MultiPolygon {
697    /// Creates a new multi-polygon
698    #[must_use]
699    pub const fn new(polygons: Vec<Polygon>) -> Self {
700        Self { polygons }
701    }
702
703    /// Creates an empty multi-polygon
704    #[must_use]
705    pub const fn empty() -> Self {
706        Self {
707            polygons: Vec::new(),
708        }
709    }
710
711    /// Adds a polygon
712    pub fn push(&mut self, polygon: Polygon) {
713        self.polygons.push(polygon);
714    }
715
716    /// Computes the bounding box
717    #[must_use]
718    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
719        if self.polygons.is_empty() {
720            return None;
721        }
722
723        let mut min_x = f64::INFINITY;
724        let mut min_y = f64::INFINITY;
725        let mut max_x = f64::NEG_INFINITY;
726        let mut max_y = f64::NEG_INFINITY;
727
728        for poly in &self.polygons {
729            if let Some((x_min, y_min, x_max, y_max)) = poly.bounds() {
730                min_x = min_x.min(x_min);
731                min_y = min_y.min(y_min);
732                max_x = max_x.max(x_max);
733                max_y = max_y.max(y_max);
734            }
735        }
736
737        if min_x.is_infinite() {
738            None
739        } else {
740            Some((min_x, min_y, max_x, max_y))
741        }
742    }
743}
744
745/// `GeometryCollection`
746#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
747pub struct GeometryCollection {
748    /// Geometries in the collection
749    pub geometries: Vec<Geometry>,
750}
751
752impl GeometryCollection {
753    /// Creates a new geometry collection
754    #[must_use]
755    pub const fn new(geometries: Vec<Geometry>) -> Self {
756        Self { geometries }
757    }
758
759    /// Creates an empty geometry collection
760    #[must_use]
761    pub const fn empty() -> Self {
762        Self {
763            geometries: Vec::new(),
764        }
765    }
766
767    /// Adds a geometry
768    pub fn push(&mut self, geometry: Geometry) {
769        self.geometries.push(geometry);
770    }
771
772    /// Computes the bounding box
773    #[must_use]
774    pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
775        if self.geometries.is_empty() {
776            return None;
777        }
778
779        let mut min_x = f64::INFINITY;
780        let mut min_y = f64::INFINITY;
781        let mut max_x = f64::NEG_INFINITY;
782        let mut max_y = f64::NEG_INFINITY;
783
784        for geom in &self.geometries {
785            if let Some((x_min, y_min, x_max, y_max)) = geom.bounds() {
786                min_x = min_x.min(x_min);
787                min_y = min_y.min(y_min);
788                max_x = max_x.max(x_max);
789                max_y = max_y.max(y_max);
790            }
791        }
792
793        if min_x.is_infinite() {
794            None
795        } else {
796            Some((min_x, min_y, max_x, max_y))
797        }
798    }
799}
800
801// ─── Display (WKT-style) ──────────────────────────────────────────────────────
802
803impl fmt::Display for Coordinate {
804    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805        write!(f, "{} {}", self.x, self.y)?;
806        if let Some(z) = self.z {
807            write!(f, " {z}")?;
808        }
809        Ok(())
810    }
811}
812
813impl fmt::Display for Point {
814    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815        write!(f, "POINT({})", self.coord)
816    }
817}
818
819/// Writes a ring's coordinate list inside parentheses: `(x1 y1, x2 y2, ...)`.
820fn fmt_ring(f: &mut fmt::Formatter<'_>, ring: &LineString) -> fmt::Result {
821    write!(f, "(")?;
822    for (i, c) in ring.coords.iter().enumerate() {
823        if i > 0 {
824            write!(f, ", ")?;
825        }
826        write!(f, "{c}")?;
827    }
828    write!(f, ")")
829}
830
831impl fmt::Display for LineString {
832    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
833        write!(f, "LINESTRING")?;
834        fmt_ring(f, self)
835    }
836}
837
838impl fmt::Display for Polygon {
839    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
840        write!(f, "POLYGON(")?;
841        fmt_ring(f, &self.exterior)?;
842        for interior in &self.interiors {
843            write!(f, ", ")?;
844            fmt_ring(f, interior)?;
845        }
846        write!(f, ")")
847    }
848}
849
850impl fmt::Display for MultiPoint {
851    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
852        write!(f, "MULTIPOINT(")?;
853        for (i, p) in self.points.iter().enumerate() {
854            if i > 0 {
855                write!(f, ", ")?;
856            }
857            write!(f, "({} {})", p.coord.x, p.coord.y)?;
858        }
859        write!(f, ")")
860    }
861}
862
863impl fmt::Display for MultiLineString {
864    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865        write!(f, "MULTILINESTRING(")?;
866        for (i, ls) in self.line_strings.iter().enumerate() {
867            if i > 0 {
868                write!(f, ", ")?;
869            }
870            fmt_ring(f, ls)?;
871        }
872        write!(f, ")")
873    }
874}
875
876impl fmt::Display for MultiPolygon {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        write!(f, "MULTIPOLYGON(")?;
879        for (i, poly) in self.polygons.iter().enumerate() {
880            if i > 0 {
881                write!(f, ", ")?;
882            }
883            write!(f, "(")?;
884            fmt_ring(f, &poly.exterior)?;
885            for interior in &poly.interiors {
886                write!(f, ", ")?;
887                fmt_ring(f, interior)?;
888            }
889            write!(f, ")")?;
890        }
891        write!(f, ")")
892    }
893}
894
895impl fmt::Display for Geometry {
896    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
897        match self {
898            Self::Point(p) => write!(f, "{p}"),
899            Self::LineString(ls) => write!(f, "{ls}"),
900            Self::Polygon(poly) => write!(f, "{poly}"),
901            Self::MultiPoint(mp) => write!(f, "{mp}"),
902            Self::MultiLineString(mls) => write!(f, "{mls}"),
903            Self::MultiPolygon(mpoly) => write!(f, "{mpoly}"),
904            Self::GeometryCollection(gc) => {
905                write!(f, "GEOMETRYCOLLECTION(")?;
906                for (i, g) in gc.geometries.iter().enumerate() {
907                    if i > 0 {
908                        write!(f, ", ")?;
909                    }
910                    write!(f, "{g}")?;
911                }
912                write!(f, ")")
913            }
914        }
915    }
916}
917
918// ─── OGC WKB serialization ────────────────────────────────────────────────────
919
920#[cfg(feature = "std")]
921mod wkb {
922    //! WKB (Well-Known Binary) serialization helpers.
923    //!
924    //! Uses little-endian byte order throughout (byte-order marker `0x01`).
925
926    use std::io::Write;
927
928    /// WKB geometry type codes (ISO 19125 / OGC 06-103r4 / SFSQL 1.2)
929    pub(crate) const WKB_POINT: u32 = 1;
930    pub(crate) const WKB_LINESTRING: u32 = 2;
931    pub(crate) const WKB_POLYGON: u32 = 3;
932    pub(crate) const WKB_MULTIPOINT: u32 = 4;
933    pub(crate) const WKB_MULTILINESTRING: u32 = 5;
934    pub(crate) const WKB_MULTIPOLYGON: u32 = 6;
935    pub(crate) const WKB_GEOMETRYCOLLECTION: u32 = 7;
936
937    /// ISO type code offset for geometries with Z coordinate.
938    pub(crate) const WKB_Z_OFFSET: u32 = 1000;
939
940    /// Writes the 5-byte WKB header: byte-order marker + type code.
941    pub(crate) fn write_header<W: Write>(w: &mut W, wkb_type: u32) -> std::io::Result<()> {
942        w.write_all(&[0x01])?; // little-endian
943        w.write_all(&wkb_type.to_le_bytes())
944    }
945
946    /// Writes a single `f64` value as 8 little-endian bytes.
947    pub(crate) fn write_f64<W: Write>(w: &mut W, v: f64) -> std::io::Result<()> {
948        w.write_all(&v.to_le_bytes())
949    }
950
951    /// Writes a single `u32` value as 4 little-endian bytes.
952    pub(crate) fn write_u32<W: Write>(w: &mut W, v: u32) -> std::io::Result<()> {
953        w.write_all(&v.to_le_bytes())
954    }
955}
956
957#[cfg(feature = "std")]
958impl Geometry {
959    /// Serializes this geometry to OGC WKB bytes (little-endian).
960    ///
961    /// The returned `Vec<u8>` contains a complete, self-describing WKB blob that
962    /// can be consumed by any OGC-compliant geospatial library.
963    ///
964    /// # Panics
965    ///
966    /// This method cannot panic in practice: writing to a `Vec<u8>` is infallible.
967    #[must_use]
968    pub fn to_wkb(&self) -> Vec<u8> {
969        let mut buf = Vec::new();
970        // Writing to Vec<u8> via std::io::Write is infallible.
971        if self.write_wkb(&mut buf).is_err() {
972            // SAFETY: Vec<u8> io::Write impl never returns Err.
973            unreachable!("Vec<u8> writes are infallible");
974        }
975        buf
976    }
977
978    /// Writes this geometry as OGC WKB (little-endian) to the given writer.
979    ///
980    /// # Errors
981    ///
982    /// Returns an `std::io::Error` only if the underlying writer fails.
983    /// Writing to an in-memory `Vec<u8>` will never produce an error.
984    pub fn write_wkb<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
985        match self {
986            Self::Point(p) => write_point_wkb(w, p),
987            Self::LineString(ls) => write_linestring_wkb(w, ls),
988            Self::Polygon(poly) => write_polygon_wkb(w, poly),
989            Self::MultiPoint(mp) => write_multipoint_wkb(w, mp),
990            Self::MultiLineString(mls) => write_multilinestring_wkb(w, mls),
991            Self::MultiPolygon(mpoly) => write_multipolygon_wkb(w, mpoly),
992            Self::GeometryCollection(gc) => write_geometrycollection_wkb(w, gc),
993        }
994    }
995}
996
997/// Writes a [`Point`] to a WKB writer.
998///
999/// Uses type code 1001 when a Z coordinate is present, otherwise 1.
1000#[cfg(feature = "std")]
1001fn write_point_wkb<W: std::io::Write>(w: &mut W, p: &Point) -> std::io::Result<()> {
1002    use wkb::{WKB_POINT, WKB_Z_OFFSET};
1003    let has_z = p.coord.z.is_some();
1004    let wkb_type = if has_z {
1005        WKB_POINT + WKB_Z_OFFSET
1006    } else {
1007        WKB_POINT
1008    };
1009    wkb::write_header(w, wkb_type)?;
1010    wkb::write_f64(w, p.coord.x)?;
1011    wkb::write_f64(w, p.coord.y)?;
1012    if let Some(z) = p.coord.z {
1013        wkb::write_f64(w, z)?;
1014    }
1015    Ok(())
1016}
1017
1018/// Writes a [`LineString`] to a WKB writer (2D only, type code 2).
1019#[cfg(feature = "std")]
1020fn write_linestring_wkb<W: std::io::Write>(w: &mut W, ls: &LineString) -> std::io::Result<()> {
1021    wkb::write_header(w, wkb::WKB_LINESTRING)?;
1022    let count = ls.coords.len() as u32;
1023    wkb::write_u32(w, count)?;
1024    for coord in &ls.coords {
1025        wkb::write_f64(w, coord.x)?;
1026        wkb::write_f64(w, coord.y)?;
1027    }
1028    Ok(())
1029}
1030
1031/// Writes a single linear ring (no WKB header) as count + coordinate pairs.
1032#[cfg(feature = "std")]
1033fn write_ring_wkb<W: std::io::Write>(w: &mut W, ring: &LineString) -> std::io::Result<()> {
1034    let count = ring.coords.len() as u32;
1035    wkb::write_u32(w, count)?;
1036    for coord in &ring.coords {
1037        wkb::write_f64(w, coord.x)?;
1038        wkb::write_f64(w, coord.y)?;
1039    }
1040    Ok(())
1041}
1042
1043/// Writes a [`Polygon`] to a WKB writer (type code 3).
1044///
1045/// Ring count = 1 (exterior) + number of interior rings.
1046#[cfg(feature = "std")]
1047fn write_polygon_wkb<W: std::io::Write>(w: &mut W, poly: &Polygon) -> std::io::Result<()> {
1048    wkb::write_header(w, wkb::WKB_POLYGON)?;
1049    let ring_count = 1u32 + poly.interiors.len() as u32;
1050    wkb::write_u32(w, ring_count)?;
1051    write_ring_wkb(w, &poly.exterior)?;
1052    for interior in &poly.interiors {
1053        write_ring_wkb(w, interior)?;
1054    }
1055    Ok(())
1056}
1057
1058/// Writes a [`MultiPoint`] to a WKB writer (type code 4).
1059///
1060/// Each member point is a complete WKB geometry (with its own header).
1061#[cfg(feature = "std")]
1062fn write_multipoint_wkb<W: std::io::Write>(w: &mut W, mp: &MultiPoint) -> std::io::Result<()> {
1063    wkb::write_header(w, wkb::WKB_MULTIPOINT)?;
1064    wkb::write_u32(w, mp.points.len() as u32)?;
1065    for point in &mp.points {
1066        write_point_wkb(w, point)?;
1067    }
1068    Ok(())
1069}
1070
1071/// Writes a [`MultiLineString`] to a WKB writer (type code 5).
1072///
1073/// Each member line string is a complete WKB geometry (with its own header).
1074#[cfg(feature = "std")]
1075fn write_multilinestring_wkb<W: std::io::Write>(
1076    w: &mut W,
1077    mls: &MultiLineString,
1078) -> std::io::Result<()> {
1079    wkb::write_header(w, wkb::WKB_MULTILINESTRING)?;
1080    wkb::write_u32(w, mls.line_strings.len() as u32)?;
1081    for ls in &mls.line_strings {
1082        write_linestring_wkb(w, ls)?;
1083    }
1084    Ok(())
1085}
1086
1087/// Writes a [`MultiPolygon`] to a WKB writer (type code 6).
1088///
1089/// Each member polygon is a complete WKB geometry (with its own header).
1090#[cfg(feature = "std")]
1091fn write_multipolygon_wkb<W: std::io::Write>(
1092    w: &mut W,
1093    mpoly: &MultiPolygon,
1094) -> std::io::Result<()> {
1095    wkb::write_header(w, wkb::WKB_MULTIPOLYGON)?;
1096    wkb::write_u32(w, mpoly.polygons.len() as u32)?;
1097    for poly in &mpoly.polygons {
1098        write_polygon_wkb(w, poly)?;
1099    }
1100    Ok(())
1101}
1102
1103/// Writes a [`GeometryCollection`] to a WKB writer (type code 7).
1104///
1105/// Each member geometry is a complete WKB geometry (with its own header).
1106#[cfg(feature = "std")]
1107fn write_geometrycollection_wkb<W: std::io::Write>(
1108    w: &mut W,
1109    gc: &GeometryCollection,
1110) -> std::io::Result<()> {
1111    wkb::write_header(w, wkb::WKB_GEOMETRYCOLLECTION)?;
1112    wkb::write_u32(w, gc.geometries.len() as u32)?;
1113    for geom in &gc.geometries {
1114        geom.write_wkb(w)?;
1115    }
1116    Ok(())
1117}
1118
1119// ─── end WKB ──────────────────────────────────────────────────────────────────
1120
1121#[cfg(test)]
1122mod tests {
1123    use super::*;
1124
1125    #[test]
1126    fn test_coordinate_2d() {
1127        let coord = Coordinate::new_2d(1.0, 2.0);
1128        assert_eq!(coord.x, 1.0);
1129        assert_eq!(coord.y, 2.0);
1130        assert!(!coord.has_z());
1131        assert!(!coord.has_m());
1132        assert_eq!(coord.dimensions(), 2);
1133    }
1134
1135    #[test]
1136    fn test_coordinate_3d() {
1137        let coord = Coordinate::new_3d(1.0, 2.0, 3.0);
1138        assert_eq!(coord.x, 1.0);
1139        assert_eq!(coord.y, 2.0);
1140        assert_eq!(coord.z, Some(3.0));
1141        assert!(coord.has_z());
1142        assert!(!coord.has_m());
1143        assert_eq!(coord.dimensions(), 3);
1144    }
1145
1146    #[test]
1147    fn test_point() {
1148        let point = Point::new(1.0, 2.0);
1149        assert_eq!(point.coord.x, 1.0);
1150        assert_eq!(point.coord.y, 2.0);
1151    }
1152
1153    #[test]
1154    fn test_linestring() {
1155        let coords = vec![
1156            Coordinate::new_2d(0.0, 0.0),
1157            Coordinate::new_2d(1.0, 1.0),
1158            Coordinate::new_2d(2.0, 0.0),
1159        ];
1160        let ls = LineString::new(coords).ok();
1161        assert!(ls.is_some());
1162        let ls = ls.expect("linestring creation failed");
1163        assert_eq!(ls.len(), 3);
1164        assert!(!ls.is_empty());
1165
1166        let bounds = ls.bounds();
1167        assert!(bounds.is_some());
1168        let (min_x, min_y, max_x, max_y) = bounds.expect("bounds calculation failed");
1169        assert_eq!(min_x, 0.0);
1170        assert_eq!(min_y, 0.0);
1171        assert_eq!(max_x, 2.0);
1172        assert_eq!(max_y, 1.0);
1173    }
1174
1175    #[test]
1176    fn test_linestring_invalid() {
1177        let coords = vec![Coordinate::new_2d(0.0, 0.0)];
1178        let result = LineString::new(coords);
1179        assert!(result.is_err());
1180    }
1181
1182    #[test]
1183    fn test_polygon() {
1184        let exterior_coords = vec![
1185            Coordinate::new_2d(0.0, 0.0),
1186            Coordinate::new_2d(1.0, 0.0),
1187            Coordinate::new_2d(1.0, 1.0),
1188            Coordinate::new_2d(0.0, 1.0),
1189            Coordinate::new_2d(0.0, 0.0),
1190        ];
1191        let exterior = LineString::new(exterior_coords).ok();
1192        assert!(exterior.is_some());
1193        let exterior = exterior.expect("linestring creation failed");
1194
1195        let poly = Polygon::new(exterior, vec![]);
1196        assert!(poly.is_ok());
1197    }
1198
1199    #[test]
1200    fn test_polygon_not_closed() {
1201        let exterior_coords = vec![
1202            Coordinate::new_2d(0.0, 0.0),
1203            Coordinate::new_2d(1.0, 0.0),
1204            Coordinate::new_2d(1.0, 1.0),
1205            Coordinate::new_2d(0.0, 1.0),
1206        ];
1207        let exterior = LineString::new(exterior_coords).ok();
1208        assert!(exterior.is_some());
1209        let exterior = exterior.expect("linestring creation failed");
1210
1211        let result = Polygon::new(exterior, vec![]);
1212        assert!(result.is_err());
1213    }
1214
1215    // ─── WKB tests ────────────────────────────────────────────────────────────
1216
1217    #[test]
1218    #[cfg(feature = "std")]
1219    fn test_wkb_point_2d() {
1220        // A 2D point: [01][01000000][8 bytes x][8 bytes y]
1221        let geom = Geometry::Point(Point::new(1.0_f64, 2.0_f64));
1222        let wkb = geom.to_wkb();
1223
1224        assert_eq!(wkb[0], 0x01, "byte-order marker");
1225        assert_eq!(&wkb[1..5], &1u32.to_le_bytes(), "wkb_type must be 1");
1226        assert_eq!(wkb.len(), 21, "1 + 4 + 8 + 8 = 21 bytes for 2D point");
1227
1228        let x = f64::from_le_bytes(wkb[5..13].try_into().expect("8 bytes"));
1229        let y = f64::from_le_bytes(wkb[13..21].try_into().expect("8 bytes"));
1230        assert_eq!(x, 1.0_f64);
1231        assert_eq!(y, 2.0_f64);
1232    }
1233
1234    #[test]
1235    #[cfg(feature = "std")]
1236    fn test_wkb_point_3d() {
1237        // A 3D point: [01][E9030000 = 1001 LE][8 bytes x][8 bytes y][8 bytes z]
1238        let geom = Geometry::Point(Point::new_3d(1.0_f64, 2.0_f64, 3.0_f64));
1239        let wkb = geom.to_wkb();
1240
1241        assert_eq!(wkb[0], 0x01);
1242        assert_eq!(&wkb[1..5], &1001u32.to_le_bytes(), "wkb_type must be 1001");
1243        assert_eq!(wkb.len(), 29, "1 + 4 + 8 + 8 + 8 = 29 bytes for 3D point");
1244
1245        let z = f64::from_le_bytes(wkb[21..29].try_into().expect("8 bytes"));
1246        assert_eq!(z, 3.0_f64);
1247    }
1248
1249    #[test]
1250    #[cfg(feature = "std")]
1251    fn test_wkb_write_equals_to_wkb() {
1252        let geom = Geometry::Point(Point::new(5.0_f64, 10.0_f64));
1253        let a = geom.to_wkb();
1254        let mut b = Vec::new();
1255        geom.write_wkb(&mut b).expect("Vec<u8> write cannot fail");
1256        assert_eq!(a, b);
1257    }
1258
1259    #[test]
1260    #[cfg(feature = "std")]
1261    fn test_wkb_linestring() {
1262        let coords = vec![
1263            Coordinate::new_2d(0.0, 0.0),
1264            Coordinate::new_2d(1.0, 1.0),
1265            Coordinate::new_2d(2.0, 0.0),
1266        ];
1267        let ls = LineString::new(coords).expect("valid linestring");
1268        let geom = Geometry::LineString(ls);
1269        let wkb = geom.to_wkb();
1270
1271        // Header: 1 byte marker + 4 byte type + 4 byte count + n * 16 bytes (2 f64)
1272        // = 1 + 4 + 4 + 3*16 = 57 bytes
1273        assert_eq!(wkb[0], 0x01);
1274        assert_eq!(&wkb[1..5], &2u32.to_le_bytes(), "wkb_type must be 2");
1275
1276        let count = u32::from_le_bytes(wkb[5..9].try_into().expect("4 bytes"));
1277        assert_eq!(count, 3, "three coordinates");
1278        assert_eq!(wkb.len(), 1 + 4 + 4 + 3 * 16, "total length");
1279    }
1280
1281    #[test]
1282    #[cfg(feature = "std")]
1283    fn test_wkb_polygon_simple() {
1284        let exterior_coords = vec![
1285            Coordinate::new_2d(0.0, 0.0),
1286            Coordinate::new_2d(1.0, 0.0),
1287            Coordinate::new_2d(1.0, 1.0),
1288            Coordinate::new_2d(0.0, 1.0),
1289            Coordinate::new_2d(0.0, 0.0),
1290        ];
1291        let exterior = LineString::new(exterior_coords).expect("valid exterior");
1292        let poly = Polygon::new(exterior, vec![]).expect("valid polygon");
1293        let geom = Geometry::Polygon(poly);
1294        let wkb = geom.to_wkb();
1295
1296        assert_eq!(wkb[0], 0x01);
1297        assert_eq!(&wkb[1..5], &3u32.to_le_bytes(), "wkb_type must be 3");
1298
1299        let ring_count = u32::from_le_bytes(wkb[5..9].try_into().expect("4 bytes"));
1300        assert_eq!(ring_count, 1, "one exterior ring");
1301        // ring: 4 byte point_count + 5 * 16 byte coordinates
1302        let point_count = u32::from_le_bytes(wkb[9..13].try_into().expect("4 bytes"));
1303        assert_eq!(point_count, 5);
1304    }
1305
1306    #[test]
1307    #[cfg(feature = "std")]
1308    fn test_wkb_geometry_collection() {
1309        let gc = GeometryCollection::new(vec![
1310            Geometry::Point(Point::new(0.0, 0.0)),
1311            Geometry::Point(Point::new(1.0, 1.0)),
1312        ]);
1313        let geom = Geometry::GeometryCollection(gc);
1314        let wkb = geom.to_wkb();
1315
1316        assert_eq!(wkb[0], 0x01);
1317        assert_eq!(&wkb[1..5], &7u32.to_le_bytes(), "wkb_type must be 7");
1318
1319        let member_count = u32::from_le_bytes(wkb[5..9].try_into().expect("4 bytes"));
1320        assert_eq!(member_count, 2, "two member geometries");
1321    }
1322
1323    #[test]
1324    #[cfg(feature = "std")]
1325    fn test_wkb_multipoint() {
1326        let mp = MultiPoint::new(vec![Point::new(0.0, 0.0), Point::new(3.0, 4.0)]);
1327        let geom = Geometry::MultiPoint(mp);
1328        let wkb = geom.to_wkb();
1329
1330        assert_eq!(&wkb[1..5], &4u32.to_le_bytes(), "wkb_type must be 4");
1331        let count = u32::from_le_bytes(wkb[5..9].try_into().expect("4 bytes"));
1332        assert_eq!(count, 2);
1333    }
1334
1335    #[test]
1336    fn test_display_geometry_point_wkt() {
1337        let p = Geometry::Point(Point::new(1.0, 2.0));
1338        assert_eq!(p.to_string(), "POINT(1 2)");
1339    }
1340
1341    #[test]
1342    fn test_display_geometry_point_3d() {
1343        let p = Geometry::Point(Point::new_3d(1.0, 2.0, 3.0));
1344        assert_eq!(p.to_string(), "POINT(1 2 3)");
1345    }
1346
1347    #[test]
1348    fn test_display_linestring() {
1349        let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(1.0, 1.0)];
1350        let ls = Geometry::LineString(LineString::new(coords).expect("valid"));
1351        assert_eq!(ls.to_string(), "LINESTRING(0 0, 1 1)");
1352    }
1353
1354    #[test]
1355    fn test_display_geometry_collection() {
1356        let gc = Geometry::GeometryCollection(GeometryCollection::new(vec![
1357            Geometry::Point(Point::new(0.0, 0.0)),
1358            Geometry::Point(Point::new(1.0, 1.0)),
1359        ]));
1360        assert_eq!(gc.to_string(), "GEOMETRYCOLLECTION(POINT(0 0), POINT(1 1))");
1361    }
1362}