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}