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;
const HEADER_LEN: usize = 5;
const COUNT_LEN: usize = 4;
const POINT_LEN: usize = 16;
#[must_use]
pub fn to_wkb<G: Geometry + WriteWkb>(g: &G, order: ByteOrder) -> Vec<u8> {
let mut out = Vec::with_capacity(g.wkb_len().unwrap_or(0));
g.write_wkb(order, &mut out);
out
}
#[must_use]
pub fn to_wkb_polygon<Pg>(polygon: &Pg, order: ByteOrder) -> Vec<u8>
where
Pg: PolygonTrait,
Pg::Point: PointTrait<Scalar = f64>,
{
let capacity = polygon_body_len(polygon)
.and_then(|body_len| HEADER_LEN.checked_add(body_len))
.unwrap_or(0);
let mut out = Vec::with_capacity(capacity);
write_header(WKB_POLYGON, order, &mut out);
write_polygon_body(polygon, order, &mut out);
out
}
#[doc(hidden)]
pub trait WriteWkb {
fn wkb_len(&self) -> Option<usize> {
None
}
fn write_wkb(&self, order: ByteOrder, out: &mut Vec<u8>);
}
fn point_run_len(point_count: usize) -> Option<usize> {
COUNT_LEN.checked_add(point_count.checked_mul(POINT_LEN)?)
}
fn polygon_body_len<Pg>(pg: &Pg) -> Option<usize>
where
Pg: PolygonTrait,
Pg::Point: PointTrait<Scalar = f64>,
{
let mut len = COUNT_LEN.checked_add(point_run_len(pg.exterior().points().len())?)?;
for ring in pg.interiors() {
len = len.checked_add(point_run_len(ring.points().len())?)?;
}
Some(len)
}
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_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>) {
match order {
ByteOrder::LittleEndian => write_point_body_le(p, out),
ByteOrder::BigEndian => write_point_body_be(p, out),
}
}
fn write_point_body_le<P: PointTrait<Scalar = f64>>(p: &P, out: &mut Vec<u8>) {
let mut bytes = [0; POINT_LEN];
bytes[..8].copy_from_slice(&p.get::<0>().to_le_bytes());
bytes[8..].copy_from_slice(&p.get::<1>().to_le_bytes());
out.extend_from_slice(&bytes);
}
fn write_point_body_be<P: PointTrait<Scalar = f64>>(p: &P, out: &mut Vec<u8>) {
let mut bytes = [0; POINT_LEN];
bytes[..8].copy_from_slice(&p.get::<0>().to_be_bytes());
bytes[8..].copy_from_slice(&p.get::<1>().to_be_bytes());
out.extend_from_slice(&bytes);
}
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);
match order {
ByteOrder::LittleEndian => {
for p in points {
write_point_body_le(p, out);
}
}
ByteOrder::BigEndian => {
for p in points {
write_point_body_be(p, 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 wkb_len(&self) -> Option<usize> {
Some(HEADER_LEN + POINT_LEN)
}
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 wkb_len(&self) -> Option<usize> {
HEADER_LEN.checked_add(point_run_len(self.points().len())?)
}
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 wkb_len(&self) -> Option<usize> {
HEADER_LEN
.checked_add(COUNT_LEN)?
.checked_add(point_run_len(self.points().len())?)
}
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 wkb_len(&self) -> Option<usize> {
HEADER_LEN.checked_add(polygon_body_len(self)?)
}
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 wkb_len(&self) -> Option<usize> {
HEADER_LEN
.checked_add(COUNT_LEN)?
.checked_add(self.points().len().checked_mul(HEADER_LEN + POINT_LEN)?)
}
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 wkb_len(&self) -> Option<usize> {
let mut len = HEADER_LEN.checked_add(COUNT_LEN)?;
for linestring in self.linestrings() {
len = len
.checked_add(HEADER_LEN)?
.checked_add(point_run_len(linestring.points().len())?)?;
}
Some(len)
}
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 wkb_len(&self) -> Option<usize> {
let mut len = HEADER_LEN.checked_add(COUNT_LEN)?;
for polygon in self.polygons() {
len = len
.checked_add(HEADER_LEN)?
.checked_add(polygon_body_len(polygon)?)?;
}
Some(len)
}
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 wkb_len(&self) -> Option<usize> {
match self {
DynGeometry::Point(point) => point.wkb_len(),
DynGeometry::LineString(linestring) => linestring.wkb_len(),
DynGeometry::Polygon(polygon) => polygon.wkb_len(),
DynGeometry::MultiPoint(multipoint) => multipoint.wkb_len(),
DynGeometry::MultiLineString(multilinestring) => multilinestring.wkb_len(),
DynGeometry::MultiPolygon(multipolygon) => multipolygon.wkb_len(),
DynGeometry::GeometryCollection(items) => {
let mut len = HEADER_LEN.checked_add(COUNT_LEN)?;
let mut stack = Vec::with_capacity(items.len());
stack.extend(items);
while let Some(geometry) = stack.pop() {
match geometry {
DynGeometry::Point(point) => len = len.checked_add(point.wkb_len()?)?,
DynGeometry::LineString(linestring) => {
len = len.checked_add(linestring.wkb_len()?)?;
}
DynGeometry::Polygon(polygon) => {
len = len.checked_add(polygon.wkb_len()?)?;
}
DynGeometry::MultiPoint(multipoint) => {
len = len.checked_add(multipoint.wkb_len()?)?;
}
DynGeometry::MultiLineString(multilinestring) => {
len = len.checked_add(multilinestring.wkb_len()?)?;
}
DynGeometry::MultiPolygon(multipolygon) => {
len = len.checked_add(multipolygon.wkb_len()?)?;
}
DynGeometry::GeometryCollection(nested) => {
len = len.checked_add(HEADER_LEN)?.checked_add(COUNT_LEN)?;
stack.extend(nested);
}
}
}
Some(len)
}
}
}
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]);
}
#[test]
fn trait_polygon_entry_point_matches_model_writer() {
let polygon = Polygon::with_inners(
Ring::from_vec(vec![
Pt::new(0.0, 0.0),
Pt::new(0.0, 2.0),
Pt::new(2.0, 2.0),
Pt::new(2.0, 0.0),
Pt::new(0.0, 0.0),
]),
vec![],
);
assert_eq!(
to_wkb_polygon(&polygon, ByteOrder::LittleEndian),
to_wkb(&polygon, ByteOrder::LittleEndian)
);
}
}