use core::ffi::CStr;
use crate::error::{Error, Result};
use crate::{Geom, IndexKind, Point, Rect};
enum ParseFormat {
Wkt,
Wkb,
GeoJson,
Hex,
GeoBin,
Auto,
}
fn parse_error_from_c(ptr: *const libc::c_char, format: ParseFormat) -> Error {
let msg = if ptr.is_null() {
"Unknown error".to_owned()
} else {
let c_str = unsafe { CStr::from_ptr(ptr) };
c_str.to_string_lossy().into_owned()
};
if msg == "no memory" {
return Error::OutOfMemory;
}
match format {
ParseFormat::Wkt => Error::WktParse(msg),
ParseFormat::Wkb => Error::WkbParse(msg),
ParseFormat::GeoJson => Error::GeoJsonParse(msg),
ParseFormat::Hex => Error::HexParse(msg),
ParseFormat::GeoBin => Error::GeoBinParse(msg),
ParseFormat::Auto => Error::Parse(msg),
}
}
impl Geom {
pub fn from_geojson(geojson: &str) -> Result<Self> {
Self::from_geojson_ix(geojson, IndexKind::Default)
}
pub fn from_geojson_ix(geojson: &str, ix: IndexKind) -> Result<Self> {
let ptr = unsafe {
tg_geom_sys::tg_parse_geojsonn_ix(
geojson.as_ptr() as *const libc::c_char,
geojson.len(),
ix.into(),
)
};
Self::from_parsed_ptr(ptr, ParseFormat::GeoJson)
}
pub fn from_wkt(wkt: &str) -> Result<Self> {
Self::from_wkt_ix(wkt, IndexKind::Default)
}
pub fn from_wkt_ix(wkt: &str, ix: IndexKind) -> Result<Self> {
let ptr = unsafe {
tg_geom_sys::tg_parse_wktn_ix(wkt.as_ptr() as *const libc::c_char, wkt.len(), ix.into())
};
Self::from_parsed_ptr(ptr, ParseFormat::Wkt)
}
pub fn from_wkb(wkb: &[u8]) -> Result<Self> {
Self::from_wkb_ix(wkb, IndexKind::Default)
}
pub fn from_wkb_ix(wkb: &[u8], ix: IndexKind) -> Result<Self> {
let ptr = unsafe { tg_geom_sys::tg_parse_wkb_ix(wkb.as_ptr(), wkb.len(), ix.into()) };
Self::from_parsed_ptr(ptr, ParseFormat::Wkb)
}
pub fn from_hex(hex: &str) -> Result<Self> {
Self::from_hex_ix(hex, IndexKind::Default)
}
pub fn from_hex_ix(hex: &str, ix: IndexKind) -> Result<Self> {
let ptr = unsafe {
tg_geom_sys::tg_parse_hexn_ix(hex.as_ptr() as *const libc::c_char, hex.len(), ix.into())
};
Self::from_parsed_ptr(ptr, ParseFormat::Hex)
}
pub fn from_geobin(geobin: &[u8]) -> Result<Self> {
Self::from_geobin_ix(geobin, IndexKind::Default)
}
pub fn from_geobin_ix(geobin: &[u8], ix: IndexKind) -> Result<Self> {
let ptr =
unsafe { tg_geom_sys::tg_parse_geobin_ix(geobin.as_ptr(), geobin.len(), ix.into()) };
Self::from_parsed_ptr(ptr, ParseFormat::GeoBin)
}
pub fn parse(data: &[u8]) -> Result<Self> {
Self::parse_ix(data, IndexKind::Default)
}
pub fn parse_ix(data: &[u8], ix: IndexKind) -> Result<Self> {
let ptr = unsafe {
tg_geom_sys::tg_parse_ix(data.as_ptr() as *const libc::c_void, data.len(), ix.into())
};
Self::from_parsed_ptr(ptr, ParseFormat::Auto)
}
fn from_parsed_ptr(ptr: *mut tg_geom_sys::tg_geom, format: ParseFormat) -> Result<Self> {
if ptr.is_null() {
return Err(Error::OutOfMemory);
}
let err_ptr = unsafe { tg_geom_sys::tg_geom_error(ptr) };
if !err_ptr.is_null() {
let err = parse_error_from_c(err_ptr, format);
unsafe { tg_geom_sys::tg_geom_free(ptr) };
return Err(err);
}
unsafe { Self::from_raw(ptr) }.ok_or(Error::OutOfMemory)
}
pub fn to_geojson(&self) -> String {
let needed =
unsafe { tg_geom_sys::tg_geom_geojson(self.as_ptr(), core::ptr::null_mut(), 0) };
if needed == 0 {
return String::new();
}
let mut buf = vec![0u8; needed + 1];
let written = unsafe {
tg_geom_sys::tg_geom_geojson(
self.as_ptr(),
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
)
};
buf.truncate(written);
String::from_utf8_lossy(&buf).into_owned()
}
pub fn to_wkt(&self) -> String {
let needed = unsafe { tg_geom_sys::tg_geom_wkt(self.as_ptr(), core::ptr::null_mut(), 0) };
if needed == 0 {
return String::new();
}
let mut buf = vec![0u8; needed + 1];
let written = unsafe {
tg_geom_sys::tg_geom_wkt(
self.as_ptr(),
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
)
};
buf.truncate(written);
String::from_utf8_lossy(&buf).into_owned()
}
pub fn to_wkb(&self) -> Vec<u8> {
let len = unsafe { tg_geom_sys::tg_geom_wkb(self.as_ptr(), core::ptr::null_mut(), 0) };
let mut buf = vec![0u8; len];
unsafe {
tg_geom_sys::tg_geom_wkb(self.as_ptr(), buf.as_mut_ptr(), len);
}
buf
}
pub fn to_hex(&self) -> String {
let needed = unsafe { tg_geom_sys::tg_geom_hex(self.as_ptr(), core::ptr::null_mut(), 0) };
if needed == 0 {
return String::new();
}
let mut buf = vec![0u8; needed + 1];
let written = unsafe {
tg_geom_sys::tg_geom_hex(
self.as_ptr(),
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
)
};
buf.truncate(written);
String::from_utf8_lossy(&buf).into_owned()
}
pub fn to_geobin(&self) -> Vec<u8> {
let len = unsafe { tg_geom_sys::tg_geom_geobin(self.as_ptr(), core::ptr::null_mut(), 0) };
let mut buf = vec![0u8; len];
unsafe {
tg_geom_sys::tg_geom_geobin(self.as_ptr(), buf.as_mut_ptr(), len);
}
buf
}
}
pub fn geobin_rect(geobin: &[u8]) -> Rect {
let r = unsafe { tg_geom_sys::tg_geobin_rect(geobin.as_ptr(), geobin.len()) };
r.into()
}
pub fn geobin_point(geobin: &[u8]) -> Point {
let p = unsafe { tg_geom_sys::tg_geobin_point(geobin.as_ptr(), geobin.len()) };
p.into()
}
pub fn geobin_fullrect(geobin: &[u8]) -> (i32, [f64; 4], [f64; 4]) {
let mut min = [0.0f64; 4];
let mut max = [0.0f64; 4];
let dims = unsafe {
tg_geom_sys::tg_geobin_fullrect(
geobin.as_ptr(),
geobin.len(),
min.as_mut_ptr(),
max.as_mut_ptr(),
)
};
(dims, min, max)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::GeomType;
fn p(x: f64, y: f64) -> Point {
Point::new(x, y)
}
fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
Rect::from_coords(min_x, min_y, max_x, max_y)
}
#[test]
fn test_wkt_point() {
let geom = Geom::from_wkt("POINT(1 2)").unwrap();
assert_eq!(geom.geom_type(), GeomType::Point);
assert_eq!(geom.point(), p(1.0, 2.0));
}
#[test]
fn test_wkt_point_z() {
let geom = Geom::from_wkt("POINT Z(1 2 3)").unwrap();
assert_eq!(geom.geom_type(), GeomType::Point);
assert!(geom.has_z());
assert_eq!(geom.z(), 3.0);
}
#[test]
fn test_wkt_point_empty() {
let geom = Geom::from_wkt("POINT EMPTY").unwrap();
assert_eq!(geom.geom_type(), GeomType::Point);
assert!(geom.is_empty());
}
#[test]
fn test_wkt_linestring() {
let geom = Geom::from_wkt("LINESTRING(0 0, 1 1, 2 2)").unwrap();
assert_eq!(geom.geom_type(), GeomType::LineString);
assert!(!geom.is_empty());
}
#[test]
fn test_wkt_linestring_empty() {
let geom = Geom::from_wkt("LINESTRING EMPTY").unwrap();
assert_eq!(geom.geom_type(), GeomType::LineString);
assert!(geom.is_empty());
}
#[test]
fn test_wkt_polygon() {
let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
assert_eq!(geom.geom_type(), GeomType::Polygon);
assert!(!geom.is_empty());
}
#[test]
fn test_wkt_polygon_with_hole() {
let geom =
Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 8 2, 8 8, 2 8, 2 2))")
.unwrap();
assert_eq!(geom.geom_type(), GeomType::Polygon);
let poly = geom.poly().unwrap();
assert_eq!(poly.num_holes(), 1);
}
#[test]
fn test_wkt_polygon_empty() {
let geom = Geom::from_wkt("POLYGON EMPTY").unwrap();
assert_eq!(geom.geom_type(), GeomType::Polygon);
assert!(geom.is_empty());
}
#[test]
fn test_wkt_multipoint() {
let geom = Geom::from_wkt("MULTIPOINT((1 2), (3 4))").unwrap();
assert_eq!(geom.geom_type(), GeomType::MultiPoint);
assert_eq!(geom.num_points(), 2);
}
#[test]
fn test_wkt_multilinestring() {
let geom = Geom::from_wkt("MULTILINESTRING((0 0, 1 1), (2 2, 3 3))").unwrap();
assert_eq!(geom.geom_type(), GeomType::MultiLineString);
assert_eq!(geom.num_lines(), 2);
}
#[test]
fn test_wkt_multipolygon() {
let geom = Geom::from_wkt(
"MULTIPOLYGON(((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))",
)
.unwrap();
assert_eq!(geom.geom_type(), GeomType::MultiPolygon);
assert_eq!(geom.num_polys(), 2);
}
#[test]
fn test_wkt_geometrycollection() {
let geom = Geom::from_wkt("GEOMETRYCOLLECTION(POINT(1 2), LINESTRING(0 0, 1 1))").unwrap();
assert_eq!(geom.geom_type(), GeomType::GeometryCollection);
assert_eq!(geom.num_geometries(), 2);
}
#[test]
fn test_wkt_parse_error() {
let result = Geom::from_wkt("INVALID WKT");
assert!(result.is_err());
match result {
Err(Error::WktParse(_)) => {}
_ => panic!("Expected WktParse error"),
}
assert!(Geom::from_wkt(" POINT ").is_err());
assert!(Geom::from_wkt("POINT").is_err());
assert!(Geom::from_wkt("POINT EMPTY").is_ok());
assert!(Geom::from_wkt("POINT ZM ()").is_err());
assert!(Geom::from_wkt("POINT Z ()").is_err());
assert!(Geom::from_wkt("POINT ()").is_err());
assert!(Geom::from_wkt("POINT ZM () asdf").is_err());
}
#[test]
fn test_wkb_roundtrip_point() {
let original = Geom::from_wkt("POINT(1 2)").unwrap();
let wkb = original.to_wkb();
let parsed = Geom::from_wkb(&wkb).unwrap();
assert_eq!(parsed.geom_type(), GeomType::Point);
assert_eq!(parsed.point(), p(1.0, 2.0));
}
#[test]
fn test_wkb_roundtrip_linestring() {
let original = Geom::from_wkt("LINESTRING(0 0, 1 1, 2 2)").unwrap();
let wkb = original.to_wkb();
let parsed = Geom::from_wkb(&wkb).unwrap();
assert_eq!(parsed.geom_type(), GeomType::LineString);
}
#[test]
fn test_wkb_roundtrip_polygon() {
let original = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
let wkb = original.to_wkb();
let parsed = Geom::from_wkb(&wkb).unwrap();
assert_eq!(parsed.geom_type(), GeomType::Polygon);
}
#[test]
fn test_wkb_invalid() {
let result = Geom::from_wkb(&[0x00, 0x01, 0x02, 0x03]);
assert!(result.is_err());
}
#[test]
fn test_hex_roundtrip() {
let original = Geom::from_wkt("POINT(1 2)").unwrap();
let hex = original.to_hex();
let parsed = Geom::from_hex(&hex).unwrap();
assert_eq!(parsed.geom_type(), GeomType::Point);
assert_eq!(parsed.point(), p(1.0, 2.0));
}
#[test]
fn test_hex_format() {
let geom = Geom::from_wkt("POINT(1 2)").unwrap();
let hex = geom.to_hex();
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_geojson_point() {
let geojson = r#"{"type":"Point","coordinates":[1,2]}"#;
let geom = Geom::from_geojson(geojson).unwrap();
assert_eq!(geom.geom_type(), GeomType::Point);
assert_eq!(geom.point(), p(1.0, 2.0));
}
#[test]
fn test_geojson_linestring() {
let geojson = r#"{"type":"LineString","coordinates":[[0,0],[1,1],[2,2]]}"#;
let geom = Geom::from_geojson(geojson).unwrap();
assert_eq!(geom.geom_type(), GeomType::LineString);
}
#[test]
fn test_geojson_polygon() {
let geojson = r#"{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"#;
let geom = Geom::from_geojson(geojson).unwrap();
assert_eq!(geom.geom_type(), GeomType::Polygon);
}
#[test]
fn test_geojson_feature() {
let geojson = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":{"name":"test"}}"#;
let geom = Geom::from_geojson(geojson).unwrap();
assert!(geom.is_feature());
assert_eq!(geom.geom_type(), GeomType::Point);
}
#[test]
fn test_geojson_roundtrip() {
let original = Geom::from_wkt("POINT(1 2)").unwrap();
let geojson = original.to_geojson();
let parsed = Geom::from_geojson(&geojson).unwrap();
assert_eq!(parsed.geom_type(), GeomType::Point);
assert_eq!(parsed.point(), p(1.0, 2.0));
}
#[test]
fn test_geojson_invalid() {
let result = Geom::from_geojson("{ invalid json }");
assert!(result.is_err());
}
#[test]
fn test_geobin_roundtrip_point() {
let original = Geom::from_wkt("POINT(1 2)").unwrap();
let geobin = original.to_geobin();
let parsed = Geom::from_geobin(&geobin).unwrap();
assert_eq!(parsed.geom_type(), GeomType::Point);
assert_eq!(parsed.point(), p(1.0, 2.0));
}
#[test]
fn test_geobin_roundtrip_polygon() {
let original = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
let geobin = original.to_geobin();
let parsed = Geom::from_geobin(&geobin).unwrap();
assert_eq!(parsed.geom_type(), GeomType::Polygon);
}
#[test]
fn test_geobin_rect() {
let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
let geobin = geom.to_geobin();
let rect = geobin_rect(&geobin);
assert_eq!(rect, r(0.0, 0.0, 10.0, 10.0));
}
#[test]
fn test_geobin_point_extraction() {
let geom = Geom::from_wkt("POINT(5 6)").unwrap();
let geobin = geom.to_geobin();
let point = geobin_point(&geobin);
assert_eq!(point, p(5.0, 6.0));
}
#[test]
fn test_geobin_fullrect() {
let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
let geobin = geom.to_geobin();
let (dims, min, max) = geobin_fullrect(&geobin);
assert!(dims >= 2);
assert_eq!(min[0], 0.0);
assert_eq!(min[1], 0.0);
assert_eq!(max[0], 10.0);
assert_eq!(max[1], 10.0);
}
#[test]
fn test_parse_auto_wkt() {
let data = b"POINT(1 2)";
let geom = Geom::parse(data).unwrap();
assert_eq!(geom.geom_type(), GeomType::Point);
}
#[test]
fn test_parse_auto_geojson() {
let data = br#"{"type":"Point","coordinates":[1,2]}"#;
let geom = Geom::parse(data).unwrap();
assert_eq!(geom.geom_type(), GeomType::Point);
}
#[test]
fn test_parse_with_index_kind() {
let geom_default = Geom::from_wkt_ix(
"POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))",
IndexKind::Default,
)
.unwrap();
let geom_natural = Geom::from_wkt_ix(
"POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))",
IndexKind::Natural,
)
.unwrap();
let geom_none = Geom::from_wkt_ix(
"POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))",
IndexKind::None,
)
.unwrap();
assert_eq!(geom_default.geom_type(), GeomType::Polygon);
assert_eq!(geom_natural.geom_type(), GeomType::Polygon);
assert_eq!(geom_none.geom_type(), GeomType::Polygon);
}
#[test]
fn test_to_wkt_point() {
let geom = Geom::new_point(p(1.0, 2.0)).unwrap();
let wkt = geom.to_wkt();
assert!(wkt.contains("POINT"));
assert!(wkt.contains("1"));
assert!(wkt.contains("2"));
}
#[test]
fn test_to_wkt_polygon() {
let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
let wkt = geom.to_wkt();
assert!(wkt.contains("POLYGON"));
}
#[test]
fn test_empty_wkt() {
let geom = Geom::from_wkt("POINT EMPTY").unwrap();
let wkt = geom.to_wkt();
assert!(wkt.contains("EMPTY"));
}
#[test]
fn test_multipoint_wkt() {
let geom = Geom::from_wkt("MULTIPOINT((0 0), (1 1), (2 2))").unwrap();
let wkt = geom.to_wkt();
assert!(wkt.contains("MULTIPOINT"));
}
#[test]
fn test_geometrycollection_wkt() {
let geom = Geom::from_wkt("GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0, 1 1))").unwrap();
let wkt = geom.to_wkt();
assert!(wkt.contains("GEOMETRYCOLLECTION"));
}
}