Skip to main content

velr_types/
spatial.rs

1use crate::error::{DecodeError, EncodeError};
2use crate::tag;
3use serde_json::{Map, Number, Value};
4use std::fmt::Write;
5
6const EWKB_Z_FLAG: u32 = 0x8000_0000;
7const EWKB_M_FLAG: u32 = 0x4000_0000;
8const EWKB_SRID_FLAG: u32 = 0x2000_0000;
9
10const EWKB_POINT: u32 = 1;
11const EWKB_LINESTRING: u32 = 2;
12const EWKB_POLYGON: u32 = 3;
13const EWKB_MULTI_POINT: u32 = 4;
14const EWKB_MULTI_LINESTRING: u32 = 5;
15const EWKB_MULTI_POLYGON: u32 = 6;
16const EWKB_GEOMETRY_COLLECTION: u32 = 7;
17
18const DEFAULT_GEOJSON_SRID: u32 = 4326;
19
20#[derive(Debug, Copy, Clone, PartialEq)]
21pub enum Position {
22    Xy { x: f64, y: f64 },
23    Xyz { x: f64, y: f64, z: f64 },
24}
25
26impl Position {
27    pub fn dimension(&self) -> usize {
28        match self {
29            Self::Xy { .. } => 2,
30            Self::Xyz { .. } => 3,
31        }
32    }
33
34    pub fn x(&self) -> f64 {
35        match self {
36            Self::Xy { x, .. } | Self::Xyz { x, .. } => *x,
37        }
38    }
39
40    pub fn y(&self) -> f64 {
41        match self {
42            Self::Xy { y, .. } | Self::Xyz { y, .. } => *y,
43        }
44    }
45
46    pub fn z(&self) -> Option<f64> {
47        match self {
48            Self::Xy { .. } => None,
49            Self::Xyz { z, .. } => Some(*z),
50        }
51    }
52
53    fn to_geojson_value(&self) -> Result<Value, EncodeError> {
54        let mut out = vec![json_number(self.x())?, json_number(self.y())?];
55        if let Some(z) = self.z() {
56            out.push(json_number(z)?);
57        }
58        Ok(Value::Array(out))
59    }
60
61    fn from_geojson_value(value: &Value) -> Result<Self, DecodeError> {
62        let coords = value
63            .as_array()
64            .ok_or_else(|| DecodeError::InvalidSpatial("coordinate must be a JSON array".into()))?;
65        match coords.as_slice() {
66            [x, y] => Ok(Self::Xy {
67                x: json_f64(x)?,
68                y: json_f64(y)?,
69            }),
70            [x, y, z] => Ok(Self::Xyz {
71                x: json_f64(x)?,
72                y: json_f64(y)?,
73                z: json_f64(z)?,
74            }),
75            _ => Err(DecodeError::InvalidSpatial(
76                "coordinates must contain exactly 2 or 3 numbers".into(),
77            )),
78        }
79    }
80
81    fn to_wkt_coords(&self) -> Result<String, EncodeError> {
82        let mut out = String::new();
83        write!(
84            &mut out,
85            "{} {}",
86            format_wkt_number(self.x())?,
87            format_wkt_number(self.y())?
88        )
89        .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT coordinate"))?;
90        if let Some(z) = self.z() {
91            write!(&mut out, " {}", format_wkt_number(z)?)
92                .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT coordinate"))?;
93        }
94        Ok(out)
95    }
96}
97
98#[derive(Debug, Clone, PartialEq)]
99pub struct PointValue {
100    pub srid: u32,
101    pub position: Position,
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub struct LineStringValue {
106    positions: Vec<Position>,
107}
108
109impl LineStringValue {
110    pub fn new(positions: Vec<Position>) -> Result<Self, EncodeError> {
111        ensure_consistent_dimensions(&positions)?;
112        Ok(Self { positions })
113    }
114
115    pub fn len(&self) -> usize {
116        self.positions.len()
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.positions.is_empty()
121    }
122
123    pub fn iter(&self) -> std::slice::Iter<'_, Position> {
124        self.positions.iter()
125    }
126
127    pub fn positions(&self) -> &[Position] {
128        &self.positions
129    }
130
131    fn from_positions_unchecked(positions: Vec<Position>) -> Self {
132        Self { positions }
133    }
134}
135
136#[derive(Debug, Clone, PartialEq)]
137pub struct LinearRingValue {
138    positions: Vec<Position>,
139}
140
141impl LinearRingValue {
142    pub fn new(positions: Vec<Position>) -> Result<Self, EncodeError> {
143        ensure_consistent_dimensions(&positions)?;
144        if !positions.is_empty() {
145            if positions.len() < 4 {
146                return Err(EncodeError::InvalidSpatial(
147                    "linear ring must have at least 4 positions",
148                ));
149            }
150            if positions.first() != positions.last() {
151                return Err(EncodeError::InvalidSpatial("linear ring must be closed"));
152            }
153        }
154        Ok(Self { positions })
155    }
156
157    pub fn len(&self) -> usize {
158        self.positions.len()
159    }
160
161    pub fn is_empty(&self) -> bool {
162        self.positions.is_empty()
163    }
164
165    pub fn iter(&self) -> std::slice::Iter<'_, Position> {
166        self.positions.iter()
167    }
168
169    pub fn positions(&self) -> &[Position] {
170        &self.positions
171    }
172
173    fn from_positions_unchecked(positions: Vec<Position>) -> Self {
174        Self { positions }
175    }
176}
177
178#[derive(Debug, Clone, PartialEq)]
179pub struct PolygonValue {
180    rings: Vec<LinearRingValue>,
181}
182
183impl PolygonValue {
184    pub fn new(rings: Vec<LinearRingValue>) -> Result<Self, EncodeError> {
185        Ok(Self { rings })
186    }
187
188    pub fn len(&self) -> usize {
189        self.rings.len()
190    }
191
192    pub fn is_empty(&self) -> bool {
193        self.rings.is_empty()
194    }
195
196    pub fn rings(&self) -> std::slice::Iter<'_, LinearRingValue> {
197        self.rings.iter()
198    }
199
200    pub fn ring_slice(&self) -> &[LinearRingValue] {
201        &self.rings
202    }
203
204    fn from_rings_unchecked(rings: Vec<LinearRingValue>) -> Self {
205        Self { rings }
206    }
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub enum GeometryShape {
211    Point(Position),
212    LineString(LineStringValue),
213    Polygon(PolygonValue),
214    MultiPoint(Vec<Position>),
215    MultiLineString(Vec<LineStringValue>),
216    MultiPolygon(Vec<PolygonValue>),
217    GeometryCollection(Vec<GeometryShape>),
218}
219
220#[derive(Debug, Clone, PartialEq)]
221pub struct GeometryValue {
222    pub srid: u32,
223    pub shape: GeometryShape,
224}
225
226#[derive(Debug, Clone, PartialEq)]
227pub struct GeographyValue {
228    pub srid: u32,
229    pub shape: GeometryShape,
230}
231
232impl PointValue {
233    pub fn to_geojson_value(&self) -> Result<Value, EncodeError> {
234        let mut obj = Map::new();
235        obj.insert("type".into(), Value::String("Point".into()));
236        obj.insert("coordinates".into(), self.position.to_geojson_value()?);
237        Ok(Value::Object(obj))
238    }
239
240    pub fn to_geojson_string(&self) -> Result<String, EncodeError> {
241        serde_json::to_string(&self.to_geojson_value()?)
242            .map_err(|_| EncodeError::InvalidSpatial("failed to encode GeoJSON point"))
243    }
244
245    pub fn from_geojson_value(value: &Value) -> Result<Self, DecodeError> {
246        let shape = GeometryShape::from_geojson_value(value)?;
247        match shape {
248            GeometryShape::Point(position) => Ok(Self {
249                srid: DEFAULT_GEOJSON_SRID,
250                position,
251            }),
252            _ => Err(DecodeError::InvalidSpatial(
253                "GeoJSON value must be a Point for PointValue".into(),
254            )),
255        }
256    }
257
258    pub fn from_geojson_str(s: &str) -> Result<Self, DecodeError> {
259        let value: Value =
260            serde_json::from_str(s).map_err(|e| DecodeError::InvalidJson(e.to_string()))?;
261        Self::from_geojson_value(&value)
262    }
263
264    pub fn to_wkt_string(&self) -> Result<String, EncodeError> {
265        geometry_shape_to_wkt(&GeometryShape::Point(self.position))
266    }
267
268    pub fn to_ewkt_string(&self) -> Result<String, EncodeError> {
269        Ok(format!("SRID={};{}", self.srid, self.to_wkt_string()?))
270    }
271
272    pub fn from_wkt_str(s: &str) -> Result<Self, DecodeError> {
273        let (srid, shape) = parse_wkt_or_ewkt(s)?;
274        match shape {
275            GeometryShape::Point(position) => Ok(Self {
276                srid: srid.unwrap_or(DEFAULT_GEOJSON_SRID),
277                position,
278            }),
279            _ => Err(DecodeError::InvalidSpatial(
280                "WKT value must be a Point for PointValue".into(),
281            )),
282        }
283    }
284}
285
286impl GeometryValue {
287    pub fn to_geojson_value(&self) -> Result<Value, EncodeError> {
288        self.shape.to_geojson_value()
289    }
290
291    pub fn to_geojson_string(&self) -> Result<String, EncodeError> {
292        serde_json::to_string(&self.to_geojson_value()?)
293            .map_err(|_| EncodeError::InvalidSpatial("failed to encode GeoJSON geometry"))
294    }
295
296    pub fn from_geojson_value(value: &Value) -> Result<Self, DecodeError> {
297        Ok(Self {
298            srid: DEFAULT_GEOJSON_SRID,
299            shape: GeometryShape::from_geojson_value(value)?,
300        })
301    }
302
303    pub fn from_geojson_str(s: &str) -> Result<Self, DecodeError> {
304        let value: Value =
305            serde_json::from_str(s).map_err(|e| DecodeError::InvalidJson(e.to_string()))?;
306        Self::from_geojson_value(&value)
307    }
308
309    pub fn to_wkt_string(&self) -> Result<String, EncodeError> {
310        geometry_shape_to_wkt(&self.shape)
311    }
312
313    pub fn to_ewkt_string(&self) -> Result<String, EncodeError> {
314        Ok(format!("SRID={};{}", self.srid, self.to_wkt_string()?))
315    }
316
317    pub fn from_wkt_str(s: &str) -> Result<Self, DecodeError> {
318        let (srid, shape) = parse_wkt_or_ewkt(s)?;
319        Ok(Self {
320            srid: srid.unwrap_or(DEFAULT_GEOJSON_SRID),
321            shape,
322        })
323    }
324}
325
326impl GeographyValue {
327    pub fn to_geojson_value(&self) -> Result<Value, EncodeError> {
328        self.shape.to_geojson_value()
329    }
330
331    pub fn to_geojson_string(&self) -> Result<String, EncodeError> {
332        serde_json::to_string(&self.to_geojson_value()?)
333            .map_err(|_| EncodeError::InvalidSpatial("failed to encode GeoJSON geography"))
334    }
335
336    pub fn from_geojson_value(value: &Value) -> Result<Self, DecodeError> {
337        Ok(Self {
338            srid: DEFAULT_GEOJSON_SRID,
339            shape: GeometryShape::from_geojson_value(value)?,
340        })
341    }
342
343    pub fn from_geojson_str(s: &str) -> Result<Self, DecodeError> {
344        let value: Value =
345            serde_json::from_str(s).map_err(|e| DecodeError::InvalidJson(e.to_string()))?;
346        Self::from_geojson_value(&value)
347    }
348
349    pub fn to_wkt_string(&self) -> Result<String, EncodeError> {
350        geometry_shape_to_wkt(&self.shape)
351    }
352
353    pub fn to_ewkt_string(&self) -> Result<String, EncodeError> {
354        Ok(format!("SRID={};{}", self.srid, self.to_wkt_string()?))
355    }
356
357    pub fn from_wkt_str(s: &str) -> Result<Self, DecodeError> {
358        let (srid, shape) = parse_wkt_or_ewkt(s)?;
359        Ok(Self {
360            srid: srid.unwrap_or(DEFAULT_GEOJSON_SRID),
361            shape,
362        })
363    }
364}
365
366impl PointValue {
367    pub fn encode_blob(&self) -> Result<Vec<u8>, EncodeError> {
368        let mut out = vec![tag::POINT];
369        encode_geometry_record(
370            &mut out,
371            EWKB_POINT,
372            self.srid,
373            &GeometryShape::Point(self.position),
374            true,
375        )?;
376        Ok(out)
377    }
378
379    pub fn decode_blob(blob: &[u8]) -> Result<Self, DecodeError> {
380        let (srid, shape) = decode_top_level_shape(blob, tag::POINT)?;
381        match shape {
382            GeometryShape::Point(position) => Ok(Self { srid, position }),
383            _ => Err(DecodeError::InvalidSpatial(
384                "point tag must decode to point shape".into(),
385            )),
386        }
387    }
388}
389
390impl GeometryValue {
391    pub fn encode_blob(&self) -> Result<Vec<u8>, EncodeError> {
392        let mut out = vec![tag::GEOMETRY];
393        encode_geometry_record(
394            &mut out,
395            self.shape.base_type(),
396            self.srid,
397            &self.shape,
398            true,
399        )?;
400        Ok(out)
401    }
402
403    pub fn decode_blob(blob: &[u8]) -> Result<Self, DecodeError> {
404        let (srid, shape) = decode_top_level_shape(blob, tag::GEOMETRY)?;
405        Ok(Self { srid, shape })
406    }
407}
408
409impl GeographyValue {
410    pub fn encode_blob(&self) -> Result<Vec<u8>, EncodeError> {
411        let mut out = vec![tag::GEOGRAPHY];
412        encode_geometry_record(
413            &mut out,
414            self.shape.base_type(),
415            self.srid,
416            &self.shape,
417            true,
418        )?;
419        Ok(out)
420    }
421
422    pub fn decode_blob(blob: &[u8]) -> Result<Self, DecodeError> {
423        let (srid, shape) = decode_top_level_shape(blob, tag::GEOGRAPHY)?;
424        Ok(Self { srid, shape })
425    }
426}
427
428impl GeometryShape {
429    pub fn to_geojson_value(&self) -> Result<Value, EncodeError> {
430        let mut obj = Map::new();
431        match self {
432            Self::Point(position) => {
433                obj.insert("type".into(), Value::String("Point".into()));
434                obj.insert("coordinates".into(), position.to_geojson_value()?);
435            }
436            Self::LineString(line) => {
437                obj.insert("type".into(), Value::String("LineString".into()));
438                obj.insert(
439                    "coordinates".into(),
440                    Value::Array(
441                        line.positions()
442                            .iter()
443                            .map(Position::to_geojson_value)
444                            .collect::<Result<Vec<_>, _>>()?,
445                    ),
446                );
447            }
448            Self::Polygon(poly) => {
449                obj.insert("type".into(), Value::String("Polygon".into()));
450                obj.insert(
451                    "coordinates".into(),
452                    Value::Array(
453                        poly.ring_slice()
454                            .iter()
455                            .map(|ring| {
456                                ring.positions()
457                                    .iter()
458                                    .map(Position::to_geojson_value)
459                                    .collect::<Result<Vec<_>, _>>()
460                                    .map(Value::Array)
461                            })
462                            .collect::<Result<Vec<_>, _>>()?,
463                    ),
464                );
465            }
466            Self::MultiPoint(points) => {
467                obj.insert("type".into(), Value::String("MultiPoint".into()));
468                obj.insert(
469                    "coordinates".into(),
470                    Value::Array(
471                        points
472                            .iter()
473                            .map(Position::to_geojson_value)
474                            .collect::<Result<Vec<_>, _>>()?,
475                    ),
476                );
477            }
478            Self::MultiLineString(lines) => {
479                obj.insert("type".into(), Value::String("MultiLineString".into()));
480                obj.insert(
481                    "coordinates".into(),
482                    Value::Array(
483                        lines
484                            .iter()
485                            .map(|line| {
486                                line.positions()
487                                    .iter()
488                                    .map(Position::to_geojson_value)
489                                    .collect::<Result<Vec<_>, _>>()
490                                    .map(Value::Array)
491                            })
492                            .collect::<Result<Vec<_>, _>>()?,
493                    ),
494                );
495            }
496            Self::MultiPolygon(polys) => {
497                obj.insert("type".into(), Value::String("MultiPolygon".into()));
498                obj.insert(
499                    "coordinates".into(),
500                    Value::Array(
501                        polys
502                            .iter()
503                            .map(|poly| {
504                                poly.ring_slice()
505                                    .iter()
506                                    .map(|ring| {
507                                        ring.positions()
508                                            .iter()
509                                            .map(Position::to_geojson_value)
510                                            .collect::<Result<Vec<_>, _>>()
511                                            .map(Value::Array)
512                                    })
513                                    .collect::<Result<Vec<_>, _>>()
514                                    .map(Value::Array)
515                            })
516                            .collect::<Result<Vec<_>, _>>()?,
517                    ),
518                );
519            }
520            Self::GeometryCollection(shapes) => {
521                obj.insert("type".into(), Value::String("GeometryCollection".into()));
522                obj.insert(
523                    "geometries".into(),
524                    Value::Array(
525                        shapes
526                            .iter()
527                            .map(GeometryShape::to_geojson_value)
528                            .collect::<Result<Vec<_>, _>>()?,
529                    ),
530                );
531            }
532        }
533        Ok(Value::Object(obj))
534    }
535
536    pub fn from_geojson_value(value: &Value) -> Result<Self, DecodeError> {
537        let obj = value.as_object().ok_or_else(|| {
538            DecodeError::InvalidSpatial("GeoJSON geometry must be an object".into())
539        })?;
540        if obj.contains_key("crs") {
541            return Err(DecodeError::InvalidSpatial(
542                "GeoJSON CRS objects are out of scope".into(),
543            ));
544        }
545        let kind = obj.get("type").and_then(Value::as_str).ok_or_else(|| {
546            DecodeError::InvalidSpatial("GeoJSON geometry must contain a string type".into())
547        })?;
548        match kind {
549            "Point" => Ok(Self::Point(Position::from_geojson_value(required(
550                obj,
551                "coordinates",
552            )?)?)),
553            "LineString" => {
554                let positions = parse_positions_array(required(obj, "coordinates")?)?;
555                Ok(Self::LineString(
556                    LineStringValue::new(positions).map_err(map_spatial_encode)?,
557                ))
558            }
559            "Polygon" => {
560                let rings = parse_polygon_rings(required(obj, "coordinates")?)?;
561                Ok(Self::Polygon(
562                    PolygonValue::new(rings).map_err(map_spatial_encode)?,
563                ))
564            }
565            "MultiPoint" => {
566                let points = parse_positions_array(required(obj, "coordinates")?)?;
567                ensure_consistent_dimensions(&points).map_err(map_spatial_encode)?;
568                Ok(Self::MultiPoint(points))
569            }
570            "MultiLineString" => {
571                let coords = required(obj, "coordinates")?.as_array().ok_or_else(|| {
572                    DecodeError::InvalidSpatial(
573                        "MultiLineString coordinates must be an array".into(),
574                    )
575                })?;
576                let mut lines = Vec::with_capacity(coords.len());
577                for line in coords {
578                    lines.push(
579                        LineStringValue::new(parse_positions_array(line)?)
580                            .map_err(map_spatial_encode)?,
581                    );
582                }
583                Ok(Self::MultiLineString(lines))
584            }
585            "MultiPolygon" => {
586                let coords = required(obj, "coordinates")?.as_array().ok_or_else(|| {
587                    DecodeError::InvalidSpatial("MultiPolygon coordinates must be an array".into())
588                })?;
589                let mut polys = Vec::with_capacity(coords.len());
590                for poly in coords {
591                    polys.push(
592                        PolygonValue::new(parse_polygon_rings(poly)?)
593                            .map_err(map_spatial_encode)?,
594                    );
595                }
596                Ok(Self::MultiPolygon(polys))
597            }
598            "GeometryCollection" => {
599                let geometries = required(obj, "geometries")?.as_array().ok_or_else(|| {
600                    DecodeError::InvalidSpatial(
601                        "GeometryCollection geometries must be an array".into(),
602                    )
603                })?;
604                let mut shapes = Vec::with_capacity(geometries.len());
605                for child in geometries {
606                    shapes.push(Self::from_geojson_value(child)?);
607                }
608                Ok(Self::GeometryCollection(shapes))
609            }
610            "Feature" | "FeatureCollection" => Err(DecodeError::InvalidSpatial(
611                "GeoJSON Feature wrappers are not spatial scalar values".into(),
612            )),
613            other => Err(DecodeError::InvalidSpatial(format!(
614                "unsupported GeoJSON geometry type {other}"
615            ))),
616        }
617    }
618
619    fn base_type(&self) -> u32 {
620        match self {
621            Self::Point(_) => EWKB_POINT,
622            Self::LineString(_) => EWKB_LINESTRING,
623            Self::Polygon(_) => EWKB_POLYGON,
624            Self::MultiPoint(_) => EWKB_MULTI_POINT,
625            Self::MultiLineString(_) => EWKB_MULTI_LINESTRING,
626            Self::MultiPolygon(_) => EWKB_MULTI_POLYGON,
627            Self::GeometryCollection(_) => EWKB_GEOMETRY_COLLECTION,
628        }
629    }
630
631    fn has_z(&self) -> bool {
632        match self {
633            Self::Point(pos) => pos.dimension() == 3,
634            Self::LineString(line) => line.positions.first().map(Position::dimension) == Some(3),
635            Self::Polygon(poly) => {
636                poly.rings
637                    .first()
638                    .and_then(|ring| ring.positions.first())
639                    .map(Position::dimension)
640                    == Some(3)
641            }
642            Self::MultiPoint(points) => points.first().map(Position::dimension) == Some(3),
643            Self::MultiLineString(lines) => {
644                lines
645                    .first()
646                    .and_then(|line| line.positions.first())
647                    .map(Position::dimension)
648                    == Some(3)
649            }
650            Self::MultiPolygon(polys) => {
651                polys
652                    .first()
653                    .and_then(|poly| poly.rings.first())
654                    .and_then(|ring| ring.positions.first())
655                    .map(Position::dimension)
656                    == Some(3)
657            }
658            Self::GeometryCollection(shapes) => {
659                shapes.first().map(GeometryShape::has_z).unwrap_or(false)
660            }
661        }
662    }
663}
664
665fn geometry_shape_to_wkt(shape: &GeometryShape) -> Result<String, EncodeError> {
666    let mut out = String::new();
667    match shape {
668        GeometryShape::Point(position) => {
669            write!(
670                &mut out,
671                "POINT{} ({})",
672                dim_suffix(true, position.dimension() == 3),
673                position.to_wkt_coords()?
674            )
675            .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT point"))?;
676        }
677        GeometryShape::LineString(line) => {
678            write!(&mut out, "LINESTRING{}", dim_suffix(true, shape.has_z()))
679                .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT linestring"))?;
680            if line.is_empty() {
681                out.push_str(" EMPTY");
682            } else {
683                out.push_str(" (");
684                push_joined_positions(&mut out, line.positions())?;
685                out.push(')');
686            }
687        }
688        GeometryShape::Polygon(poly) => {
689            write!(&mut out, "POLYGON{}", dim_suffix(true, shape.has_z()))
690                .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT polygon"))?;
691            if poly.is_empty() {
692                out.push_str(" EMPTY");
693            } else {
694                out.push_str(" (");
695                for (idx, ring) in poly.ring_slice().iter().enumerate() {
696                    if idx > 0 {
697                        out.push_str(", ");
698                    }
699                    out.push('(');
700                    push_joined_positions(&mut out, ring.positions())?;
701                    out.push(')');
702                }
703                out.push(')');
704            }
705        }
706        GeometryShape::MultiPoint(points) => {
707            write!(&mut out, "MULTIPOINT{}", dim_suffix(true, shape.has_z()))
708                .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT multipoint"))?;
709            if points.is_empty() {
710                out.push_str(" EMPTY");
711            } else {
712                out.push_str(" (");
713                for (idx, point) in points.iter().enumerate() {
714                    if idx > 0 {
715                        out.push_str(", ");
716                    }
717                    out.push('(');
718                    out.push_str(&point.to_wkt_coords()?);
719                    out.push(')');
720                }
721                out.push(')');
722            }
723        }
724        GeometryShape::MultiLineString(lines) => {
725            write!(
726                &mut out,
727                "MULTILINESTRING{}",
728                dim_suffix(true, shape.has_z())
729            )
730            .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT multilinestring"))?;
731            if lines.is_empty() {
732                out.push_str(" EMPTY");
733            } else {
734                out.push_str(" (");
735                for (idx, line) in lines.iter().enumerate() {
736                    if idx > 0 {
737                        out.push_str(", ");
738                    }
739                    out.push('(');
740                    push_joined_positions(&mut out, line.positions())?;
741                    out.push(')');
742                }
743                out.push(')');
744            }
745        }
746        GeometryShape::MultiPolygon(polys) => {
747            write!(&mut out, "MULTIPOLYGON{}", dim_suffix(true, shape.has_z()))
748                .map_err(|_| EncodeError::InvalidSpatial("failed to format WKT multipolygon"))?;
749            if polys.is_empty() {
750                out.push_str(" EMPTY");
751            } else {
752                out.push_str(" (");
753                for (pidx, poly) in polys.iter().enumerate() {
754                    if pidx > 0 {
755                        out.push_str(", ");
756                    }
757                    out.push('(');
758                    for (ridx, ring) in poly.ring_slice().iter().enumerate() {
759                        if ridx > 0 {
760                            out.push_str(", ");
761                        }
762                        out.push('(');
763                        push_joined_positions(&mut out, ring.positions())?;
764                        out.push(')');
765                    }
766                    out.push(')');
767                }
768                out.push(')');
769            }
770        }
771        GeometryShape::GeometryCollection(shapes) => {
772            out.push_str("GEOMETRYCOLLECTION");
773            if shapes.is_empty() {
774                out.push_str(" EMPTY");
775            } else {
776                out.push_str(" (");
777                for (idx, child) in shapes.iter().enumerate() {
778                    if idx > 0 {
779                        out.push_str(", ");
780                    }
781                    out.push_str(&geometry_shape_to_wkt(child)?);
782                }
783                out.push(')');
784            }
785        }
786    }
787    Ok(out)
788}
789
790fn push_joined_positions(out: &mut String, positions: &[Position]) -> Result<(), EncodeError> {
791    for (idx, position) in positions.iter().enumerate() {
792        if idx > 0 {
793            out.push_str(", ");
794        }
795        out.push_str(&position.to_wkt_coords()?);
796    }
797    Ok(())
798}
799
800fn dim_suffix(allow_collection_marker: bool, has_z: bool) -> &'static str {
801    if has_z && allow_collection_marker {
802        " Z"
803    } else {
804        ""
805    }
806}
807
808fn required<'a>(obj: &'a Map<String, Value>, key: &str) -> Result<&'a Value, DecodeError> {
809    obj.get(key)
810        .ok_or_else(|| DecodeError::InvalidSpatial(format!("missing GeoJSON field {key}")))
811}
812
813fn json_number(value: f64) -> Result<Value, EncodeError> {
814    if !value.is_finite() {
815        return Err(EncodeError::InvalidSpatial(
816            "spatial coordinates must be finite",
817        ));
818    }
819    Number::from_f64(value)
820        .map(Value::Number)
821        .ok_or(EncodeError::InvalidSpatial(
822            "spatial coordinates must be finite",
823        ))
824}
825
826fn json_f64(value: &Value) -> Result<f64, DecodeError> {
827    let number = value
828        .as_f64()
829        .ok_or_else(|| DecodeError::InvalidSpatial("coordinate must be numeric".into()))?;
830    if !number.is_finite() {
831        return Err(DecodeError::InvalidSpatial(
832            "spatial coordinates must be finite".into(),
833        ));
834    }
835    Ok(number)
836}
837
838fn parse_positions_array(value: &Value) -> Result<Vec<Position>, DecodeError> {
839    let coords = value
840        .as_array()
841        .ok_or_else(|| DecodeError::InvalidSpatial("coordinates must be an array".into()))?;
842    let mut positions = Vec::with_capacity(coords.len());
843    for coord in coords {
844        positions.push(Position::from_geojson_value(coord)?);
845    }
846    Ok(positions)
847}
848
849fn parse_polygon_rings(value: &Value) -> Result<Vec<LinearRingValue>, DecodeError> {
850    let rings = value.as_array().ok_or_else(|| {
851        DecodeError::InvalidSpatial("polygon coordinates must be an array".into())
852    })?;
853    let mut out = Vec::with_capacity(rings.len());
854    for ring in rings {
855        out.push(LinearRingValue::new(parse_positions_array(ring)?).map_err(map_spatial_encode)?);
856    }
857    Ok(out)
858}
859
860fn map_spatial_encode(err: EncodeError) -> DecodeError {
861    match err {
862        EncodeError::InvalidSpatial(msg) => DecodeError::InvalidSpatial(msg.into()),
863        _ => DecodeError::InvalidSpatial("invalid spatial value".into()),
864    }
865}
866
867fn format_wkt_number(value: f64) -> Result<String, EncodeError> {
868    if !value.is_finite() {
869        return Err(EncodeError::InvalidSpatial(
870            "spatial coordinates must be finite",
871        ));
872    }
873    Ok(value.to_string())
874}
875
876fn parse_wkt_or_ewkt(s: &str) -> Result<(Option<u32>, GeometryShape), DecodeError> {
877    let mut parser = WktParser::new(s);
878    parser.skip_ws();
879    let srid = if parser.consume_keyword_ci("SRID") {
880        parser.skip_ws();
881        parser.expect_char('=')?;
882        let srid = parser.parse_u32()?;
883        parser.skip_ws();
884        parser.expect_char(';')?;
885        Some(srid)
886    } else {
887        None
888    };
889    let shape = parser.parse_geometry_shape()?;
890    parser.skip_ws();
891    if !parser.is_eof() {
892        return Err(DecodeError::NonCanonical("trailing bytes after WKT"));
893    }
894    Ok((srid, shape))
895}
896
897struct WktParser<'a> {
898    input: &'a str,
899    pos: usize,
900}
901
902impl<'a> WktParser<'a> {
903    fn new(input: &'a str) -> Self {
904        Self { input, pos: 0 }
905    }
906
907    fn is_eof(&self) -> bool {
908        self.pos >= self.input.len()
909    }
910
911    fn skip_ws(&mut self) {
912        while let Some(ch) = self.peek_char() {
913            if ch.is_whitespace() {
914                self.pos += ch.len_utf8();
915            } else {
916                break;
917            }
918        }
919    }
920
921    fn peek_char(&self) -> Option<char> {
922        self.input[self.pos..].chars().next()
923    }
924
925    fn consume_char(&mut self, expected: char) -> bool {
926        self.skip_ws();
927        if self.peek_char() == Some(expected) {
928            self.pos += expected.len_utf8();
929            true
930        } else {
931            false
932        }
933    }
934
935    fn expect_char(&mut self, expected: char) -> Result<(), DecodeError> {
936        if self.consume_char(expected) {
937            Ok(())
938        } else {
939            Err(DecodeError::InvalidSpatial(format!(
940                "expected '{expected}' in WKT"
941            )))
942        }
943    }
944
945    fn consume_keyword_ci(&mut self, keyword: &str) -> bool {
946        self.skip_ws();
947        let rest = &self.input[self.pos..];
948        let Some(prefix) = rest.get(..keyword.len()) else {
949            return false;
950        };
951        if prefix.eq_ignore_ascii_case(keyword) {
952            self.pos += keyword.len();
953            true
954        } else {
955            false
956        }
957    }
958
959    fn parse_ident(&mut self) -> Result<String, DecodeError> {
960        self.skip_ws();
961        let start = self.pos;
962        while let Some(ch) = self.peek_char() {
963            if ch.is_ascii_alphabetic() {
964                self.pos += ch.len_utf8();
965            } else {
966                break;
967            }
968        }
969        if self.pos == start {
970            return Err(DecodeError::InvalidSpatial(
971                "expected geometry type in WKT".into(),
972            ));
973        }
974        Ok(self.input[start..self.pos].to_ascii_uppercase())
975    }
976
977    fn parse_u32(&mut self) -> Result<u32, DecodeError> {
978        let start = self.pos;
979        self.skip_ws();
980        let start = self.pos.max(start);
981        while let Some(ch) = self.peek_char() {
982            if ch.is_ascii_digit() {
983                self.pos += 1;
984            } else {
985                break;
986            }
987        }
988        if self.pos == start {
989            return Err(DecodeError::InvalidSpatial("expected integer".into()));
990        }
991        self.input[start..self.pos]
992            .parse::<u32>()
993            .map_err(|_| DecodeError::InvalidSpatial("invalid integer".into()))
994    }
995
996    fn parse_number(&mut self) -> Result<f64, DecodeError> {
997        self.skip_ws();
998        let start = self.pos;
999        let bytes = self.input.as_bytes();
1000        while self.pos < bytes.len() {
1001            let ch = bytes[self.pos] as char;
1002            if ch.is_ascii_digit() || matches!(ch, '+' | '-' | '.' | 'e' | 'E') {
1003                self.pos += 1;
1004            } else {
1005                break;
1006            }
1007        }
1008        if self.pos == start {
1009            return Err(DecodeError::InvalidSpatial("expected number in WKT".into()));
1010        }
1011        let value = self.input[start..self.pos]
1012            .parse::<f64>()
1013            .map_err(|_| DecodeError::InvalidSpatial("invalid number in WKT".into()))?;
1014        if !value.is_finite() {
1015            return Err(DecodeError::InvalidSpatial(
1016                "spatial coordinates must be finite".into(),
1017            ));
1018        }
1019        Ok(value)
1020    }
1021
1022    fn parse_optional_dimension_keyword(&mut self) -> Result<(), DecodeError> {
1023        self.skip_ws();
1024        let checkpoint = self.pos;
1025        let ident = self.parse_ident();
1026        match ident {
1027            Ok(value) if value == "Z" => Ok(()),
1028            Ok(value) if value == "M" || value == "ZM" => Err(DecodeError::InvalidSpatial(
1029                "M dimensions are not supported in the initial subset".into(),
1030            )),
1031            Ok(_) => {
1032                self.pos = checkpoint;
1033                Ok(())
1034            }
1035            Err(_) => {
1036                self.pos = checkpoint;
1037                Ok(())
1038            }
1039        }
1040    }
1041
1042    fn parse_geometry_shape(&mut self) -> Result<GeometryShape, DecodeError> {
1043        let kind = self.parse_ident()?;
1044        self.parse_optional_dimension_keyword()?;
1045        if self.consume_keyword_ci("EMPTY") {
1046            return match kind.as_str() {
1047                "POINT" => Err(DecodeError::InvalidSpatial(
1048                    "POINT EMPTY is not supported".into(),
1049                )),
1050                "LINESTRING" => Ok(GeometryShape::LineString(
1051                    LineStringValue::new(vec![]).map_err(map_spatial_encode)?,
1052                )),
1053                "POLYGON" => Ok(GeometryShape::Polygon(
1054                    PolygonValue::new(vec![]).map_err(map_spatial_encode)?,
1055                )),
1056                "MULTIPOINT" => Ok(GeometryShape::MultiPoint(vec![])),
1057                "MULTILINESTRING" => Ok(GeometryShape::MultiLineString(vec![])),
1058                "MULTIPOLYGON" => Ok(GeometryShape::MultiPolygon(vec![])),
1059                "GEOMETRYCOLLECTION" => Ok(GeometryShape::GeometryCollection(vec![])),
1060                _ => Err(DecodeError::InvalidSpatial(format!(
1061                    "unsupported WKT geometry type {kind}"
1062                ))),
1063            };
1064        }
1065
1066        match kind.as_str() {
1067            "POINT" => {
1068                self.expect_char('(')?;
1069                let position = self.parse_position()?;
1070                self.expect_char(')')?;
1071                Ok(GeometryShape::Point(position))
1072            }
1073            "LINESTRING" => {
1074                self.expect_char('(')?;
1075                let positions = self.parse_position_seq(')')?;
1076                self.expect_char(')')?;
1077                Ok(GeometryShape::LineString(
1078                    LineStringValue::new(positions).map_err(map_spatial_encode)?,
1079                ))
1080            }
1081            "POLYGON" => {
1082                self.expect_char('(')?;
1083                let mut rings = Vec::new();
1084                loop {
1085                    self.expect_char('(')?;
1086                    let positions = self.parse_position_seq(')')?;
1087                    self.expect_char(')')?;
1088                    rings.push(LinearRingValue::new(positions).map_err(map_spatial_encode)?);
1089                    if self.consume_char(',') {
1090                        continue;
1091                    }
1092                    break;
1093                }
1094                self.expect_char(')')?;
1095                Ok(GeometryShape::Polygon(
1096                    PolygonValue::new(rings).map_err(map_spatial_encode)?,
1097                ))
1098            }
1099            "MULTIPOINT" => {
1100                self.expect_char('(')?;
1101                let mut points = Vec::new();
1102                loop {
1103                    self.skip_ws();
1104                    let point = if self.consume_char('(') {
1105                        let position = self.parse_position()?;
1106                        self.expect_char(')')?;
1107                        position
1108                    } else {
1109                        self.parse_position()?
1110                    };
1111                    points.push(point);
1112                    if self.consume_char(',') {
1113                        continue;
1114                    }
1115                    break;
1116                }
1117                self.expect_char(')')?;
1118                ensure_consistent_dimensions(&points).map_err(map_spatial_encode)?;
1119                Ok(GeometryShape::MultiPoint(points))
1120            }
1121            "MULTILINESTRING" => {
1122                self.expect_char('(')?;
1123                let mut lines = Vec::new();
1124                loop {
1125                    self.expect_char('(')?;
1126                    let positions = self.parse_position_seq(')')?;
1127                    self.expect_char(')')?;
1128                    lines.push(LineStringValue::new(positions).map_err(map_spatial_encode)?);
1129                    if self.consume_char(',') {
1130                        continue;
1131                    }
1132                    break;
1133                }
1134                self.expect_char(')')?;
1135                Ok(GeometryShape::MultiLineString(lines))
1136            }
1137            "MULTIPOLYGON" => {
1138                self.expect_char('(')?;
1139                let mut polys = Vec::new();
1140                loop {
1141                    self.expect_char('(')?;
1142                    let mut rings = Vec::new();
1143                    loop {
1144                        self.expect_char('(')?;
1145                        let positions = self.parse_position_seq(')')?;
1146                        self.expect_char(')')?;
1147                        rings.push(LinearRingValue::new(positions).map_err(map_spatial_encode)?);
1148                        if self.consume_char(',') {
1149                            continue;
1150                        }
1151                        break;
1152                    }
1153                    self.expect_char(')')?;
1154                    polys.push(PolygonValue::new(rings).map_err(map_spatial_encode)?);
1155                    if self.consume_char(',') {
1156                        continue;
1157                    }
1158                    break;
1159                }
1160                self.expect_char(')')?;
1161                Ok(GeometryShape::MultiPolygon(polys))
1162            }
1163            "GEOMETRYCOLLECTION" => {
1164                self.expect_char('(')?;
1165                let mut shapes = Vec::new();
1166                loop {
1167                    shapes.push(self.parse_geometry_shape()?);
1168                    if self.consume_char(',') {
1169                        continue;
1170                    }
1171                    break;
1172                }
1173                self.expect_char(')')?;
1174                Ok(GeometryShape::GeometryCollection(shapes))
1175            }
1176            _ => Err(DecodeError::InvalidSpatial(format!(
1177                "unsupported WKT geometry type {kind}"
1178            ))),
1179        }
1180    }
1181
1182    fn parse_position_seq(&mut self, until: char) -> Result<Vec<Position>, DecodeError> {
1183        let mut positions = Vec::new();
1184        loop {
1185            self.skip_ws();
1186            if self.peek_char() == Some(until) {
1187                break;
1188            }
1189            positions.push(self.parse_position()?);
1190            if self.consume_char(',') {
1191                continue;
1192            }
1193            break;
1194        }
1195        Ok(positions)
1196    }
1197
1198    fn parse_position(&mut self) -> Result<Position, DecodeError> {
1199        let x = self.parse_number()?;
1200        let y = self.parse_number()?;
1201        self.skip_ws();
1202        if self.peek_is_number_start() {
1203            let z = self.parse_number()?;
1204            self.skip_ws();
1205            if self.peek_is_number_start() {
1206                return Err(DecodeError::InvalidSpatial(
1207                    "only XY and XYZ coordinates are supported".into(),
1208                ));
1209            }
1210            Ok(Position::Xyz { x, y, z })
1211        } else {
1212            Ok(Position::Xy { x, y })
1213        }
1214    }
1215
1216    fn peek_is_number_start(&self) -> bool {
1217        matches!(
1218            self.peek_char(),
1219            Some('+') | Some('-') | Some('.') | Some('0'..='9')
1220        )
1221    }
1222}
1223
1224fn ensure_consistent_dimensions(positions: &[Position]) -> Result<(), EncodeError> {
1225    let Some(first) = positions.first() else {
1226        return Ok(());
1227    };
1228    let dim = first.dimension();
1229    if positions.iter().any(|pos| pos.dimension() != dim) {
1230        return Err(EncodeError::InvalidSpatial(
1231            "all positions must have the same dimension",
1232        ));
1233    }
1234    Ok(())
1235}
1236
1237fn encode_geometry_record(
1238    out: &mut Vec<u8>,
1239    base_type: u32,
1240    srid: u32,
1241    shape: &GeometryShape,
1242    include_srid: bool,
1243) -> Result<(), EncodeError> {
1244    out.push(0x01);
1245    let mut type_word = base_type;
1246    if shape.has_z() {
1247        type_word |= EWKB_Z_FLAG;
1248    }
1249    if include_srid {
1250        type_word |= EWKB_SRID_FLAG;
1251    }
1252    out.extend_from_slice(&type_word.to_le_bytes());
1253    if include_srid {
1254        out.extend_from_slice(&srid.to_le_bytes());
1255    }
1256    encode_shape_body(out, shape, srid)?;
1257    Ok(())
1258}
1259
1260fn encode_shape_body(
1261    out: &mut Vec<u8>,
1262    shape: &GeometryShape,
1263    srid: u32,
1264) -> Result<(), EncodeError> {
1265    match shape {
1266        GeometryShape::Point(position) => encode_position(out, position),
1267        GeometryShape::LineString(line) => {
1268            ensure_consistent_dimensions(line.positions())?;
1269            out.extend_from_slice(&(line.len() as u32).to_le_bytes());
1270            for position in line.positions() {
1271                encode_position(out, position);
1272            }
1273        }
1274        GeometryShape::Polygon(poly) => {
1275            out.extend_from_slice(&(poly.len() as u32).to_le_bytes());
1276            for ring in poly.ring_slice() {
1277                ensure_consistent_dimensions(ring.positions())?;
1278                out.extend_from_slice(&(ring.len() as u32).to_le_bytes());
1279                for position in ring.positions() {
1280                    encode_position(out, position);
1281                }
1282            }
1283        }
1284        GeometryShape::MultiPoint(points) => {
1285            ensure_consistent_dimensions(points)?;
1286            out.extend_from_slice(&(points.len() as u32).to_le_bytes());
1287            for position in points {
1288                encode_geometry_record(
1289                    out,
1290                    EWKB_POINT,
1291                    srid,
1292                    &GeometryShape::Point(*position),
1293                    false,
1294                )?;
1295            }
1296        }
1297        GeometryShape::MultiLineString(lines) => {
1298            out.extend_from_slice(&(lines.len() as u32).to_le_bytes());
1299            for line in lines {
1300                encode_geometry_record(
1301                    out,
1302                    EWKB_LINESTRING,
1303                    srid,
1304                    &GeometryShape::LineString(line.clone()),
1305                    false,
1306                )?;
1307            }
1308        }
1309        GeometryShape::MultiPolygon(polys) => {
1310            out.extend_from_slice(&(polys.len() as u32).to_le_bytes());
1311            for poly in polys {
1312                encode_geometry_record(
1313                    out,
1314                    EWKB_POLYGON,
1315                    srid,
1316                    &GeometryShape::Polygon(poly.clone()),
1317                    false,
1318                )?;
1319            }
1320        }
1321        GeometryShape::GeometryCollection(shapes) => {
1322            out.extend_from_slice(&(shapes.len() as u32).to_le_bytes());
1323            for child in shapes {
1324                encode_geometry_record(out, child.base_type(), srid, child, false)?;
1325            }
1326        }
1327    }
1328    Ok(())
1329}
1330
1331fn encode_position(out: &mut Vec<u8>, position: &Position) {
1332    out.extend_from_slice(&position.x().to_le_bytes());
1333    out.extend_from_slice(&position.y().to_le_bytes());
1334    if let Some(z) = position.z() {
1335        out.extend_from_slice(&z.to_le_bytes());
1336    }
1337}
1338
1339fn decode_top_level_shape(
1340    blob: &[u8],
1341    expected_tag: u8,
1342) -> Result<(u32, GeometryShape), DecodeError> {
1343    if blob.is_empty() {
1344        return Err(DecodeError::EmptyInput);
1345    }
1346    if blob[0] != expected_tag {
1347        return Err(DecodeError::UnexpectedTag {
1348            expected: "spatial top-level tag",
1349            actual: blob[0],
1350        });
1351    }
1352    let mut reader = Reader::new(&blob[1..]);
1353    let (shape, srid) = decode_geometry_record(&mut reader, true, None)?;
1354    if !reader.is_empty() {
1355        return Err(DecodeError::NonCanonical(
1356            "trailing bytes after spatial value",
1357        ));
1358    }
1359    let srid =
1360        srid.ok_or_else(|| DecodeError::NonCanonical("top-level spatial value must carry SRID"))?;
1361    Ok((srid, shape))
1362}
1363
1364fn decode_geometry_record(
1365    reader: &mut Reader<'_>,
1366    require_srid: bool,
1367    parent_srid: Option<u32>,
1368) -> Result<(GeometryShape, Option<u32>), DecodeError> {
1369    let byte_order = reader.read_u8()?;
1370    if byte_order != 0x01 {
1371        return Err(DecodeError::NonCanonical(
1372            "only little-endian EWKB is canonical",
1373        ));
1374    }
1375    let type_word = reader.read_u32_le()?;
1376    if type_word & EWKB_M_FLAG != 0 {
1377        return Err(DecodeError::InvalidSpatial(
1378            "M dimensions are not supported in the initial subset".into(),
1379        ));
1380    }
1381    let has_z = type_word & EWKB_Z_FLAG != 0;
1382    let has_srid = type_word & EWKB_SRID_FLAG != 0;
1383    let base_type = type_word & 0x0000_00ff;
1384    let srid = if has_srid {
1385        Some(reader.read_u32_le()?)
1386    } else {
1387        None
1388    };
1389    if require_srid && srid.is_none() {
1390        return Err(DecodeError::NonCanonical(
1391            "top-level spatial value must include SRID",
1392        ));
1393    }
1394    if let (Some(parent), Some(child)) = (parent_srid, srid) {
1395        if parent != child {
1396            return Err(DecodeError::InvalidSpatial(
1397                "nested geometry SRID must match parent".into(),
1398            ));
1399        }
1400    }
1401    let effective_srid = srid.or(parent_srid);
1402    let shape = match base_type {
1403        EWKB_POINT => GeometryShape::Point(decode_position(reader, has_z)?),
1404        EWKB_LINESTRING => {
1405            let count = reader.read_u32_le()? as usize;
1406            let mut positions = Vec::with_capacity(count);
1407            for _ in 0..count {
1408                positions.push(decode_position(reader, has_z)?);
1409            }
1410            GeometryShape::LineString(LineStringValue::from_positions_unchecked(positions))
1411        }
1412        EWKB_POLYGON => {
1413            let ring_count = reader.read_u32_le()? as usize;
1414            let mut rings = Vec::with_capacity(ring_count);
1415            for _ in 0..ring_count {
1416                let point_count = reader.read_u32_le()? as usize;
1417                let mut positions = Vec::with_capacity(point_count);
1418                for _ in 0..point_count {
1419                    positions.push(decode_position(reader, has_z)?);
1420                }
1421                rings.push(LinearRingValue::from_positions_unchecked(positions));
1422            }
1423            GeometryShape::Polygon(PolygonValue::from_rings_unchecked(rings))
1424        }
1425        EWKB_MULTI_POINT => {
1426            let count = reader.read_u32_le()? as usize;
1427            let mut points = Vec::with_capacity(count);
1428            for _ in 0..count {
1429                let (child, _) = decode_geometry_record(reader, false, effective_srid)?;
1430                match child {
1431                    GeometryShape::Point(point) => points.push(point),
1432                    _ => {
1433                        return Err(DecodeError::InvalidSpatial(
1434                            "multipoint child must be point".into(),
1435                        ))
1436                    }
1437                }
1438            }
1439            GeometryShape::MultiPoint(points)
1440        }
1441        EWKB_MULTI_LINESTRING => {
1442            let count = reader.read_u32_le()? as usize;
1443            let mut lines = Vec::with_capacity(count);
1444            for _ in 0..count {
1445                let (child, _) = decode_geometry_record(reader, false, effective_srid)?;
1446                match child {
1447                    GeometryShape::LineString(line) => lines.push(line),
1448                    _ => {
1449                        return Err(DecodeError::InvalidSpatial(
1450                            "multilinestring child must be linestring".into(),
1451                        ))
1452                    }
1453                }
1454            }
1455            GeometryShape::MultiLineString(lines)
1456        }
1457        EWKB_MULTI_POLYGON => {
1458            let count = reader.read_u32_le()? as usize;
1459            let mut polys = Vec::with_capacity(count);
1460            for _ in 0..count {
1461                let (child, _) = decode_geometry_record(reader, false, effective_srid)?;
1462                match child {
1463                    GeometryShape::Polygon(poly) => polys.push(poly),
1464                    _ => {
1465                        return Err(DecodeError::InvalidSpatial(
1466                            "multipolygon child must be polygon".into(),
1467                        ))
1468                    }
1469                }
1470            }
1471            GeometryShape::MultiPolygon(polys)
1472        }
1473        EWKB_GEOMETRY_COLLECTION => {
1474            let count = reader.read_u32_le()? as usize;
1475            let mut shapes = Vec::with_capacity(count);
1476            for _ in 0..count {
1477                let (child, _) = decode_geometry_record(reader, false, effective_srid)?;
1478                shapes.push(child);
1479            }
1480            GeometryShape::GeometryCollection(shapes)
1481        }
1482        _ => {
1483            return Err(DecodeError::InvalidSpatial(format!(
1484                "unsupported EWKB base type {base_type}"
1485            )))
1486        }
1487    };
1488    Ok((shape, srid))
1489}
1490
1491fn decode_position(reader: &mut Reader<'_>, has_z: bool) -> Result<Position, DecodeError> {
1492    let x = reader.read_f64_le()?;
1493    let y = reader.read_f64_le()?;
1494    if has_z {
1495        let z = reader.read_f64_le()?;
1496        Ok(Position::Xyz { x, y, z })
1497    } else {
1498        Ok(Position::Xy { x, y })
1499    }
1500}
1501
1502struct Reader<'a> {
1503    bytes: &'a [u8],
1504    offset: usize,
1505}
1506
1507impl<'a> Reader<'a> {
1508    fn new(bytes: &'a [u8]) -> Self {
1509        Self { bytes, offset: 0 }
1510    }
1511
1512    fn is_empty(&self) -> bool {
1513        self.offset == self.bytes.len()
1514    }
1515
1516    fn read_u8(&mut self) -> Result<u8, DecodeError> {
1517        if self.offset >= self.bytes.len() {
1518            return Err(DecodeError::Truncated);
1519        }
1520        let value = self.bytes[self.offset];
1521        self.offset += 1;
1522        Ok(value)
1523    }
1524
1525    fn read_u32_le(&mut self) -> Result<u32, DecodeError> {
1526        let bytes = self.read_exact::<4>()?;
1527        Ok(u32::from_le_bytes(bytes))
1528    }
1529
1530    fn read_f64_le(&mut self) -> Result<f64, DecodeError> {
1531        let bytes = self.read_exact::<8>()?;
1532        Ok(f64::from_le_bytes(bytes))
1533    }
1534
1535    fn read_exact<const N: usize>(&mut self) -> Result<[u8; N], DecodeError> {
1536        if self.offset + N > self.bytes.len() {
1537            return Err(DecodeError::Truncated);
1538        }
1539        let mut out = [0u8; N];
1540        out.copy_from_slice(&self.bytes[self.offset..self.offset + N]);
1541        self.offset += N;
1542        Ok(out)
1543    }
1544}
1545
1546#[cfg(test)]
1547mod tests {
1548    use super::*;
1549    use serde_json::json;
1550    use std::convert::TryInto;
1551
1552    fn sample_ring() -> LinearRingValue {
1553        LinearRingValue::new(vec![
1554            Position::Xy { x: 0.0, y: 0.0 },
1555            Position::Xy { x: 4.0, y: 0.0 },
1556            Position::Xy { x: 4.0, y: 4.0 },
1557            Position::Xy { x: 0.0, y: 0.0 },
1558        ])
1559        .unwrap()
1560    }
1561
1562    fn sample_polygon() -> PolygonValue {
1563        PolygonValue::new(vec![sample_ring()]).unwrap()
1564    }
1565
1566    fn sample_xyz_ring() -> LinearRingValue {
1567        LinearRingValue::new(vec![
1568            Position::Xyz {
1569                x: 0.0,
1570                y: 0.0,
1571                z: 1.0,
1572            },
1573            Position::Xyz {
1574                x: 4.0,
1575                y: 0.0,
1576                z: 1.0,
1577            },
1578            Position::Xyz {
1579                x: 4.0,
1580                y: 4.0,
1581                z: 1.0,
1582            },
1583            Position::Xyz {
1584                x: 0.0,
1585                y: 0.0,
1586                z: 1.0,
1587            },
1588        ])
1589        .unwrap()
1590    }
1591
1592    fn sample_xyz_polygon() -> PolygonValue {
1593        PolygonValue::new(vec![sample_xyz_ring()]).unwrap()
1594    }
1595
1596    fn sample_shapes() -> Vec<GeometryShape> {
1597        vec![
1598            GeometryShape::Point(Position::Xy { x: 12.5, y: 55.7 }),
1599            GeometryShape::LineString(
1600                LineStringValue::new(vec![
1601                    Position::Xy { x: 12.5, y: 55.7 },
1602                    Position::Xy { x: 13.1, y: 56.0 },
1603                ])
1604                .unwrap(),
1605            ),
1606            GeometryShape::Polygon(sample_polygon()),
1607            GeometryShape::MultiPoint(vec![
1608                Position::Xyz {
1609                    x: 1.0,
1610                    y: 2.0,
1611                    z: 3.0,
1612                },
1613                Position::Xyz {
1614                    x: 4.0,
1615                    y: 5.0,
1616                    z: 6.0,
1617                },
1618            ]),
1619            GeometryShape::MultiLineString(vec![
1620                LineStringValue::new(vec![
1621                    Position::Xy { x: 0.0, y: 0.0 },
1622                    Position::Xy { x: 1.0, y: 1.0 },
1623                ])
1624                .unwrap(),
1625                LineStringValue::new(vec![
1626                    Position::Xy { x: 2.0, y: 2.0 },
1627                    Position::Xy { x: 3.0, y: 3.0 },
1628                ])
1629                .unwrap(),
1630            ]),
1631            GeometryShape::MultiPolygon(vec![
1632                sample_polygon(),
1633                PolygonValue::new(vec![LinearRingValue::new(vec![
1634                    Position::Xy { x: 10.0, y: 10.0 },
1635                    Position::Xy { x: 12.0, y: 10.0 },
1636                    Position::Xy { x: 12.0, y: 12.0 },
1637                    Position::Xy { x: 10.0, y: 10.0 },
1638                ])
1639                .unwrap()])
1640                .unwrap(),
1641            ]),
1642            GeometryShape::GeometryCollection(vec![
1643                GeometryShape::Point(Position::Xy { x: 12.5, y: 55.7 }),
1644                GeometryShape::LineString(
1645                    LineStringValue::new(vec![
1646                        Position::Xy { x: 12.5, y: 55.7 },
1647                        Position::Xy { x: 13.1, y: 56.0 },
1648                    ])
1649                    .unwrap(),
1650                ),
1651            ]),
1652        ]
1653    }
1654
1655    fn sample_xyz_non_point_shapes() -> Vec<GeometryShape> {
1656        vec![
1657            GeometryShape::LineString(
1658                LineStringValue::new(vec![
1659                    Position::Xyz {
1660                        x: 12.5,
1661                        y: 55.7,
1662                        z: 9.0,
1663                    },
1664                    Position::Xyz {
1665                        x: 13.1,
1666                        y: 56.0,
1667                        z: 10.5,
1668                    },
1669                ])
1670                .unwrap(),
1671            ),
1672            GeometryShape::Polygon(sample_xyz_polygon()),
1673            GeometryShape::MultiPoint(vec![
1674                Position::Xyz {
1675                    x: 1.0,
1676                    y: 2.0,
1677                    z: 3.0,
1678                },
1679                Position::Xyz {
1680                    x: 4.0,
1681                    y: 5.0,
1682                    z: 6.0,
1683                },
1684            ]),
1685            GeometryShape::MultiLineString(vec![
1686                LineStringValue::new(vec![
1687                    Position::Xyz {
1688                        x: 0.0,
1689                        y: 0.0,
1690                        z: 1.0,
1691                    },
1692                    Position::Xyz {
1693                        x: 1.0,
1694                        y: 1.0,
1695                        z: 1.0,
1696                    },
1697                ])
1698                .unwrap(),
1699                LineStringValue::new(vec![
1700                    Position::Xyz {
1701                        x: 2.0,
1702                        y: 2.0,
1703                        z: 2.0,
1704                    },
1705                    Position::Xyz {
1706                        x: 3.0,
1707                        y: 3.0,
1708                        z: 2.0,
1709                    },
1710                ])
1711                .unwrap(),
1712            ]),
1713            GeometryShape::MultiPolygon(vec![
1714                sample_xyz_polygon(),
1715                PolygonValue::new(vec![LinearRingValue::new(vec![
1716                    Position::Xyz {
1717                        x: 10.0,
1718                        y: 10.0,
1719                        z: 5.0,
1720                    },
1721                    Position::Xyz {
1722                        x: 12.0,
1723                        y: 10.0,
1724                        z: 5.0,
1725                    },
1726                    Position::Xyz {
1727                        x: 12.0,
1728                        y: 12.0,
1729                        z: 5.0,
1730                    },
1731                    Position::Xyz {
1732                        x: 10.0,
1733                        y: 10.0,
1734                        z: 5.0,
1735                    },
1736                ])
1737                .unwrap()])
1738                .unwrap(),
1739            ]),
1740            GeometryShape::GeometryCollection(vec![
1741                GeometryShape::Point(Position::Xyz {
1742                    x: 12.5,
1743                    y: 55.7,
1744                    z: 9.0,
1745                }),
1746                GeometryShape::LineString(
1747                    LineStringValue::new(vec![
1748                        Position::Xyz {
1749                            x: 12.5,
1750                            y: 55.7,
1751                            z: 9.0,
1752                        },
1753                        Position::Xyz {
1754                            x: 13.1,
1755                            y: 56.0,
1756                            z: 10.5,
1757                        },
1758                    ])
1759                    .unwrap(),
1760                ),
1761            ]),
1762        ]
1763    }
1764
1765    fn empty_non_point_shapes() -> Vec<(&'static str, GeometryShape)> {
1766        vec![
1767            (
1768                "LINESTRING EMPTY",
1769                GeometryShape::LineString(LineStringValue::new(vec![]).unwrap()),
1770            ),
1771            (
1772                "POLYGON EMPTY",
1773                GeometryShape::Polygon(PolygonValue::new(vec![]).unwrap()),
1774            ),
1775            ("MULTIPOINT EMPTY", GeometryShape::MultiPoint(vec![])),
1776            (
1777                "MULTILINESTRING EMPTY",
1778                GeometryShape::MultiLineString(vec![]),
1779            ),
1780            ("MULTIPOLYGON EMPTY", GeometryShape::MultiPolygon(vec![])),
1781            (
1782                "GEOMETRYCOLLECTION EMPTY",
1783                GeometryShape::GeometryCollection(vec![]),
1784            ),
1785        ]
1786    }
1787
1788    fn overwrite_u32_le(bytes: &mut [u8], offset: usize, value: u32) {
1789        bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
1790    }
1791
1792    #[test]
1793    fn point_blob_geojson_and_wkt_roundtrip_xyz() {
1794        let point = PointValue {
1795            srid: 4326,
1796            position: Position::Xyz {
1797                x: 12.5,
1798                y: 55.7,
1799                z: 9.0,
1800            },
1801        };
1802        assert_eq!(
1803            PointValue::decode_blob(&point.encode_blob().unwrap()).unwrap(),
1804            point
1805        );
1806        assert_eq!(
1807            PointValue::from_geojson_str(&point.to_geojson_string().unwrap()).unwrap(),
1808            point
1809        );
1810        assert_eq!(
1811            PointValue::from_wkt_str(&point.to_ewkt_string().unwrap()).unwrap(),
1812            point
1813        );
1814        assert_eq!(
1815            PointValue::from_wkt_str(&point.to_wkt_string().unwrap()).unwrap(),
1816            point
1817        );
1818    }
1819
1820    #[test]
1821    fn geometry_family_roundtrips_through_blob_geojson_and_wkt() {
1822        for shape in sample_shapes() {
1823            let geometry = GeometryValue {
1824                srid: 4326,
1825                shape: shape.clone(),
1826            };
1827            assert_eq!(
1828                GeometryValue::decode_blob(&geometry.encode_blob().unwrap()).unwrap(),
1829                geometry
1830            );
1831            assert_eq!(
1832                GeometryValue::from_geojson_str(&geometry.to_geojson_string().unwrap()).unwrap(),
1833                geometry
1834            );
1835            assert_eq!(
1836                GeometryValue::from_wkt_str(&geometry.to_ewkt_string().unwrap()).unwrap(),
1837                geometry
1838            );
1839            assert_eq!(
1840                GeometryValue::from_wkt_str(&geometry.to_wkt_string().unwrap()).unwrap(),
1841                geometry
1842            );
1843        }
1844    }
1845
1846    #[test]
1847    fn geography_family_roundtrips_through_blob_geojson_and_wkt() {
1848        for shape in sample_shapes() {
1849            let geography = GeographyValue {
1850                srid: 4326,
1851                shape: shape.clone(),
1852            };
1853            assert_eq!(
1854                GeographyValue::decode_blob(&geography.encode_blob().unwrap()).unwrap(),
1855                geography
1856            );
1857            assert_eq!(
1858                GeographyValue::from_geojson_str(&geography.to_geojson_string().unwrap()).unwrap(),
1859                geography
1860            );
1861            assert_eq!(
1862                GeographyValue::from_wkt_str(&geography.to_ewkt_string().unwrap()).unwrap(),
1863                geography
1864            );
1865            assert_eq!(
1866                GeographyValue::from_wkt_str(&geography.to_wkt_string().unwrap()).unwrap(),
1867                geography
1868            );
1869        }
1870    }
1871
1872    #[test]
1873    fn geometry_family_roundtrips_xyz_non_point_shapes() {
1874        for shape in sample_xyz_non_point_shapes() {
1875            let geometry = GeometryValue {
1876                srid: 4326,
1877                shape: shape.clone(),
1878            };
1879            assert_eq!(
1880                GeometryValue::decode_blob(&geometry.encode_blob().unwrap()).unwrap(),
1881                geometry
1882            );
1883            assert_eq!(
1884                GeometryValue::from_geojson_str(&geometry.to_geojson_string().unwrap()).unwrap(),
1885                geometry
1886            );
1887            assert_eq!(
1888                GeometryValue::from_wkt_str(&geometry.to_ewkt_string().unwrap()).unwrap(),
1889                geometry
1890            );
1891            assert_eq!(
1892                GeometryValue::from_wkt_str(&geometry.to_wkt_string().unwrap()).unwrap(),
1893                geometry
1894            );
1895        }
1896    }
1897
1898    #[test]
1899    fn geography_family_roundtrips_xyz_non_point_shapes() {
1900        for shape in sample_xyz_non_point_shapes() {
1901            let geography = GeographyValue {
1902                srid: 4326,
1903                shape: shape.clone(),
1904            };
1905            assert_eq!(
1906                GeographyValue::decode_blob(&geography.encode_blob().unwrap()).unwrap(),
1907                geography
1908            );
1909            assert_eq!(
1910                GeographyValue::from_geojson_str(&geography.to_geojson_string().unwrap()).unwrap(),
1911                geography
1912            );
1913            assert_eq!(
1914                GeographyValue::from_wkt_str(&geography.to_ewkt_string().unwrap()).unwrap(),
1915                geography
1916            );
1917            assert_eq!(
1918                GeographyValue::from_wkt_str(&geography.to_wkt_string().unwrap()).unwrap(),
1919                geography
1920            );
1921        }
1922    }
1923
1924    #[test]
1925    fn geojson_rejects_feature_wrappers() {
1926        let value = json!({
1927            "type": "Feature",
1928            "geometry": {"type": "Point", "coordinates": [1.0, 2.0]}
1929        });
1930        assert!(matches!(
1931            GeometryValue::from_geojson_value(&value),
1932            Err(DecodeError::InvalidSpatial(_))
1933        ));
1934    }
1935
1936    #[test]
1937    fn geojson_rejects_crs_objects() {
1938        let value = json!({
1939            "type": "Point",
1940            "coordinates": [1.0, 2.0],
1941            "crs": {"type": "name", "properties": {"name": "EPSG:4326"}}
1942        });
1943        assert!(matches!(
1944            GeometryValue::from_geojson_value(&value),
1945            Err(DecodeError::InvalidSpatial(_))
1946        ));
1947    }
1948
1949    #[test]
1950    fn multipoint_wkt_accepts_compact_form() {
1951        let value = GeometryValue::from_wkt_str("SRID=4326;MULTIPOINT (1 2, 3 4)").unwrap();
1952        assert_eq!(
1953            value,
1954            GeometryValue {
1955                srid: 4326,
1956                shape: GeometryShape::MultiPoint(vec![
1957                    Position::Xy { x: 1.0, y: 2.0 },
1958                    Position::Xy { x: 3.0, y: 4.0 },
1959                ]),
1960            }
1961        );
1962    }
1963
1964    #[test]
1965    fn geometry_empty_wkt_roundtrips_through_blob_and_wkt() {
1966        for (wkt, shape) in empty_non_point_shapes() {
1967            let geometry = GeometryValue::from_wkt_str(wkt).unwrap();
1968            assert_eq!(
1969                geometry,
1970                GeometryValue {
1971                    srid: 4326,
1972                    shape: shape.clone(),
1973                }
1974            );
1975            assert_eq!(
1976                GeometryValue::decode_blob(&geometry.encode_blob().unwrap()).unwrap(),
1977                geometry
1978            );
1979            assert_eq!(
1980                GeometryValue::from_wkt_str(&geometry.to_wkt_string().unwrap()).unwrap(),
1981                geometry
1982            );
1983        }
1984    }
1985
1986    #[test]
1987    fn geography_empty_wkt_roundtrips_through_blob_and_wkt() {
1988        for (wkt, shape) in empty_non_point_shapes() {
1989            let geography = GeographyValue::from_wkt_str(wkt).unwrap();
1990            assert_eq!(
1991                geography,
1992                GeographyValue {
1993                    srid: 4326,
1994                    shape: shape.clone(),
1995                }
1996            );
1997            assert_eq!(
1998                GeographyValue::decode_blob(&geography.encode_blob().unwrap()).unwrap(),
1999                geography
2000            );
2001            assert_eq!(
2002                GeographyValue::from_wkt_str(&geography.to_wkt_string().unwrap()).unwrap(),
2003                geography
2004            );
2005        }
2006    }
2007
2008    #[test]
2009    fn geography_geojson_defaults_to_4326_without_transforming_coordinates() {
2010        let geography = GeographyValue::from_geojson_value(&json!({
2011            "type": "LineString",
2012            "coordinates": [[12.5, 55.7], [13.1, 56.0]]
2013        }))
2014        .unwrap();
2015        assert_eq!(
2016            geography,
2017            GeographyValue {
2018                srid: 4326,
2019                shape: GeometryShape::LineString(
2020                    LineStringValue::new(vec![
2021                        Position::Xy { x: 12.5, y: 55.7 },
2022                        Position::Xy { x: 13.1, y: 56.0 },
2023                    ])
2024                    .unwrap(),
2025                ),
2026            }
2027        );
2028    }
2029
2030    #[test]
2031    fn point_empty_is_rejected_in_wkt() {
2032        assert!(matches!(
2033            PointValue::from_wkt_str("POINT EMPTY"),
2034            Err(DecodeError::InvalidSpatial(_))
2035        ));
2036        assert!(matches!(
2037            GeometryValue::from_wkt_str("POINT EMPTY"),
2038            Err(DecodeError::InvalidSpatial(_))
2039        ));
2040    }
2041
2042    #[test]
2043    fn wkt_rejects_m_and_zm_dimensions() {
2044        assert!(matches!(
2045            GeometryValue::from_wkt_str("POINT M (1 2 3)"),
2046            Err(DecodeError::InvalidSpatial(_))
2047        ));
2048        assert!(matches!(
2049            GeometryValue::from_wkt_str("POINT ZM (1 2 3 4)"),
2050            Err(DecodeError::InvalidSpatial(_))
2051        ));
2052    }
2053
2054    #[test]
2055    fn rejects_big_endian_ewkb_blob() {
2056        let mut blob = PointValue {
2057            srid: 4326,
2058            position: Position::Xy { x: 12.5, y: 55.7 },
2059        }
2060        .encode_blob()
2061        .unwrap();
2062        blob[1] = 0x00;
2063        assert!(matches!(
2064            PointValue::decode_blob(&blob),
2065            Err(DecodeError::NonCanonical(_))
2066        ));
2067    }
2068
2069    #[test]
2070    fn rejects_blob_without_top_level_srid() {
2071        let mut blob = PointValue {
2072            srid: 4326,
2073            position: Position::Xy { x: 12.5, y: 55.7 },
2074        }
2075        .encode_blob()
2076        .unwrap();
2077        let type_word = u32::from_le_bytes(blob[2..6].try_into().unwrap()) & !EWKB_SRID_FLAG;
2078        overwrite_u32_le(&mut blob, 2, type_word);
2079        blob.drain(6..10);
2080        assert!(matches!(
2081            PointValue::decode_blob(&blob),
2082            Err(DecodeError::NonCanonical(_))
2083        ));
2084    }
2085
2086    #[test]
2087    fn rejects_nested_geometry_srid_mismatch() {
2088        let mut blob = GeometryValue {
2089            srid: 4326,
2090            shape: GeometryShape::MultiPoint(vec![Position::Xy { x: 1.0, y: 2.0 }]),
2091        }
2092        .encode_blob()
2093        .unwrap();
2094        let child_type_word = u32::from_le_bytes(blob[15..19].try_into().unwrap()) | EWKB_SRID_FLAG;
2095        overwrite_u32_le(&mut blob, 15, child_type_word);
2096        blob.splice(19..19, 3857u32.to_le_bytes());
2097        assert!(matches!(
2098            GeometryValue::decode_blob(&blob),
2099            Err(DecodeError::InvalidSpatial(_))
2100        ));
2101    }
2102
2103    #[test]
2104    fn rejects_multipoint_child_with_wrong_subtype() {
2105        let mut blob = GeometryValue {
2106            srid: 4326,
2107            shape: GeometryShape::MultiPoint(vec![Position::Xy { x: 0.0, y: 0.0 }]),
2108        }
2109        .encode_blob()
2110        .unwrap();
2111        overwrite_u32_le(&mut blob, 15, EWKB_LINESTRING);
2112        assert!(matches!(
2113            GeometryValue::decode_blob(&blob),
2114            Err(DecodeError::InvalidSpatial(_))
2115        ));
2116    }
2117
2118    #[test]
2119    fn rejects_ewkb_m_dimension_flag() {
2120        let mut blob = PointValue {
2121            srid: 4326,
2122            position: Position::Xy { x: 12.5, y: 55.7 },
2123        }
2124        .encode_blob()
2125        .unwrap();
2126        let type_word = u32::from_le_bytes(blob[2..6].try_into().unwrap()) | EWKB_M_FLAG;
2127        overwrite_u32_le(&mut blob, 2, type_word);
2128        assert!(matches!(
2129            PointValue::decode_blob(&blob),
2130            Err(DecodeError::InvalidSpatial(_))
2131        ));
2132    }
2133}