///|
priv enum WkbEncoding {
IsoWkb
Ewkb
}
///|
priv struct WkbWriter {
bytes : Array[Byte]
order : WkbByteOrder
encoding : WkbEncoding
}
///|
fn WkbWriter::new(order : WkbByteOrder, encoding : WkbEncoding) -> WkbWriter {
{ bytes: [], order, encoding, }
}
///|
fn WkbWriter::write_byte_order(self : WkbWriter) -> Unit {
match self.order {
LittleEndian => self.bytes.push(b'\x01')
BigEndian => self.bytes.push(b'\x00')
}
}
///|
fn WkbWriter::write_u32(self : WkbWriter, value : UInt32) -> Unit {
match self.order {
LittleEndian => {
self.bytes.push(value.land(255U).to_byte())
self.bytes.push((value >> 8).land(255U).to_byte())
self.bytes.push((value >> 16).land(255U).to_byte())
self.bytes.push((value >> 24).land(255U).to_byte())
}
BigEndian => {
self.bytes.push((value >> 24).land(255U).to_byte())
self.bytes.push((value >> 16).land(255U).to_byte())
self.bytes.push((value >> 8).land(255U).to_byte())
self.bytes.push(value.land(255U).to_byte())
}
}
}
///|
fn WkbWriter::write_f64(self : WkbWriter, value : Double) -> Unit {
let bits = value.reinterpret_as_uint64()
for index in 0..<8 {
let shift = match self.order {
LittleEndian => index * 8
BigEndian => (7 - index) * 8
}
self.bytes.push((bits >> shift).land(255UL).to_byte())
}
}
///|
fn wkb_dimension_offset(dimension : CoordinateDimension) -> UInt32 {
match dimension {
Xy => 0U
Xyz => 1000U
Xym => 2000U
Xyzm => 3000U
}
}
///|
fn WkbWriter::write_header(
self : WkbWriter,
base_code : UInt32,
dimension : CoordinateDimension,
srid : Int32?,
) -> Unit {
self.write_byte_order()
match self.encoding {
IsoWkb => self.write_u32(base_code + wkb_dimension_offset(dimension))
Ewkb => {
let mut type_code = base_code
match dimension {
Xyz | Xyzm => type_code = type_code | 0x80000000U
_ => ()
}
match dimension {
Xym | Xyzm => type_code = type_code | 0x40000000U
_ => ()
}
match srid {
Some(_) => type_code = type_code | 0x20000000U
None => ()
}
self.write_u32(type_code)
match srid {
Some(value) => self.write_u32(value.reinterpret_as_uint())
None => ()
}
}
}
}
///|
fn validate_wkb_writer_coordinate(
coordinate : Coordinate,
dimension : CoordinateDimension,
) -> Result[Unit, GeoError] {
if coordinate.dimension() != dimension {
return Err(
InvalidCoordinate("coordinate dimension does not match geometry"),
)
}
if coordinate.x.is_nan() || coordinate.x.is_inf() {
return Err(InvalidCoordinate("non-finite x ordinate"))
}
if coordinate.y.is_nan() || coordinate.y.is_inf() {
return Err(InvalidCoordinate("non-finite y ordinate"))
}
match coordinate.z {
Some(value) =>
if value.is_nan() || value.is_inf() {
return Err(InvalidCoordinate("non-finite z ordinate"))
}
None => ()
}
match coordinate.m {
Some(value) =>
if value.is_nan() || value.is_inf() {
return Err(InvalidCoordinate("non-finite m ordinate"))
}
None => ()
}
Ok(())
}
///|
fn WkbWriter::write_coordinate(
self : WkbWriter,
coordinate : Coordinate,
dimension : CoordinateDimension,
) -> Result[Unit, GeoError] {
match validate_wkb_writer_coordinate(coordinate, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
self.write_f64(coordinate.x)
self.write_f64(coordinate.y)
match dimension {
Xyz | Xyzm =>
match coordinate.z {
Some(value) => self.write_f64(value)
None => ()
}
_ => ()
}
match dimension {
Xym | Xyzm =>
match coordinate.m {
Some(value) => self.write_f64(value)
None => ()
}
_ => ()
}
Ok(())
}
///|
fn WkbWriter::write_point_geometry(
self : WkbWriter,
coordinate : Coordinate,
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(1U, dimension, srid)
self.write_coordinate(coordinate, dimension)
}
///|
fn WkbWriter::write_empty_point_geometry(
self : WkbWriter,
dimension : CoordinateDimension,
srid : Int32?,
) -> Unit {
self.write_header(1U, dimension, srid)
self.write_f64(@double.not_a_number)
self.write_f64(@double.not_a_number)
match dimension {
Xyz | Xyzm => self.write_f64(@double.not_a_number)
_ => ()
}
match dimension {
Xym | Xyzm => self.write_f64(@double.not_a_number)
_ => ()
}
}
///|
fn WkbWriter::write_coordinates(
self : WkbWriter,
coordinates : Array[Coordinate],
dimension : CoordinateDimension,
) -> Result[Unit, GeoError] {
for coordinate in coordinates {
match self.write_coordinate(coordinate, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
fn WkbWriter::write_line_string_geometry(
self : WkbWriter,
coordinates : Array[Coordinate],
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(2U, dimension, srid)
self.write_u32(coordinates.length().reinterpret_as_uint())
self.write_coordinates(coordinates, dimension)
}
///|
fn WkbWriter::write_polygon_geometry(
self : WkbWriter,
rings : Array[Array[Coordinate]],
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(3U, dimension, srid)
self.write_u32(rings.length().reinterpret_as_uint())
for ring in rings {
if ring.length() == 0 {
return Err(InvalidCoordinate("polygon ring has no coordinates"))
}
self.write_u32(ring.length().reinterpret_as_uint())
match self.write_coordinates(ring, dimension) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
fn WkbWriter::write_multi_point_geometry(
self : WkbWriter,
coordinates : Array[Coordinate],
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(4U, dimension, srid)
self.write_u32(coordinates.length().reinterpret_as_uint())
for coordinate in coordinates {
match self.write_point_geometry(coordinate, dimension, None) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
fn WkbWriter::write_multi_line_string_geometry(
self : WkbWriter,
lines : Array[Array[Coordinate]],
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(5U, dimension, srid)
self.write_u32(lines.length().reinterpret_as_uint())
for line in lines {
match self.write_line_string_geometry(line, dimension, None) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
fn WkbWriter::write_multi_polygon_geometry(
self : WkbWriter,
polygons : Array[Array[Array[Coordinate]]],
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(6U, dimension, srid)
self.write_u32(polygons.length().reinterpret_as_uint())
for polygon in polygons {
match self.write_polygon_geometry(polygon, dimension, None) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
fn WkbWriter::write_geometry_collection_geometry(
self : WkbWriter,
geometries : Array[Geometry],
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
self.write_header(7U, dimension, srid)
self.write_u32(geometries.length().reinterpret_as_uint())
for geometry in geometries {
match self.write_geometry(geometry, dimension, None) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
///|
fn WkbWriter::write_geometry(
self : WkbWriter,
geometry : Geometry,
dimension : CoordinateDimension,
srid : Int32?,
) -> Result[Unit, GeoError] {
match geometry {
Point(coordinate) => self.write_point_geometry(coordinate, dimension, srid)
EmptyPoint => {
self.write_empty_point_geometry(dimension, srid)
Ok(())
}
LineString(coordinates) =>
self.write_line_string_geometry(coordinates, dimension, srid)
Polygon(rings) => self.write_polygon_geometry(rings, dimension, srid)
MultiPoint(coordinates) =>
self.write_multi_point_geometry(coordinates, dimension, srid)
MultiLineString(lines) =>
self.write_multi_line_string_geometry(lines, dimension, srid)
MultiPolygon(polygons) =>
self.write_multi_polygon_geometry(polygons, dimension, srid)
GeometryCollection(geometries) =>
self.write_geometry_collection_geometry(geometries, dimension, srid)
}
}
///|
fn resolve_wkb_dimension(
geometry : Geometry,
) -> Result[CoordinateDimension, GeoError] {
match geometry_dimension_summary(geometry) {
Uniform(value) => Ok(value)
NoDimension => Ok(Xy)
Mixed => Err(InvalidCoordinate("inconsistent coordinate dimensions"))
}
}
///|
/// Serializes a geometry as little-endian WKB.
pub fn format_wkb(geometry : Geometry) -> Result[Bytes, GeoError] {
format_wkb_with_order(geometry, LittleEndian)
}
///|
/// Serializes a geometry as WKB using the requested byte order.
pub fn format_wkb_with_order(
geometry : Geometry,
order : WkbByteOrder,
) -> Result[Bytes, GeoError] {
let dimension = match resolve_wkb_dimension(geometry) {
Ok(value) => value
Err(err) => return Err(err)
}
let writer = WkbWriter::new(order, IsoWkb)
match writer.write_geometry(geometry, dimension, None) {
Ok(_) => Ok(Bytes::from_array(writer.bytes))
Err(err) => Err(err)
}
}
///|
/// Serializes a spatial geometry as little-endian EWKB.
pub fn format_ewkb(value : SpatialGeometry) -> Result[Bytes, GeoError] {
format_ewkb_with_order(value, LittleEndian)
}
///|
/// Serializes a spatial geometry as EWKB using the requested byte order.
pub fn format_ewkb_with_order(
value : SpatialGeometry,
order : WkbByteOrder,
) -> Result[Bytes, GeoError] {
match value.srid {
Some(srid) => if srid < 0 { return Err(InvalidSrid(srid)) }
None => ()
}
let dimension = match resolve_wkb_dimension(value.geometry) {
Ok(value) => value
Err(err) => return Err(err)
}
let writer = WkbWriter::new(order, Ewkb)
match writer.write_geometry(value.geometry, dimension, value.srid) {
Ok(_) => Ok(Bytes::from_array(writer.bytes))
Err(err) => Err(err)
}
}