use geo::Geometry;
use geozero::wkb::Wkb;
use geozero::{CoordDimensions, ToGeo, ToWkb};
#[derive(Debug, thiserror::Error)]
pub enum WkbError {
#[error("WKB encode error: {0}")]
EncodeError(String),
#[error("WKB decode error: {0}")]
DecodeError(String),
}
pub type Result<T> = std::result::Result<T, WkbError>;
pub fn geometry_to_wkb(geom: &Geometry) -> Result<Vec<u8>> {
geom.to_wkb(CoordDimensions::xy())
.map_err(|e| WkbError::EncodeError(e.to_string()))
}
pub fn wkb_to_geometry(wkb: &[u8]) -> Result<Geometry> {
Wkb(wkb.to_vec())
.to_geo()
.map_err(|e| WkbError::DecodeError(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
use geo::{line_string, point, polygon, Coord, LineString, MultiPolygon, Point, Polygon};
#[test]
fn test_point_round_trip() {
let original = Geometry::Point(point!(x: 1.5, y: 2.5));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::Point(p) => {
assert!((p.x() - 1.5).abs() < 1e-10);
assert!((p.y() - 2.5).abs() < 1e-10);
}
_ => panic!("Expected Point, got {:?}", restored),
}
}
#[test]
fn test_linestring_round_trip() {
let original = Geometry::LineString(line_string![
(x: 0.0, y: 0.0),
(x: 1.0, y: 1.0),
(x: 2.0, y: 0.0)
]);
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::LineString(ls) => {
assert_eq!(ls.0.len(), 3);
assert!((ls.0[0].x - 0.0).abs() < 1e-10);
assert!((ls.0[1].x - 1.0).abs() < 1e-10);
assert!((ls.0[2].x - 2.0).abs() < 1e-10);
}
_ => panic!("Expected LineString, got {:?}", restored),
}
}
#[test]
fn test_polygon_round_trip() {
let original = Geometry::Polygon(polygon![
(x: 0.0, y: 0.0),
(x: 4.0, y: 0.0),
(x: 4.0, y: 4.0),
(x: 0.0, y: 4.0),
(x: 0.0, y: 0.0)
]);
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::Polygon(poly) => {
assert_eq!(poly.exterior().0.len(), 5);
assert!(poly.interiors().is_empty());
}
_ => panic!("Expected Polygon, got {:?}", restored),
}
}
#[test]
fn test_polygon_with_hole_round_trip() {
let exterior = LineString::from(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 10.0, y: 0.0 },
Coord { x: 10.0, y: 10.0 },
Coord { x: 0.0, y: 10.0 },
Coord { x: 0.0, y: 0.0 },
]);
let hole = LineString::from(vec![
Coord { x: 2.0, y: 2.0 },
Coord { x: 8.0, y: 2.0 },
Coord { x: 8.0, y: 8.0 },
Coord { x: 2.0, y: 8.0 },
Coord { x: 2.0, y: 2.0 },
]);
let original = Geometry::Polygon(Polygon::new(exterior, vec![hole]));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::Polygon(poly) => {
assert_eq!(poly.exterior().0.len(), 5);
assert_eq!(poly.interiors().len(), 1);
assert_eq!(poly.interiors()[0].0.len(), 5);
}
_ => panic!("Expected Polygon, got {:?}", restored),
}
}
#[test]
fn test_multipolygon_round_trip() {
let poly1 = polygon![
(x: 0.0, y: 0.0),
(x: 1.0, y: 0.0),
(x: 1.0, y: 1.0),
(x: 0.0, y: 1.0),
(x: 0.0, y: 0.0)
];
let poly2 = polygon![
(x: 5.0, y: 5.0),
(x: 6.0, y: 5.0),
(x: 6.0, y: 6.0),
(x: 5.0, y: 6.0),
(x: 5.0, y: 5.0)
];
let original = Geometry::MultiPolygon(MultiPolygon::new(vec![poly1, poly2]));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::MultiPolygon(mp) => {
assert_eq!(mp.0.len(), 2);
}
_ => panic!("Expected MultiPolygon, got {:?}", restored),
}
}
#[test]
fn test_multipoint_round_trip() {
use geo::MultiPoint;
let points = vec![
Point::new(1.0, 2.0),
Point::new(3.0, 4.0),
Point::new(5.0, 6.0),
];
let original = Geometry::MultiPoint(MultiPoint::new(points));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::MultiPoint(mp) => {
assert_eq!(mp.0.len(), 3);
}
_ => panic!("Expected MultiPoint, got {:?}", restored),
}
}
#[test]
fn test_multilinestring_round_trip() {
use geo::MultiLineString;
let lines = vec![
line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 1.0)],
line_string![(x: 2.0, y: 2.0), (x: 3.0, y: 3.0)],
];
let original = Geometry::MultiLineString(MultiLineString::new(lines));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::MultiLineString(mls) => {
assert_eq!(mls.0.len(), 2);
}
_ => panic!("Expected MultiLineString, got {:?}", restored),
}
}
#[test]
fn test_point_at_origin() {
let original = Geometry::Point(point!(x: 0.0, y: 0.0));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::Point(p) => {
assert!((p.x() - 0.0).abs() < 1e-10);
assert!((p.y() - 0.0).abs() < 1e-10);
}
_ => panic!("Expected Point"),
}
}
#[test]
fn test_point_extreme_coordinates() {
let original = Geometry::Point(point!(x: 180.0, y: -90.0));
let wkb = geometry_to_wkb(&original).expect("encode should succeed");
let restored = wkb_to_geometry(&wkb).expect("decode should succeed");
match restored {
Geometry::Point(p) => {
assert!((p.x() - 180.0).abs() < 1e-10);
assert!((p.y() - (-90.0)).abs() < 1e-10);
}
_ => panic!("Expected Point"),
}
}
#[test]
fn test_decode_invalid_wkb() {
let invalid_bytes = vec![0x00, 0x01, 0x02, 0x03];
let result = wkb_to_geometry(&invalid_bytes);
assert!(result.is_err());
assert!(matches!(result, Err(WkbError::DecodeError(_))));
}
#[test]
fn test_decode_empty_bytes() {
let result = wkb_to_geometry(&[]);
assert!(result.is_err());
}
}