use alloc::vec::Vec;
use geometry_cs::CoordinateSystem;
use geometry_model::{
DynGeometry, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point, Polygon, Ring,
};
use geometry_trait::{
Geometry, Linestring as LinestringTrait, MultiLinestring as MultiLinestringTrait,
MultiPoint as MultiPointTrait, MultiPolygon as MultiPolygonTrait, Point as PointTrait,
Polygon as PolygonTrait, Ring as RingTrait,
};
use crate::header::ByteOrder;
const WKB_POINT: u32 = 1;
const WKB_LINESTRING: u32 = 2;
const WKB_POLYGON: u32 = 3;
const WKB_MULTIPOINT: u32 = 4;
const WKB_MULTILINESTRING: u32 = 5;
const WKB_MULTIPOLYGON: u32 = 6;
const WKB_GEOMETRYCOLLECTION: u32 = 7;
#[must_use]
pub fn to_wkb<G: Geometry + WriteWkb>(g: &G, order: ByteOrder) -> Vec<u8> {
let mut out = Vec::new();
g.write_wkb(order, &mut out);
out
}
#[doc(hidden)]
pub trait WriteWkb {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>);
}
fn write_byte_order(order: ByteOrder, out: &mut Vec<u8>) {
out.push(match order {
ByteOrder::LittleEndian => 0x01,
ByteOrder::BigEndian => 0x00,
});
}
fn write_u32(v: u32, order: ByteOrder, out: &mut Vec<u8>) {
let b = match order {
ByteOrder::LittleEndian => v.to_le_bytes(),
ByteOrder::BigEndian => v.to_be_bytes(),
};
out.extend_from_slice(&b);
}
fn write_f64(v: f64, order: ByteOrder, out: &mut Vec<u8>) {
let b = match order {
ByteOrder::LittleEndian => v.to_le_bytes(),
ByteOrder::BigEndian => v.to_be_bytes(),
};
out.extend_from_slice(&b);
}
fn write_header(type_code: u32, order: ByteOrder, out: &mut Vec<u8>) {
write_byte_order(order, out);
write_u32(type_code, order, out);
}
fn write_point_body<P: PointTrait<Scalar = f64>>(p: &P, order: ByteOrder, out: &mut Vec<u8>) {
write_f64(p.get::<0>(), order, out);
write_f64(p.get::<1>(), order, out);
}
fn write_point_run<'a, P, I>(points: I, order: ByteOrder, out: &mut Vec<u8>)
where
P: PointTrait<Scalar = f64> + 'a,
I: ExactSizeIterator<Item = &'a P>,
{
#[allow(
clippy::cast_possible_truncation,
reason = "WKB counts are 32-bit per OGC 06-103r4 §8.2; a WKB buffer never holds 2^32 points"
)]
let n = points.len() as u32;
write_u32(n, order, out);
for p in points {
write_point_body(p, order, out);
}
}
fn write_polygon_body<Pg>(pg: &Pg, order: ByteOrder, out: &mut Vec<u8>)
where
Pg: PolygonTrait,
Pg::Point: PointTrait<Scalar = f64>,
{
#[allow(
clippy::cast_possible_truncation,
reason = "WKB counts are 32-bit per OGC 06-103r4 §8.2"
)]
let ring_count = (1 + pg.interiors().count()) as u32;
write_u32(ring_count, order, out);
write_point_run(pg.exterior().points(), order, out);
for ring in pg.interiors() {
write_point_run(ring.points(), order, out);
}
}
impl<Cs: CoordinateSystem> WriteWkb for Point<f64, 2, Cs> {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_POINT, order, out);
write_point_body(self, order, out);
}
}
impl<P: PointTrait<Scalar = f64>> WriteWkb for Linestring<P> {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_LINESTRING, order, out);
write_point_run(self.points(), order, out);
}
}
impl<P: PointTrait<Scalar = f64>> WriteWkb for Ring<P, true, true> {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_POLYGON, order, out);
write_u32(1, order, out);
write_point_run(self.points(), order, out);
}
}
impl<P: PointTrait<Scalar = f64>> WriteWkb for Polygon<P, true, true> {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_POLYGON, order, out);
write_polygon_body(self, order, out);
}
}
impl<P: PointTrait<Scalar = f64>> WriteWkb for MultiPoint<P> {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_MULTIPOINT, order, out);
#[allow(
clippy::cast_possible_truncation,
reason = "WKB counts are 32-bit per OGC 06-103r4 §8.2"
)]
let n = self.points().len() as u32;
write_u32(n, order, out);
for p in self.points() {
write_header(WKB_POINT, order, out);
write_point_body(p, order, out);
}
}
}
impl<L> WriteWkb for MultiLinestring<L>
where
L: LinestringTrait,
L::Point: PointTrait<Scalar = f64>,
{
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_MULTILINESTRING, order, out);
#[allow(
clippy::cast_possible_truncation,
reason = "WKB counts are 32-bit per OGC 06-103r4 §8.2"
)]
let n = self.linestrings().len() as u32;
write_u32(n, order, out);
for ls in self.linestrings() {
write_header(WKB_LINESTRING, order, out);
write_point_run(ls.points(), order, out);
}
}
}
impl<Pg> WriteWkb for MultiPolygon<Pg>
where
Pg: PolygonTrait,
Pg::Point: PointTrait<Scalar = f64>,
{
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
write_header(WKB_MULTIPOLYGON, order, out);
#[allow(
clippy::cast_possible_truncation,
reason = "WKB counts are 32-bit per OGC 06-103r4 §8.2"
)]
let n = self.polygons().len() as u32;
write_u32(n, order, out);
for pg in self.polygons() {
write_header(WKB_POLYGON, order, out);
write_polygon_body(pg, order, out);
}
}
}
impl<Cs: CoordinateSystem> WriteWkb for DynGeometry<f64, Cs> {
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>) {
let mut stack = alloc::vec![self];
while let Some(g) = stack.pop() {
match g {
DynGeometry::Point(p) => p.write_wkb(order, out),
DynGeometry::LineString(ls) => ls.write_wkb(order, out),
DynGeometry::Polygon(pg) => pg.write_wkb(order, out),
DynGeometry::MultiPoint(mp) => mp.write_wkb(order, out),
DynGeometry::MultiLineString(mls) => mls.write_wkb(order, out),
DynGeometry::MultiPolygon(mpg) => mpg.write_wkb(order, out),
DynGeometry::GeometryCollection(items) => {
write_header(WKB_GEOMETRYCOLLECTION, order, out);
#[allow(
clippy::cast_possible_truncation,
reason = "WKB counts are 32-bit per OGC 06-103r4 §8.2"
)]
let n = items.len() as u32;
write_u32(n, order, out);
stack.extend(items.iter().rev());
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use geometry_cs::Cartesian;
use geometry_model::Point2D;
type Pt = Point2D<f64, Cartesian>;
#[test]
fn nested_collection_writer_is_iterative() {
use geometry_model::DynGeometry;
let g = DynGeometry::<f64, Cartesian>::GeometryCollection(vec![
DynGeometry::Point(Pt::new(1.0, 1.0)),
DynGeometry::GeometryCollection(vec![DynGeometry::Point(Pt::new(2.0, 2.0))]),
]);
let mut bytes = alloc::vec::Vec::new();
g.write_wkb(ByteOrder::LittleEndian, &mut bytes);
assert_eq!(&bytes[..9], &[0x01, 0x07, 0, 0, 0, 0x02, 0, 0, 0]);
let mut deep = DynGeometry::<f64, Cartesian>::Point(Pt::new(0.0, 0.0));
for _ in 0..200_000 {
deep = DynGeometry::GeometryCollection(vec![deep]);
}
let mut out = alloc::vec::Vec::new();
deep.write_wkb(ByteOrder::LittleEndian, &mut out);
assert!(!out.is_empty());
core::mem::forget(deep); }
#[test]
fn point_little_endian() {
let expected = vec![
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, ];
assert_eq!(
to_wkb(&Pt::new(1.0, 2.0), ByteOrder::LittleEndian),
expected
);
}
#[test]
fn point_big_endian_flag_and_type() {
let bytes = to_wkb(&Pt::new(1.0, 2.0), ByteOrder::BigEndian);
assert_eq!(&bytes[0..5], &[0x00, 0x00, 0x00, 0x00, 0x01]);
}
}