1use crate::gpb::{self, GpbHeader};
20use crate::types::{GeometryType, GeometryTypeSet};
21
22use geo_traits::{
23 CoordTrait, Dimensions, GeometryTrait, LineStringTrait, LineTrait, MultiLineStringTrait,
24 MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, RectTrait, TriangleTrait,
25};
26use geo_traits::{GeometryCollectionTrait, GeometryType as GtGeometryType};
27use wkb::Endianness;
28use wkb::reader::{
29 GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, Wkb,
30};
31use wkb::writer::{WriteOptions, geometry_wkb_size, write_geometry};
32
33#[derive(Debug, thiserror::Error)]
35#[non_exhaustive]
36pub enum GeometryError {
37 #[error(transparent)]
39 Header(#[from] gpb::GpbError),
40 #[error("WKB body is unreadable (unsupported curve type or malformed geometry)")]
47 Body(#[from] wkb::error::WkbError),
48 #[error(
59 "a {container} containing a {member} cannot be written: a non-linear geometry is writable only as a body's own type"
60 )]
61 NonLinearMember {
62 container: GeometryType,
64 member: GeometryType,
66 },
67 #[error("WKB body truncated: need at least 5 bytes for the geometry type code")]
70 TruncatedWkb,
71 #[error("unknown WKB geometry type code {0}")]
74 UnknownWkbType(u32),
75 #[error("WKB geometry type code {0} is an abstract supertype and has no encoding")]
79 AbstractWkbType(u32),
80 #[error("WKB body truncated at byte {offset}")]
83 TruncatedAt {
84 offset: usize,
86 },
87 #[error("invalid WKB byte order marker at byte {offset}")]
90 InvalidByteOrder {
91 offset: usize,
93 },
94 #[error("WKB geometry nesting is too deep")]
96 NestingTooDeep,
97 #[error("failed to encode geometry to WKB")]
100 EncodeWkb(#[source] wkb::error::WkbError),
101}
102
103#[derive(Debug, Clone)]
109pub struct GpbGeometry<'a> {
110 header: GpbHeader,
111 body: &'a [u8],
112 wkb: Wkb<'a>,
113}
114
115impl<'a> GpbGeometry<'a> {
116 pub fn parse(blob: &'a [u8]) -> Result<Self, GeometryError> {
125 let (header, offset) = gpb::parse_header(blob)?;
126 let body = blob.get(offset..).unwrap_or_default();
130 let wkb = Wkb::try_new(body)?;
131 Ok(Self { header, body, wkb })
132 }
133
134 pub fn header(&self) -> &GpbHeader {
136 &self.header
137 }
138
139 pub fn wkb_body(&self) -> &'a [u8] {
145 self.body
146 }
147
148 pub fn wkb(&self) -> &Wkb<'a> {
150 &self.wkb
151 }
152
153 #[cfg(feature = "geo-types")]
159 pub fn to_geo(&self) -> Option<geo_types::Geometry<f64>> {
160 use geo_traits::to_geo::ToGeoGeometry;
161 self.wkb.try_to_geometry()
162 }
163
164 pub fn xy_envelope(&self) -> Option<[f64; 4]> {
175 let mut bounds = XyBounds::new();
176 visit_coords(&self.wkb, &mut |x, y, _| bounds.add(x, y));
177 bounds.finish()
178 }
179
180 pub fn is_empty(&self) -> bool {
187 self.header.empty || self.xy_envelope().is_none()
188 }
189
190 pub fn geometry_type(&self) -> GeometryType {
197 use wkb::reader::GeometryType as WkbType;
198 match self.wkb.geometry_type() {
199 WkbType::Point => GeometryType::Point,
200 WkbType::LineString => GeometryType::LineString,
201 WkbType::Polygon => GeometryType::Polygon,
202 WkbType::MultiPoint => GeometryType::MultiPoint,
203 WkbType::MultiLineString => GeometryType::MultiLineString,
204 WkbType::MultiPolygon => GeometryType::MultiPolygon,
205 WkbType::GeometryCollection => GeometryType::GeometryCollection,
206 _ => GeometryType::Geometry,
210 }
211 }
212
213 pub fn matches_declared(&self, declared: GeometryType) -> bool {
216 geometry_type_matches(self.geometry_type(), declared)
217 }
218}
219
220pub fn geometry_type_matches(actual: GeometryType, declared: GeometryType) -> bool {
236 use GeometryType::*;
237 if declared == Geometry || declared == actual {
238 return true;
239 }
240 if declared == GeometryCollection {
241 return matches!(
242 actual,
243 GeometryCollection
244 | MultiPoint
245 | MultiLineString
246 | MultiPolygon
247 | MultiCurve
248 | MultiSurface
249 );
250 }
251 false
252}
253
254fn body_xy_bounds(blob: &[u8]) -> Result<Option<[f64; 4]>, GeometryError> {
262 let (_, offset) = gpb::parse_header(blob)?;
263 let body = blob.get(offset..).unwrap_or_default();
265 if wkb_geometry_type(body)?.is_extension() {
266 crate::curve::xy_envelope(body)
267 } else {
268 Ok(GpbGeometry::parse(blob)?.xy_envelope())
269 }
270}
271
272pub fn blob_xy_envelope(blob: &[u8]) -> Result<Option<[f64; 4]>, GeometryError> {
282 let (header, _) = gpb::parse_header(blob)?;
283 if let Some((min_x, max_x, min_y, max_y)) = header.envelope.xy_bounds() {
284 return Ok(Some([min_x, max_x, min_y, max_y]));
285 }
286 body_xy_bounds(blob)
287}
288
289pub fn blob_is_empty(blob: &[u8]) -> Result<bool, GeometryError> {
299 let (header, _) = gpb::parse_header(blob)?;
300 if header.empty {
301 return Ok(true);
302 }
303 Ok(body_xy_bounds(blob)?.is_none())
304}
305
306pub fn blob_envelope_and_empty(blob: &[u8]) -> Result<(Option<[f64; 4]>, bool), GeometryError> {
319 let (header, _) = gpb::parse_header(blob)?;
320 if header.empty {
321 return Ok((None, true));
322 }
323 let Some(body_bounds) = body_xy_bounds(blob)? else {
324 return Ok((None, true));
325 };
326 let bounds = match header.envelope.xy_bounds() {
327 Some((min_x, max_x, min_y, max_y)) => [min_x, max_x, min_y, max_y],
328 None => body_bounds,
329 };
330 Ok((Some(bounds), false))
331}
332
333pub fn wkb_geometry_type(wkb_body: &[u8]) -> Result<GeometryType, GeometryError> {
342 let &[order, c0, c1, c2, c3, ..] = wkb_body else {
345 return Err(GeometryError::TruncatedWkb);
346 };
347 let little_endian = match order {
348 0 => false,
349 1 => true,
350 _ => return Err(GeometryError::TruncatedWkb),
351 };
352 let bytes = [c0, c1, c2, c3];
353 let code = if little_endian {
354 u32::from_le_bytes(bytes)
355 } else {
356 u32::from_be_bytes(bytes)
357 };
358 let base = if code & 0xE000_0000 != 0 {
361 code & 0x0000_00FF
362 } else {
363 code % 1000
364 };
365 GeometryType::from_wkb_base(base).ok_or(GeometryError::UnknownWkbType(code))
366}
367
368pub fn write_envelope<G: GeometryTrait<T = f64>>(geom: &G) -> (gpb::Envelope, bool) {
385 let mut bounds = XyzBounds::new();
386 visit_coords(geom, &mut |x, y, z| bounds.add(x, y, z));
387 let Some([min_x, max_x, min_y, max_y]) = bounds.xy_bounds() else {
388 return (gpb::Envelope::None, true);
389 };
390 match bounds.z_bounds() {
391 Some((min_z, max_z)) => (
392 gpb::Envelope::Xyz([min_x, max_x, min_y, max_y, min_z, max_z]),
393 false,
394 ),
395 None => (gpb::Envelope::Xy([min_x, max_x, min_y, max_y]), false),
396 }
397}
398
399pub fn encode_gpb_from_wkb(wkb_body: &[u8], srs_id: i32) -> Result<EncodedGpb, GeometryError> {
432 if wkb_geometry_type(wkb_body)?.is_extension() {
436 let scan = crate::curve::scan(wkb_body)?;
437 let body = wkb_body.get(..scan.len).unwrap_or(wkb_body);
438 let mut blob = Vec::with_capacity(gpb::header_len(&scan.envelope) + body.len());
439 gpb::encode_header_into(&mut blob, srs_id, &scan.envelope, scan.empty, true);
440 blob.extend_from_slice(body);
441 return Ok(EncodedGpb {
442 blob,
443 xy_envelope: scan.xy_envelope,
444 dimensions: scan.dimensions,
445 extension_types: scan.extension_types,
446 });
447 }
448
449 let geometry = match Wkb::try_new(wkb_body) {
450 Ok(geometry) => geometry,
451 Err(error) => return Err(non_linear_member(wkb_body).unwrap_or_else(|| error.into())),
455 };
456 let (envelope, empty) = write_envelope(&geometry);
457 let xy_envelope = envelope
458 .xy_bounds()
459 .map(|(min_x, max_x, min_y, max_y)| [min_x, max_x, min_y, max_y]);
460 let body = geometry.buf();
461 let mut blob = Vec::with_capacity(gpb::header_len(&envelope) + body.len());
464 gpb::encode_header_into(&mut blob, srs_id, &envelope, empty, false);
465 blob.extend_from_slice(body);
466 Ok(EncodedGpb {
467 blob,
468 xy_envelope,
469 dimensions: geometry.dim(),
470 extension_types: GeometryTypeSet::new(),
473 })
474}
475
476fn non_linear_member(wkb_body: &[u8]) -> Option<GeometryError> {
483 let container = wkb_geometry_type(wkb_body).ok()?;
484 let member = crate::curve::scan(wkb_body)
485 .ok()?
486 .extension_types
487 .iter()
488 .next()?;
489 Some(GeometryError::NonLinearMember { container, member })
490}
491
492#[derive(Debug, Clone, PartialEq)]
494pub struct EncodedGpb {
495 pub blob: Vec<u8>,
497 pub xy_envelope: Option<[f64; 4]>,
499 pub dimensions: Dimensions,
501 pub extension_types: GeometryTypeSet,
508}
509
510pub fn encode_gpb<G: GeometryTrait<T = f64>>(
525 geom: &G,
526 srs_id: i32,
527) -> Result<(Vec<u8>, Option<[f64; 4]>), GeometryError> {
528 let (envelope, empty) = write_envelope(geom);
529 let xy = envelope
530 .xy_bounds()
531 .map(|(min_x, max_x, min_y, max_y)| [min_x, max_x, min_y, max_y]);
532 let mut blob = Vec::with_capacity(gpb::header_len(&envelope) + geometry_wkb_size(geom));
535 gpb::encode_header_into(&mut blob, srs_id, &envelope, empty, false);
536 let options = WriteOptions {
537 endianness: Endianness::LittleEndian,
538 };
539 write_geometry(&mut blob, geom, &options).map_err(GeometryError::EncodeWkb)?;
540 Ok((blob, xy))
541}
542
543#[derive(Debug, Clone, Copy)]
548pub(crate) struct XyBounds {
549 min_x: f64,
550 max_x: f64,
551 min_y: f64,
552 max_y: f64,
553 seen: bool,
554}
555
556impl XyBounds {
557 pub(crate) fn new() -> Self {
558 Self {
559 min_x: f64::INFINITY,
560 max_x: f64::NEG_INFINITY,
561 min_y: f64::INFINITY,
562 max_y: f64::NEG_INFINITY,
563 seen: false,
564 }
565 }
566
567 pub(crate) fn add(&mut self, x: f64, y: f64) {
568 if !x.is_finite() || !y.is_finite() {
569 return;
570 }
571 self.min_x = self.min_x.min(x);
572 self.max_x = self.max_x.max(x);
573 self.min_y = self.min_y.min(y);
574 self.max_y = self.max_y.max(y);
575 self.seen = true;
576 }
577
578 pub(crate) fn finish(self) -> Option<[f64; 4]> {
579 self.seen
580 .then_some([self.min_x, self.max_x, self.min_y, self.max_y])
581 }
582}
583
584#[derive(Debug, Clone, Copy)]
590pub(crate) struct XyzBounds {
591 xy: XyBounds,
592 min_z: f64,
593 max_z: f64,
594 seen_z: bool,
595}
596
597impl XyzBounds {
598 pub(crate) fn new() -> Self {
599 Self {
600 xy: XyBounds::new(),
601 min_z: f64::INFINITY,
602 max_z: f64::NEG_INFINITY,
603 seen_z: false,
604 }
605 }
606
607 pub(crate) fn add(&mut self, x: f64, y: f64, z: Option<f64>) {
608 self.xy.add(x, y);
609 if let Some(z) = z
610 && z.is_finite()
611 {
612 self.min_z = self.min_z.min(z);
613 self.max_z = self.max_z.max(z);
614 self.seen_z = true;
615 }
616 }
617
618 pub(crate) fn xy_bounds(&self) -> Option<[f64; 4]> {
621 self.xy.finish()
622 }
623
624 pub(crate) fn z_bounds(&self) -> Option<(f64, f64)> {
626 self.seen_z.then_some((self.min_z, self.max_z))
627 }
628}
629
630fn coord_xyz(coord: &impl CoordTrait<T = f64>) -> (f64, f64, Option<f64>) {
635 let z = match coord.dim() {
636 Dimensions::Xyz | Dimensions::Xyzm => coord.nth(2),
637 _ => None,
638 };
639 (coord.x(), coord.y(), z)
640}
641
642fn visit_point(point: &impl PointTrait<T = f64>, visit: &mut impl FnMut(f64, f64, Option<f64>)) {
643 if let Some(coord) = point.coord() {
644 let (x, y, z) = coord_xyz(&coord);
645 visit(x, y, z);
646 }
647}
648
649fn visit_line_string(
650 line_string: &impl LineStringTrait<T = f64>,
651 visit: &mut impl FnMut(f64, f64, Option<f64>),
652) {
653 for coord in line_string.coords() {
654 let (x, y, z) = coord_xyz(&coord);
655 visit(x, y, z);
656 }
657}
658
659fn visit_polygon(
660 polygon: &impl PolygonTrait<T = f64>,
661 visit: &mut impl FnMut(f64, f64, Option<f64>),
662) {
663 if let Some(exterior) = polygon.exterior() {
664 visit_line_string(&exterior, visit);
665 }
666 for interior in polygon.interiors() {
667 visit_line_string(&interior, visit);
668 }
669}
670
671fn visit_coords<G: GeometryTrait<T = f64>>(
676 geom: &G,
677 visit: &mut impl FnMut(f64, f64, Option<f64>),
678) {
679 match geom.as_type() {
680 GtGeometryType::Point(point) => visit_point(point, visit),
681 GtGeometryType::LineString(line_string) => visit_line_string(line_string, visit),
682 GtGeometryType::Polygon(polygon) => visit_polygon(polygon, visit),
683 GtGeometryType::MultiPoint(multi_point) => {
684 for point in multi_point.points() {
685 visit_point(&point, visit);
686 }
687 }
688 GtGeometryType::MultiLineString(multi_line_string) => {
689 for line_string in multi_line_string.line_strings() {
690 visit_line_string(&line_string, visit);
691 }
692 }
693 GtGeometryType::MultiPolygon(multi_polygon) => {
694 for polygon in multi_polygon.polygons() {
695 visit_polygon(&polygon, visit);
696 }
697 }
698 GtGeometryType::GeometryCollection(collection) => {
699 for member in collection.geometries() {
700 visit_coords(&member, visit);
701 }
702 }
703 GtGeometryType::Rect(rect) => {
706 let (min, max) = (rect.min(), rect.max());
707 let (min_x, min_y, min_z) = coord_xyz(&min);
708 let (max_x, max_y, max_z) = coord_xyz(&max);
709 visit(min_x, min_y, min_z);
710 visit(max_x, max_y, max_z);
711 }
712 GtGeometryType::Triangle(triangle) => {
713 for coord in triangle.coords() {
714 let (x, y, z) = coord_xyz(&coord);
715 visit(x, y, z);
716 }
717 }
718 GtGeometryType::Line(line) => {
719 for coord in line.coords() {
720 let (x, y, z) = coord_xyz(&coord);
721 visit(x, y, z);
722 }
723 }
724 }
725}
726
727impl<'a> GeometryTrait for GpbGeometry<'a> {
728 type T = f64;
729 type PointType<'b>
730 = Point<'a>
731 where
732 Self: 'b;
733 type LineStringType<'b>
734 = LineString<'a>
735 where
736 Self: 'b;
737 type PolygonType<'b>
738 = Polygon<'a>
739 where
740 Self: 'b;
741 type MultiPointType<'b>
742 = MultiPoint<'a>
743 where
744 Self: 'b;
745 type MultiLineStringType<'b>
746 = MultiLineString<'a>
747 where
748 Self: 'b;
749 type MultiPolygonType<'b>
750 = MultiPolygon<'a>
751 where
752 Self: 'b;
753 type GeometryCollectionType<'b>
754 = GeometryCollection<'a>
755 where
756 Self: 'b;
757 type RectType<'b>
758 = geo_traits::UnimplementedRect<f64>
759 where
760 Self: 'b;
761 type TriangleType<'b>
762 = geo_traits::UnimplementedTriangle<f64>
763 where
764 Self: 'b;
765 type LineType<'b>
766 = geo_traits::UnimplementedLine<f64>
767 where
768 Self: 'b;
769
770 fn dim(&self) -> Dimensions {
771 self.wkb.dim()
772 }
773
774 fn as_type(
775 &self,
776 ) -> geo_traits::GeometryType<
777 '_,
778 Self::PointType<'_>,
779 Self::LineStringType<'_>,
780 Self::PolygonType<'_>,
781 Self::MultiPointType<'_>,
782 Self::MultiLineStringType<'_>,
783 Self::MultiPolygonType<'_>,
784 Self::GeometryCollectionType<'_>,
785 Self::RectType<'_>,
786 Self::TriangleType<'_>,
787 Self::LineType<'_>,
788 > {
789 self.wkb.as_type()
790 }
791}
792
793#[cfg(test)]
794mod tests {
795 use super::*;
796 use crate::gpb::{Envelope, encode_header};
797 use geo_traits::GeometryType as GtType;
798
799 fn gpb(body: &[u8]) -> Vec<u8> {
801 let mut blob = encode_header(4326, &Envelope::None, false, false);
802 blob.extend_from_slice(body);
803 blob
804 }
805
806 fn wkb_point(x: f64, y: f64) -> Vec<u8> {
808 let mut b = vec![1u8];
809 b.extend_from_slice(&1u32.to_le_bytes());
810 b.extend_from_slice(&x.to_le_bytes());
811 b.extend_from_slice(&y.to_le_bytes());
812 b
813 }
814
815 #[test]
816 fn parses_point_and_exposes_header_and_body() {
817 let body = wkb_point(3.0, 4.0);
818 let blob = gpb(&body);
819 let g = GpbGeometry::parse(&blob).unwrap();
820 assert_eq!(g.header().srs_id, 4326);
821 assert_eq!(g.wkb_body(), body.as_slice());
822 }
823
824 #[test]
825 #[expect(
826 clippy::float_cmp,
827 reason = "asserting the exact bit-level round-trip of the coordinate through WKB; the values are written and read as literals, so exact equality is the property under test"
828 )]
829 fn delegates_geometry_trait_to_wkb() {
830 let blob = gpb(&wkb_point(3.0, 4.0));
831 let g = GpbGeometry::parse(&blob).unwrap();
832 match g.as_type() {
833 GtType::Point(p) => {
834 use geo_traits::{CoordTrait, PointTrait};
835 let c = p.coord().unwrap();
836 assert_eq!(c.x(), 3.0);
837 assert_eq!(c.y(), 4.0);
838 }
839 _ => panic!("expected a point"),
840 }
841 }
842
843 #[test]
844 fn arbitrary_bytes_error_never_panic() {
845 GpbGeometry::parse(b"").unwrap_err();
846 GpbGeometry::parse(b"GP").unwrap_err();
847 GpbGeometry::parse(&gpb(&[])).unwrap_err();
849 GpbGeometry::parse(&gpb(&[1])).unwrap_err();
851 GpbGeometry::parse(&gpb(&wkb_point(1.0, 2.0)[..10])).unwrap_err();
853 }
854
855 #[test]
856 #[cfg(feature = "geo-types")]
857 fn to_geo_yields_geo_types() {
858 let blob = gpb(&wkb_point(3.0, 4.0));
859 let g = GpbGeometry::parse(&blob).unwrap();
860 let geo = g.to_geo().unwrap();
861 assert_eq!(
862 geo,
863 geo_types::Geometry::Point(geo_types::Point::new(3.0, 4.0))
864 );
865 }
866
867 #[test]
868 fn point_envelope_from_traversal() {
869 let blob = gpb(&wkb_point(3.0, -4.0));
870 let g = GpbGeometry::parse(&blob).unwrap();
871 assert_eq!(g.xy_envelope(), Some([3.0, 3.0, -4.0, -4.0]));
872 assert!(!g.is_empty());
873 }
874
875 #[test]
876 fn empty_point_is_empty_no_envelope() {
877 let blob = gpb(&wkb_point(f64::NAN, f64::NAN));
878 let g = GpbGeometry::parse(&blob).unwrap();
879 assert_eq!(g.xy_envelope(), None);
880 assert!(g.is_empty());
881 }
882
883 #[test]
884 fn header_empty_flag_reports_empty() {
885 let mut blob = encode_header(4326, &Envelope::None, true, false);
887 blob.extend_from_slice(&wkb_point(1.0, 2.0));
888 let g = GpbGeometry::parse(&blob).unwrap();
889 assert!(g.is_empty());
890 }
891
892 #[cfg(feature = "geo-types")]
893 fn wkb_body(geom: &geo_types::Geometry<f64>, little_endian: bool) -> Vec<u8> {
894 use wkb::Endianness;
895 use wkb::writer::{WriteOptions, write_geometry};
896 let options = WriteOptions {
897 endianness: if little_endian {
898 Endianness::LittleEndian
899 } else {
900 Endianness::BigEndian
901 },
902 };
903 let mut buf = Vec::new();
904 write_geometry(&mut buf, geom, &options).unwrap();
905 buf
906 }
907
908 #[test]
912 #[cfg(feature = "geo-types")]
913 fn traversal_bounds_equal_header_envelope() {
914 use geo_types::{
915 Geometry, LineString, MultiLineString, MultiPolygon, Point, Polygon, coord,
916 };
917
918 let line: Geometry<f64> =
919 LineString::from(vec![(0.0, 0.0), (10.0, -3.0), (4.0, 8.0)]).into();
920 let polygon: Geometry<f64> = Polygon::new(
921 LineString::from(vec![
922 (0.0, 0.0),
923 (6.0, 0.0),
924 (6.0, 5.0),
925 (0.0, 5.0),
926 (0.0, 0.0),
927 ]),
928 vec![LineString::from(vec![
929 (1.0, 1.0),
930 (2.0, 1.0),
931 (2.0, 2.0),
932 (1.0, 1.0),
933 ])],
934 )
935 .into();
936 let multipolygon: Geometry<f64> = MultiPolygon::new(vec![
937 Polygon::new(
938 LineString::from(vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
939 vec![],
940 ),
941 Polygon::new(
942 LineString::from(vec![(-5.0, -5.0), (-4.0, -5.0), (-4.0, -4.0), (-5.0, -5.0)]),
943 vec![],
944 ),
945 ])
946 .into();
947 let multiline: Geometry<f64> = MultiLineString::new(vec![
948 LineString::from(vec![(0.0, 0.0), (3.0, 3.0)]),
949 LineString::from(vec![(-2.0, 7.0), (9.0, -1.0)]),
950 ])
951 .into();
952 let point: Geometry<f64> = Point::from(coord! { x: 2.5, y: -6.0 }).into();
953
954 let cases: [(Geometry<f64>, [f64; 4]); 5] = [
955 (point, [2.5, 2.5, -6.0, -6.0]),
956 (line, [0.0, 10.0, -3.0, 8.0]),
957 (polygon, [0.0, 6.0, 0.0, 5.0]),
958 (multipolygon, [-5.0, 1.0, -5.0, 1.0]),
959 (multiline, [-2.0, 9.0, -1.0, 7.0]),
960 ];
961
962 for (geom, bounds) in cases {
963 for little_endian in [true, false] {
964 let mut blob = encode_header(4326, &Envelope::Xy(bounds), false, false);
965 blob.extend_from_slice(&wkb_body(&geom, little_endian));
966 let g = GpbGeometry::parse(&blob).unwrap();
967 let (bx0, bx1, by0, by1) = g.header().envelope.xy_bounds().unwrap();
968 assert_eq!(
969 g.xy_envelope(),
970 Some([bx0, bx1, by0, by1]),
971 "traversal must match header envelope (little_endian={little_endian})"
972 );
973 assert!(!g.is_empty());
974 }
975 }
976 }
977
978 #[test]
979 #[cfg(feature = "geo-types")]
980 fn geometry_collection_traversal() {
981 use geo_types::{Geometry, GeometryCollection, LineString, Point};
982 let members: Vec<Geometry<f64>> = vec![
983 Geometry::Point(Point::new(1.0, 1.0)),
984 Geometry::LineString(LineString::from(vec![(-3.0, 0.0), (4.0, 9.0)])),
985 ];
986 let gc = Geometry::GeometryCollection(GeometryCollection::new_from(members));
987 let mut blob = encode_header(4326, &Envelope::None, false, false);
988 blob.extend_from_slice(&wkb_body(&gc, true));
989 let g = GpbGeometry::parse(&blob).unwrap();
990 assert_eq!(g.xy_envelope(), Some([-3.0, 4.0, 0.0, 9.0]));
991 }
992
993 #[test]
995 fn big_endian_linestring_traversal() {
996 let mut body = vec![0u8]; body.extend_from_slice(&2u32.to_be_bytes()); body.extend_from_slice(&2u32.to_be_bytes()); for (x, y) in [(1.0f64, 2.0f64), (5.0, -1.0)] {
1000 body.extend_from_slice(&x.to_be_bytes());
1001 body.extend_from_slice(&y.to_be_bytes());
1002 }
1003 let blob = gpb(&body);
1004 let g = GpbGeometry::parse(&blob).unwrap();
1005 assert_eq!(g.xy_envelope(), Some([1.0, 5.0, -1.0, 2.0]));
1006 }
1007
1008 #[test]
1009 fn declared_type_matching_rules() {
1010 use GeometryType::*;
1011 assert!(geometry_type_matches(Point, Point));
1013 assert!(geometry_type_matches(LineString, LineString));
1014 assert!(geometry_type_matches(Point, Geometry));
1016 assert!(geometry_type_matches(CircularString, Geometry));
1017 assert!(geometry_type_matches(MultiPoint, GeometryCollection));
1019 assert!(geometry_type_matches(MultiSurface, GeometryCollection));
1020 assert!(geometry_type_matches(
1021 GeometryCollection,
1022 GeometryCollection
1023 ));
1024 assert!(!geometry_type_matches(Point, GeometryCollection));
1025 assert!(!geometry_type_matches(LineString, MultiLineString));
1027 assert!(!geometry_type_matches(Point, LineString));
1028 }
1029
1030 #[test]
1031 fn reads_wkb_type_code_including_curves() {
1032 assert_eq!(
1034 wkb_geometry_type(&wkb_point(0.0, 0.0)).unwrap(),
1035 GeometryType::Point
1036 );
1037 let mut point_zm = vec![1u8];
1039 point_zm.extend_from_slice(&3001u32.to_le_bytes());
1040 assert_eq!(wkb_geometry_type(&point_zm).unwrap(), GeometryType::Point);
1041 let mut circular = vec![1u8];
1044 circular.extend_from_slice(&8u32.to_le_bytes());
1045 assert_eq!(
1046 wkb_geometry_type(&circular).unwrap(),
1047 GeometryType::CircularString
1048 );
1049 assert!(matches!(
1051 wkb_geometry_type(b"\x01\x00"),
1052 Err(GeometryError::TruncatedWkb)
1053 ));
1054 let mut unknown = vec![1u8];
1055 unknown.extend_from_slice(&99u32.to_le_bytes());
1056 assert!(matches!(
1057 wkb_geometry_type(&unknown),
1058 Err(GeometryError::UnknownWkbType(99))
1059 ));
1060 }
1061
1062 #[test]
1063 fn matches_declared_on_parsed_geometry() {
1064 let blob = gpb(&wkb_point(1.0, 2.0));
1065 let g = GpbGeometry::parse(&blob).unwrap();
1066 assert_eq!(g.geometry_type(), GeometryType::Point);
1067 assert!(g.matches_declared(GeometryType::Point));
1068 assert!(g.matches_declared(GeometryType::Geometry));
1069 assert!(!g.matches_declared(GeometryType::LineString));
1070 }
1071
1072 fn wkb_point_z(x: f64, y: f64, z: f64) -> Vec<u8> {
1074 let mut b = vec![1u8];
1075 b.extend_from_slice(&1001u32.to_le_bytes());
1076 b.extend_from_slice(&x.to_le_bytes());
1077 b.extend_from_slice(&y.to_le_bytes());
1078 b.extend_from_slice(&z.to_le_bytes());
1079 b
1080 }
1081
1082 #[test]
1083 #[cfg(feature = "geo-types")]
1084 fn encode_gpb_xy_point_roundtrips() {
1085 let point = geo_types::Point::new(3.0, 4.0);
1086 let (blob, xy) = encode_gpb(&point, 4326).unwrap();
1087 assert_eq!(xy, Some([3.0, 3.0, 4.0, 4.0]));
1088 let g = GpbGeometry::parse(&blob).unwrap();
1089 assert_eq!(g.header().srs_id, 4326);
1090 assert_eq!(g.header().envelope, Envelope::Xy([3.0, 3.0, 4.0, 4.0]));
1091 assert!(!g.header().empty);
1092 assert_eq!(g.geometry_type(), GeometryType::Point);
1093 }
1094
1095 #[test]
1096 #[cfg(feature = "geo-types")]
1097 fn encode_gpb_linestring_envelope() {
1098 let ls = geo_types::LineString::from(vec![(0.0, 0.0), (10.0, -3.0), (4.0, 8.0)]);
1099 let (blob, xy) = encode_gpb(&ls, 4326).unwrap();
1100 assert_eq!(xy, Some([0.0, 10.0, -3.0, 8.0]));
1101 let g = GpbGeometry::parse(&blob).unwrap();
1102 assert_eq!(g.header().envelope, Envelope::Xy([0.0, 10.0, -3.0, 8.0]));
1103 assert_eq!(g.geometry_type(), GeometryType::LineString);
1104 }
1105
1106 #[test]
1107 fn encode_gpb_z_point_writes_xyz_envelope() {
1108 let src_blob = gpb(&wkb_point_z(1.0, 2.0, 9.0));
1112 let src = GpbGeometry::parse(&src_blob).unwrap();
1113 assert_eq!(src.dim(), Dimensions::Xyz);
1114 let (blob, xy) = encode_gpb(&src, 4326).unwrap();
1115 assert_eq!(xy, Some([1.0, 1.0, 2.0, 2.0]));
1116 let g = GpbGeometry::parse(&blob).unwrap();
1117 assert_eq!(
1118 g.header().envelope,
1119 Envelope::Xyz([1.0, 1.0, 2.0, 2.0, 9.0, 9.0])
1120 );
1121 assert_eq!(g.dim(), Dimensions::Xyz);
1122 assert_eq!(g.geometry_type(), GeometryType::Point);
1123 }
1124
1125 #[test]
1126 fn write_envelope_empty_point_is_empty() {
1127 let blob = gpb(&wkb_point(f64::NAN, f64::NAN));
1128 let g = GpbGeometry::parse(&blob).unwrap();
1129 let (envelope, empty) = write_envelope(&g);
1130 assert_eq!(envelope, Envelope::None);
1131 assert!(empty);
1132 let (reblob, xy) = encode_gpb(&g, 4326).unwrap();
1134 assert_eq!(xy, None);
1135 let re = GpbGeometry::parse(&reblob).unwrap();
1136 assert_eq!(re.header().envelope, Envelope::None);
1137 assert!(re.header().empty);
1138 }
1139}
1140
1141#[cfg(test)]
1142mod encode_from_wkb_tests {
1143 use super::*;
1144 use geo_types::{Geometry, LineString, Point};
1145
1146 #[test]
1151 fn agrees_with_encode_gpb() {
1152 let cases: Vec<Geometry<f64>> = vec![
1153 Geometry::Point(Point::new(1.5, -2.5)),
1154 Geometry::LineString(LineString::from(vec![(0.0, 0.0), (10.0, 5.0), (-3.0, 7.5)])),
1155 ];
1156 for geometry in cases {
1157 let (expected, expected_xy) = encode_gpb(&geometry, 4326).unwrap();
1158 let (_, offset) = gpb::parse_header(&expected).unwrap();
1160 let actual = encode_gpb_from_wkb(&expected[offset..], 4326).unwrap();
1161 assert_eq!(actual.blob, expected, "blob differs");
1162 assert_eq!(actual.xy_envelope, expected_xy, "envelope differs");
1163 }
1164 }
1165
1166 #[test]
1167 fn trailing_bytes_are_not_copied() {
1168 let (blob, _) = encode_gpb(&Point::new(3.0, 4.0), 4326).unwrap();
1169 let (_, offset) = gpb::parse_header(&blob).unwrap();
1170 let mut body = blob[offset..].to_vec();
1171 let clean = encode_gpb_from_wkb(&body, 4326).unwrap().blob;
1172 body.extend_from_slice(b"trailing rubbish");
1173 let padded = encode_gpb_from_wkb(&body, 4326).unwrap().blob;
1174 assert_eq!(clean, padded, "trailing bytes reached the blob");
1175 }
1176
1177 #[test]
1178 fn a_body_that_is_not_wkb_is_rejected() {
1179 encode_gpb_from_wkb(&[], 4326).unwrap_err();
1180 encode_gpb_from_wkb(b"not wkb at all", 4326).unwrap_err();
1181 encode_gpb_from_wkb(&[1, 0xff, 0xff, 0, 0], 4326).unwrap_err();
1183 }
1184}