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)")]
49 Body(#[from] wkb::error::WkbError),
50 #[error(
58 "a {container} containing a {member} cannot be written: a non-linear geometry is writable only as a body's own type"
59 )]
60 NonLinearMember {
61 container: GeometryType,
63 member: GeometryType,
65 },
66 #[error("WKB body truncated: need at least 5 bytes for the geometry type code")]
69 TruncatedWkb,
70 #[error("unknown WKB geometry type code {0}")]
73 UnknownWkbType(u32),
74 #[error("WKB geometry type code {0} is an abstract supertype and has no encoding")]
78 AbstractWkbType(u32),
79 #[error("WKB body truncated at byte {offset}")]
82 TruncatedAt {
83 offset: usize,
85 },
86 #[error("invalid WKB byte order marker at byte {offset}")]
89 InvalidByteOrder {
90 offset: usize,
92 },
93 #[error("WKB geometry nesting is too deep")]
95 NestingTooDeep,
96 #[error("failed to encode geometry to WKB")]
99 EncodeWkb(#[source] wkb::error::WkbError),
100}
101
102#[derive(Debug, Clone)]
108pub struct GpbGeometry<'a> {
109 header: GpbHeader,
110 body: &'a [u8],
111 wkb: Wkb<'a>,
112}
113
114impl<'a> GpbGeometry<'a> {
115 #[hotpath::measure(label = "GpbGeometry::parse")]
124 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]> {
176 let mut bounds = XyBounds::new();
177 visit_coords(&self.wkb, &mut |x, y, _| bounds.add(x, y));
178 bounds.finish()
179 }
180
181 pub fn is_empty(&self) -> bool {
188 self.header.empty || self.xy_envelope().is_none()
189 }
190
191 pub fn geometry_type(&self) -> GeometryType {
199 use wkb::reader::GeometryType as WkbType;
200 match self.wkb.geometry_type() {
201 WkbType::Point => GeometryType::Point,
202 WkbType::LineString => GeometryType::LineString,
203 WkbType::Polygon => GeometryType::Polygon,
204 WkbType::MultiPoint => GeometryType::MultiPoint,
205 WkbType::MultiLineString => GeometryType::MultiLineString,
206 WkbType::MultiPolygon => GeometryType::MultiPolygon,
207 WkbType::GeometryCollection => GeometryType::GeometryCollection,
208 _ => GeometryType::Geometry,
212 }
213 }
214
215 pub fn matches_declared(&self, declared: GeometryType) -> bool {
218 geometry_type_matches(self.geometry_type(), declared)
219 }
220}
221
222pub fn geometry_type_matches(actual: GeometryType, declared: GeometryType) -> bool {
238 use GeometryType::*;
239 if declared == Geometry || declared == actual {
240 return true;
241 }
242 if declared == GeometryCollection {
243 return matches!(
244 actual,
245 GeometryCollection
246 | MultiPoint
247 | MultiLineString
248 | MultiPolygon
249 | MultiCurve
250 | MultiSurface
251 );
252 }
253 false
254}
255
256fn body_xy_bounds(blob: &[u8]) -> Result<Option<[f64; 4]>, GeometryError> {
265 let (_, offset) = gpb::parse_header(blob)?;
266 let body = blob.get(offset..).unwrap_or_default();
268 crate::curve::xy_envelope(body)
269}
270
271pub fn blob_xy_envelope(blob: &[u8]) -> Result<Option<[f64; 4]>, GeometryError> {
281 let (header, _) = gpb::parse_header(blob)?;
282 if let Some((min_x, max_x, min_y, max_y)) = header.envelope.xy_bounds() {
283 return Ok(Some([min_x, max_x, min_y, max_y]));
284 }
285 body_xy_bounds(blob)
286}
287
288pub fn blob_is_empty(blob: &[u8]) -> Result<bool, GeometryError> {
298 let (header, _) = gpb::parse_header(blob)?;
299 if header.empty {
300 return Ok(true);
301 }
302 Ok(body_xy_bounds(blob)?.is_none())
303}
304
305#[hotpath::measure(label = "geometry::blob_envelope_and_empty")]
318pub 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
333#[hotpath::measure(label = "geometry::wkb_geometry_type")]
342pub fn wkb_geometry_type(wkb_body: &[u8]) -> Result<GeometryType, GeometryError> {
343 let &[order, c0, c1, c2, c3, ..] = wkb_body else {
346 return Err(GeometryError::TruncatedWkb);
347 };
348 let little_endian = match order {
349 0 => false,
350 1 => true,
351 _ => return Err(GeometryError::TruncatedWkb),
352 };
353 let bytes = [c0, c1, c2, c3];
354 let code = if little_endian {
355 u32::from_le_bytes(bytes)
356 } else {
357 u32::from_be_bytes(bytes)
358 };
359 let base = if code & 0xE000_0000 != 0 {
362 code & 0x0000_00FF
363 } else {
364 code % 1000
365 };
366 GeometryType::from_wkb_base(base).ok_or(GeometryError::UnknownWkbType(code))
367}
368
369pub fn write_envelope<G: GeometryTrait<T = f64>>(geom: &G) -> (gpb::Envelope, bool) {
386 let mut bounds = XyzBounds::new();
387 visit_coords(geom, &mut |x, y, z| bounds.add(x, y, z));
388 let Some([min_x, max_x, min_y, max_y]) = bounds.xy_bounds() else {
389 return (gpb::Envelope::None, true);
390 };
391 match bounds.z_bounds() {
392 Some((min_z, max_z)) => (
393 gpb::Envelope::Xyz([min_x, max_x, min_y, max_y, min_z, max_z]),
394 false,
395 ),
396 None => (gpb::Envelope::Xy([min_x, max_x, min_y, max_y]), false),
397 }
398}
399
400#[hotpath::measure(label = "geometry::encode_gpb_from_wkb")]
435pub fn encode_gpb_from_wkb(wkb_body: &[u8], srs_id: i32) -> Result<EncodedGpb, GeometryError> {
436 let own_type = wkb_geometry_type(wkb_body)?;
437 let scan = crate::curve::scan(wkb_body)?;
438 let extended = own_type.is_extension();
439 if !extended && let Some(member) = scan.extension_types.iter().next() {
443 return Err(GeometryError::NonLinearMember {
444 container: own_type,
445 member,
446 });
447 }
448 let body = wkb_body.get(..scan.len).unwrap_or(wkb_body);
449 let mut blob = Vec::with_capacity(gpb::header_len(&scan.envelope) + body.len());
452 gpb::encode_header_into(&mut blob, srs_id, &scan.envelope, scan.empty, extended);
453 blob.extend_from_slice(body);
454 Ok(EncodedGpb {
455 blob,
456 xy_envelope: scan.xy_envelope,
457 dimensions: scan.dimensions,
458 extension_types: scan.extension_types,
459 })
460}
461
462#[derive(Debug, Clone, PartialEq)]
464pub struct EncodedGpb {
465 pub blob: Vec<u8>,
467 pub xy_envelope: Option<[f64; 4]>,
469 pub dimensions: Dimensions,
471 pub extension_types: GeometryTypeSet,
478}
479
480#[hotpath::measure(label = "geometry::encode_gpb")]
497pub fn encode_gpb<G: GeometryTrait<T = f64>>(
498 geom: &G,
499 srs_id: i32,
500) -> Result<(Vec<u8>, Option<[f64; 4]>), GeometryError> {
501 let (envelope, empty) = write_envelope(geom);
502 let xy = envelope
503 .xy_bounds()
504 .map(|(min_x, max_x, min_y, max_y)| [min_x, max_x, min_y, max_y]);
505 let mut blob = Vec::with_capacity(gpb::header_len(&envelope) + geometry_wkb_size(geom));
508 gpb::encode_header_into(&mut blob, srs_id, &envelope, empty, false);
509 let options = WriteOptions {
510 endianness: Endianness::LittleEndian,
511 };
512 write_geometry(&mut blob, geom, &options).map_err(GeometryError::EncodeWkb)?;
513 Ok((blob, xy))
514}
515
516#[derive(Debug, Clone, Copy)]
521pub(crate) struct XyBounds {
522 min_x: f64,
523 max_x: f64,
524 min_y: f64,
525 max_y: f64,
526 seen: bool,
527}
528
529impl XyBounds {
530 pub(crate) fn new() -> Self {
531 Self {
532 min_x: f64::INFINITY,
533 max_x: f64::NEG_INFINITY,
534 min_y: f64::INFINITY,
535 max_y: f64::NEG_INFINITY,
536 seen: false,
537 }
538 }
539
540 pub(crate) fn add(&mut self, x: f64, y: f64) {
541 if !x.is_finite() || !y.is_finite() {
542 return;
543 }
544 self.min_x = self.min_x.min(x);
545 self.max_x = self.max_x.max(x);
546 self.min_y = self.min_y.min(y);
547 self.max_y = self.max_y.max(y);
548 self.seen = true;
549 }
550
551 pub(crate) fn add_branchless(&mut self, x: f64, y: f64) {
562 let finite = x.is_finite() & y.is_finite();
563 let x = if finite { x } else { f64::NAN };
564 let y = if finite { y } else { f64::NAN };
565 self.min_x = self.min_x.min(x);
566 self.max_x = self.max_x.max(x);
567 self.min_y = self.min_y.min(y);
568 self.max_y = self.max_y.max(y);
569 self.seen |= finite;
570 }
571
572 pub(crate) fn finish(self) -> Option<[f64; 4]> {
573 self.seen
574 .then_some([self.min_x, self.max_x, self.min_y, self.max_y])
575 }
576}
577
578#[derive(Debug, Clone, Copy)]
584pub(crate) struct XyzBounds {
585 xy: XyBounds,
586 min_z: f64,
587 max_z: f64,
588 seen_z: bool,
589}
590
591impl XyzBounds {
592 pub(crate) fn new() -> Self {
593 Self {
594 xy: XyBounds::new(),
595 min_z: f64::INFINITY,
596 max_z: f64::NEG_INFINITY,
597 seen_z: false,
598 }
599 }
600
601 pub(crate) fn add(&mut self, x: f64, y: f64, z: Option<f64>) {
602 self.xy.add(x, y);
603 if let Some(z) = z
604 && z.is_finite()
605 {
606 self.min_z = self.min_z.min(z);
607 self.max_z = self.max_z.max(z);
608 self.seen_z = true;
609 }
610 }
611
612 pub(crate) fn add_branchless(&mut self, x: f64, y: f64, z: Option<f64>) {
616 self.xy.add_branchless(x, y);
617 if let Some(z) = z {
618 let finite = z.is_finite();
619 let z = if finite { z } else { f64::NAN };
620 self.min_z = self.min_z.min(z);
621 self.max_z = self.max_z.max(z);
622 self.seen_z |= finite;
623 }
624 }
625
626 pub(crate) fn xy_bounds(&self) -> Option<[f64; 4]> {
629 self.xy.finish()
630 }
631
632 pub(crate) fn z_bounds(&self) -> Option<(f64, f64)> {
634 self.seen_z.then_some((self.min_z, self.max_z))
635 }
636}
637
638fn coord_xyz(coord: &impl CoordTrait<T = f64>) -> (f64, f64, Option<f64>) {
643 let z = match coord.dim() {
644 Dimensions::Xyz | Dimensions::Xyzm => coord.nth(2),
645 _ => None,
646 };
647 (coord.x(), coord.y(), z)
648}
649
650fn visit_point(point: &impl PointTrait<T = f64>, visit: &mut impl FnMut(f64, f64, Option<f64>)) {
651 if let Some(coord) = point.coord() {
652 let (x, y, z) = coord_xyz(&coord);
653 visit(x, y, z);
654 }
655}
656
657fn visit_line_string(
658 line_string: &impl LineStringTrait<T = f64>,
659 visit: &mut impl FnMut(f64, f64, Option<f64>),
660) {
661 for coord in line_string.coords() {
662 let (x, y, z) = coord_xyz(&coord);
663 visit(x, y, z);
664 }
665}
666
667fn visit_polygon(
668 polygon: &impl PolygonTrait<T = f64>,
669 visit: &mut impl FnMut(f64, f64, Option<f64>),
670) {
671 if let Some(exterior) = polygon.exterior() {
672 visit_line_string(&exterior, visit);
673 }
674 for interior in polygon.interiors() {
675 visit_line_string(&interior, visit);
676 }
677}
678
679fn visit_coords<G: GeometryTrait<T = f64>>(
684 geom: &G,
685 visit: &mut impl FnMut(f64, f64, Option<f64>),
686) {
687 match geom.as_type() {
688 GtGeometryType::Point(point) => visit_point(point, visit),
689 GtGeometryType::LineString(line_string) => visit_line_string(line_string, visit),
690 GtGeometryType::Polygon(polygon) => visit_polygon(polygon, visit),
691 GtGeometryType::MultiPoint(multi_point) => {
692 for point in multi_point.points() {
693 visit_point(&point, visit);
694 }
695 }
696 GtGeometryType::MultiLineString(multi_line_string) => {
697 for line_string in multi_line_string.line_strings() {
698 visit_line_string(&line_string, visit);
699 }
700 }
701 GtGeometryType::MultiPolygon(multi_polygon) => {
702 for polygon in multi_polygon.polygons() {
703 visit_polygon(&polygon, visit);
704 }
705 }
706 GtGeometryType::GeometryCollection(collection) => {
707 for member in collection.geometries() {
708 visit_coords(&member, visit);
709 }
710 }
711 GtGeometryType::Rect(rect) => {
714 let (min, max) = (rect.min(), rect.max());
715 let (min_x, min_y, min_z) = coord_xyz(&min);
716 let (max_x, max_y, max_z) = coord_xyz(&max);
717 visit(min_x, min_y, min_z);
718 visit(max_x, max_y, max_z);
719 }
720 GtGeometryType::Triangle(triangle) => {
721 for coord in triangle.coords() {
722 let (x, y, z) = coord_xyz(&coord);
723 visit(x, y, z);
724 }
725 }
726 GtGeometryType::Line(line) => {
727 for coord in line.coords() {
728 let (x, y, z) = coord_xyz(&coord);
729 visit(x, y, z);
730 }
731 }
732 }
733}
734
735impl<'a> GeometryTrait for GpbGeometry<'a> {
736 type T = f64;
737 type PointType<'b>
738 = Point<'a>
739 where
740 Self: 'b;
741 type LineStringType<'b>
742 = LineString<'a>
743 where
744 Self: 'b;
745 type PolygonType<'b>
746 = Polygon<'a>
747 where
748 Self: 'b;
749 type MultiPointType<'b>
750 = MultiPoint<'a>
751 where
752 Self: 'b;
753 type MultiLineStringType<'b>
754 = MultiLineString<'a>
755 where
756 Self: 'b;
757 type MultiPolygonType<'b>
758 = MultiPolygon<'a>
759 where
760 Self: 'b;
761 type GeometryCollectionType<'b>
762 = GeometryCollection<'a>
763 where
764 Self: 'b;
765 type RectType<'b>
766 = geo_traits::UnimplementedRect<f64>
767 where
768 Self: 'b;
769 type TriangleType<'b>
770 = geo_traits::UnimplementedTriangle<f64>
771 where
772 Self: 'b;
773 type LineType<'b>
774 = geo_traits::UnimplementedLine<f64>
775 where
776 Self: 'b;
777
778 fn dim(&self) -> Dimensions {
779 self.wkb.dim()
780 }
781
782 fn as_type(
783 &self,
784 ) -> geo_traits::GeometryType<
785 '_,
786 Self::PointType<'_>,
787 Self::LineStringType<'_>,
788 Self::PolygonType<'_>,
789 Self::MultiPointType<'_>,
790 Self::MultiLineStringType<'_>,
791 Self::MultiPolygonType<'_>,
792 Self::GeometryCollectionType<'_>,
793 Self::RectType<'_>,
794 Self::TriangleType<'_>,
795 Self::LineType<'_>,
796 > {
797 self.wkb.as_type()
798 }
799}
800
801#[cfg(test)]
802mod tests {
803 use super::*;
804 use crate::gpb::{Envelope, encode_header};
805 use geo_traits::GeometryType as GtType;
806
807 fn gpb(body: &[u8]) -> Vec<u8> {
809 let mut blob = encode_header(4326, &Envelope::None, false, false);
810 blob.extend_from_slice(body);
811 blob
812 }
813
814 fn wkb_point(x: f64, y: f64) -> Vec<u8> {
816 let mut b = vec![1u8];
817 b.extend_from_slice(&1u32.to_le_bytes());
818 b.extend_from_slice(&x.to_le_bytes());
819 b.extend_from_slice(&y.to_le_bytes());
820 b
821 }
822
823 #[test]
824 fn parses_point_and_exposes_header_and_body() {
825 let body = wkb_point(3.0, 4.0);
826 let blob = gpb(&body);
827 let g = GpbGeometry::parse(&blob).unwrap();
828 assert_eq!(g.header().srs_id, 4326);
829 assert_eq!(g.wkb_body(), body.as_slice());
830 }
831
832 #[test]
833 fn delegates_geometry_trait_to_wkb() {
834 let blob = gpb(&wkb_point(3.0, 4.0));
835 let g = GpbGeometry::parse(&blob).unwrap();
836 match g.as_type() {
837 GtType::Point(p) => {
838 use geo_traits::{CoordTrait, PointTrait};
839 let c = p.coord().unwrap();
840 assert_eq!(c.x().to_bits(), 3.0_f64.to_bits());
842 assert_eq!(c.y().to_bits(), 4.0_f64.to_bits());
843 }
844 _ => panic!("expected a point"),
845 }
846 }
847
848 #[test]
849 fn arbitrary_bytes_error_never_panic() {
850 GpbGeometry::parse(b"").unwrap_err();
851 GpbGeometry::parse(b"GP").unwrap_err();
852 GpbGeometry::parse(&gpb(&[])).unwrap_err();
854 GpbGeometry::parse(&gpb(&[1])).unwrap_err();
856 GpbGeometry::parse(&gpb(&wkb_point(1.0, 2.0)[..10])).unwrap_err();
858 }
859
860 fn wkb_counted(code: u32, count: u32) -> Vec<u8> {
862 let mut b = vec![1u8];
863 b.extend_from_slice(&code.to_le_bytes());
864 b.extend_from_slice(&count.to_le_bytes());
865 b
866 }
867
868 #[test]
869 fn huge_declared_counts_and_deep_nesting_error_without_allocating() {
870 for code in [3, 5, 6, 7] {
874 GpbGeometry::parse(&gpb(&wkb_counted(code, u32::MAX))).unwrap_err();
875 }
876 let mut nested = Vec::new();
877 for _ in 0..100_000 {
878 nested.extend_from_slice(&wkb_counted(7, 1));
879 }
880 GpbGeometry::parse(&gpb(&nested)).unwrap_err();
881 }
882
883 #[test]
884 #[cfg(feature = "geo-types")]
885 fn to_geo_yields_geo_types() {
886 let blob = gpb(&wkb_point(3.0, 4.0));
887 let g = GpbGeometry::parse(&blob).unwrap();
888 let geo = g.to_geo().unwrap();
889 assert_eq!(
890 geo,
891 geo_types::Geometry::Point(geo_types::Point::new(3.0, 4.0))
892 );
893 }
894
895 #[test]
896 fn point_envelope_from_traversal() {
897 let blob = gpb(&wkb_point(3.0, -4.0));
898 let g = GpbGeometry::parse(&blob).unwrap();
899 assert_eq!(g.xy_envelope(), Some([3.0, 3.0, -4.0, -4.0]));
900 assert!(!g.is_empty());
901 }
902
903 #[test]
904 fn empty_point_is_empty_no_envelope() {
905 let blob = gpb(&wkb_point(f64::NAN, f64::NAN));
906 let g = GpbGeometry::parse(&blob).unwrap();
907 assert_eq!(g.xy_envelope(), None);
908 assert!(g.is_empty());
909 }
910
911 #[test]
912 fn header_empty_flag_reports_empty() {
913 let mut blob = encode_header(4326, &Envelope::None, true, false);
915 blob.extend_from_slice(&wkb_point(1.0, 2.0));
916 let g = GpbGeometry::parse(&blob).unwrap();
917 assert!(g.is_empty());
918 }
919
920 #[cfg(feature = "geo-types")]
921 fn wkb_body(geom: &geo_types::Geometry<f64>, little_endian: bool) -> Vec<u8> {
922 use wkb::Endianness;
923 use wkb::writer::{WriteOptions, write_geometry};
924 let options = WriteOptions {
925 endianness: if little_endian {
926 Endianness::LittleEndian
927 } else {
928 Endianness::BigEndian
929 },
930 };
931 let mut buf = Vec::new();
932 write_geometry(&mut buf, geom, &options).unwrap();
933 buf
934 }
935
936 #[test]
940 #[cfg(feature = "geo-types")]
941 fn traversal_bounds_equal_header_envelope() {
942 use geo_types::{
943 Geometry, LineString, MultiLineString, MultiPolygon, Point, Polygon, coord,
944 };
945
946 let line: Geometry<f64> =
947 LineString::from(vec![(0.0, 0.0), (10.0, -3.0), (4.0, 8.0)]).into();
948 let polygon: Geometry<f64> = Polygon::new(
949 LineString::from(vec![
950 (0.0, 0.0),
951 (6.0, 0.0),
952 (6.0, 5.0),
953 (0.0, 5.0),
954 (0.0, 0.0),
955 ]),
956 vec![LineString::from(vec![
957 (1.0, 1.0),
958 (2.0, 1.0),
959 (2.0, 2.0),
960 (1.0, 1.0),
961 ])],
962 )
963 .into();
964 let multipolygon: Geometry<f64> = MultiPolygon::new(vec![
965 Polygon::new(
966 LineString::from(vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
967 vec![],
968 ),
969 Polygon::new(
970 LineString::from(vec![(-5.0, -5.0), (-4.0, -5.0), (-4.0, -4.0), (-5.0, -5.0)]),
971 vec![],
972 ),
973 ])
974 .into();
975 let multiline: Geometry<f64> = MultiLineString::new(vec![
976 LineString::from(vec![(0.0, 0.0), (3.0, 3.0)]),
977 LineString::from(vec![(-2.0, 7.0), (9.0, -1.0)]),
978 ])
979 .into();
980 let point: Geometry<f64> = Point::from(coord! { x: 2.5, y: -6.0 }).into();
981
982 let cases: [(Geometry<f64>, [f64; 4]); 5] = [
983 (point, [2.5, 2.5, -6.0, -6.0]),
984 (line, [0.0, 10.0, -3.0, 8.0]),
985 (polygon, [0.0, 6.0, 0.0, 5.0]),
986 (multipolygon, [-5.0, 1.0, -5.0, 1.0]),
987 (multiline, [-2.0, 9.0, -1.0, 7.0]),
988 ];
989
990 for (geom, bounds) in cases {
991 for little_endian in [true, false] {
992 let mut blob = encode_header(4326, &Envelope::Xy(bounds), false, false);
993 blob.extend_from_slice(&wkb_body(&geom, little_endian));
994 let g = GpbGeometry::parse(&blob).unwrap();
995 let (bx0, bx1, by0, by1) = g.header().envelope.xy_bounds().unwrap();
996 assert_eq!(
997 g.xy_envelope(),
998 Some([bx0, bx1, by0, by1]),
999 "traversal must match header envelope (little_endian={little_endian})"
1000 );
1001 assert!(!g.is_empty());
1002 }
1003 }
1004 }
1005
1006 #[test]
1007 #[cfg(feature = "geo-types")]
1008 fn geometry_collection_traversal() {
1009 use geo_types::{Geometry, GeometryCollection, LineString, Point};
1010 let members: Vec<Geometry<f64>> = vec![
1011 Geometry::Point(Point::new(1.0, 1.0)),
1012 Geometry::LineString(LineString::from(vec![(-3.0, 0.0), (4.0, 9.0)])),
1013 ];
1014 let gc = Geometry::GeometryCollection(GeometryCollection::new_from(members));
1015 let mut blob = encode_header(4326, &Envelope::None, false, false);
1016 blob.extend_from_slice(&wkb_body(&gc, true));
1017 let g = GpbGeometry::parse(&blob).unwrap();
1018 assert_eq!(g.xy_envelope(), Some([-3.0, 4.0, 0.0, 9.0]));
1019 }
1020
1021 #[test]
1023 fn big_endian_linestring_traversal() {
1024 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)] {
1028 body.extend_from_slice(&x.to_be_bytes());
1029 body.extend_from_slice(&y.to_be_bytes());
1030 }
1031 let blob = gpb(&body);
1032 let g = GpbGeometry::parse(&blob).unwrap();
1033 assert_eq!(g.xy_envelope(), Some([1.0, 5.0, -1.0, 2.0]));
1034 }
1035
1036 #[test]
1037 fn declared_type_matching_rules() {
1038 use GeometryType::*;
1039 assert!(geometry_type_matches(Point, Point));
1041 assert!(geometry_type_matches(LineString, LineString));
1042 assert!(geometry_type_matches(Point, Geometry));
1044 assert!(geometry_type_matches(CircularString, Geometry));
1045 assert!(geometry_type_matches(MultiPoint, GeometryCollection));
1047 assert!(geometry_type_matches(MultiSurface, GeometryCollection));
1048 assert!(geometry_type_matches(
1049 GeometryCollection,
1050 GeometryCollection
1051 ));
1052 assert!(!geometry_type_matches(Point, GeometryCollection));
1053 assert!(!geometry_type_matches(LineString, MultiLineString));
1055 assert!(!geometry_type_matches(Point, LineString));
1056 }
1057
1058 #[test]
1059 fn reads_wkb_type_code_including_curves() {
1060 assert_eq!(
1062 wkb_geometry_type(&wkb_point(0.0, 0.0)).unwrap(),
1063 GeometryType::Point
1064 );
1065 let mut point_zm = vec![1u8];
1067 point_zm.extend_from_slice(&3001u32.to_le_bytes());
1068 assert_eq!(wkb_geometry_type(&point_zm).unwrap(), GeometryType::Point);
1069 let mut circular = vec![1u8];
1072 circular.extend_from_slice(&8u32.to_le_bytes());
1073 assert_eq!(
1074 wkb_geometry_type(&circular).unwrap(),
1075 GeometryType::CircularString
1076 );
1077 assert!(matches!(
1079 wkb_geometry_type(b"\x01\x00"),
1080 Err(GeometryError::TruncatedWkb)
1081 ));
1082 let mut unknown = vec![1u8];
1083 unknown.extend_from_slice(&99u32.to_le_bytes());
1084 assert!(matches!(
1085 wkb_geometry_type(&unknown),
1086 Err(GeometryError::UnknownWkbType(99))
1087 ));
1088 }
1089
1090 #[test]
1091 fn matches_declared_on_parsed_geometry() {
1092 let blob = gpb(&wkb_point(1.0, 2.0));
1093 let g = GpbGeometry::parse(&blob).unwrap();
1094 assert_eq!(g.geometry_type(), GeometryType::Point);
1095 assert!(g.matches_declared(GeometryType::Point));
1096 assert!(g.matches_declared(GeometryType::Geometry));
1097 assert!(!g.matches_declared(GeometryType::LineString));
1098 }
1099
1100 fn wkb_point_z(x: f64, y: f64, z: f64) -> Vec<u8> {
1102 let mut b = vec![1u8];
1103 b.extend_from_slice(&1001u32.to_le_bytes());
1104 b.extend_from_slice(&x.to_le_bytes());
1105 b.extend_from_slice(&y.to_le_bytes());
1106 b.extend_from_slice(&z.to_le_bytes());
1107 b
1108 }
1109
1110 #[test]
1111 #[cfg(feature = "geo-types")]
1112 fn encode_gpb_xy_point_roundtrips() {
1113 let point = geo_types::Point::new(3.0, 4.0);
1114 let (blob, xy) = encode_gpb(&point, 4326).unwrap();
1115 assert_eq!(xy, Some([3.0, 3.0, 4.0, 4.0]));
1116 let g = GpbGeometry::parse(&blob).unwrap();
1117 assert_eq!(g.header().srs_id, 4326);
1118 assert_eq!(g.header().envelope, Envelope::Xy([3.0, 3.0, 4.0, 4.0]));
1119 assert!(!g.header().empty);
1120 assert_eq!(g.geometry_type(), GeometryType::Point);
1121 }
1122
1123 #[test]
1124 #[cfg(feature = "geo-types")]
1125 fn encode_gpb_linestring_envelope() {
1126 let ls = geo_types::LineString::from(vec![(0.0, 0.0), (10.0, -3.0), (4.0, 8.0)]);
1127 let (blob, xy) = encode_gpb(&ls, 4326).unwrap();
1128 assert_eq!(xy, Some([0.0, 10.0, -3.0, 8.0]));
1129 let g = GpbGeometry::parse(&blob).unwrap();
1130 assert_eq!(g.header().envelope, Envelope::Xy([0.0, 10.0, -3.0, 8.0]));
1131 assert_eq!(g.geometry_type(), GeometryType::LineString);
1132 }
1133
1134 #[test]
1135 fn encode_gpb_z_point_writes_xyz_envelope() {
1136 let src_blob = gpb(&wkb_point_z(1.0, 2.0, 9.0));
1140 let src = GpbGeometry::parse(&src_blob).unwrap();
1141 assert_eq!(src.dim(), Dimensions::Xyz);
1142 let (blob, xy) = encode_gpb(&src, 4326).unwrap();
1143 assert_eq!(xy, Some([1.0, 1.0, 2.0, 2.0]));
1144 let g = GpbGeometry::parse(&blob).unwrap();
1145 assert_eq!(
1146 g.header().envelope,
1147 Envelope::Xyz([1.0, 1.0, 2.0, 2.0, 9.0, 9.0])
1148 );
1149 assert_eq!(g.dim(), Dimensions::Xyz);
1150 assert_eq!(g.geometry_type(), GeometryType::Point);
1151 }
1152
1153 #[test]
1154 fn write_envelope_empty_point_is_empty() {
1155 let blob = gpb(&wkb_point(f64::NAN, f64::NAN));
1156 let g = GpbGeometry::parse(&blob).unwrap();
1157 let (envelope, empty) = write_envelope(&g);
1158 assert_eq!(envelope, Envelope::None);
1159 assert!(empty);
1160 let (reblob, xy) = encode_gpb(&g, 4326).unwrap();
1162 assert_eq!(xy, None);
1163 let re = GpbGeometry::parse(&reblob).unwrap();
1164 assert_eq!(re.header().envelope, Envelope::None);
1165 assert!(re.header().empty);
1166 }
1167}
1168
1169#[cfg(test)]
1170mod encode_from_wkb_tests {
1171 use super::*;
1172 use geo_types::{Geometry, LineString, Point};
1173
1174 #[test]
1179 fn agrees_with_encode_gpb() {
1180 let cases: Vec<Geometry<f64>> = vec![
1181 Geometry::Point(Point::new(1.5, -2.5)),
1182 Geometry::LineString(LineString::from(vec![(0.0, 0.0), (10.0, 5.0), (-3.0, 7.5)])),
1183 ];
1184 for geometry in cases {
1185 let (expected, expected_xy) = encode_gpb(&geometry, 4326).unwrap();
1186 let (_, offset) = gpb::parse_header(&expected).unwrap();
1188 let actual = encode_gpb_from_wkb(&expected[offset..], 4326).unwrap();
1189 assert_eq!(actual.blob, expected, "blob differs");
1190 assert_eq!(actual.xy_envelope, expected_xy, "envelope differs");
1191 }
1192 }
1193
1194 #[test]
1195 fn trailing_bytes_are_not_copied() {
1196 let (blob, _) = encode_gpb(&Point::new(3.0, 4.0), 4326).unwrap();
1197 let (_, offset) = gpb::parse_header(&blob).unwrap();
1198 let mut body = blob[offset..].to_vec();
1199 let clean = encode_gpb_from_wkb(&body, 4326).unwrap().blob;
1200 body.extend_from_slice(b"trailing rubbish");
1201 let padded = encode_gpb_from_wkb(&body, 4326).unwrap().blob;
1202 assert_eq!(clean, padded, "trailing bytes reached the blob");
1203 }
1204
1205 #[test]
1206 fn a_body_that_is_not_wkb_is_rejected() {
1207 encode_gpb_from_wkb(&[], 4326).unwrap_err();
1208 encode_gpb_from_wkb(b"not wkb at all", 4326).unwrap_err();
1209 encode_gpb_from_wkb(&[1, 0xff, 0xff, 0, 0], 4326).unwrap_err();
1211 }
1212}