use geo_types::Geometry as GeoGeometry;
use geozero::{ToJson, ToWkt};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq)]
pub struct Geometry(pub GeoGeometry<f64>);
impl Geometry {
pub fn into_inner(self) -> GeoGeometry<f64> {
self.0
}
pub fn to_ewkt(&self, srid: i32) -> Result<String, String> {
let wkt = self.0.to_wkt().map_err(|e| e.to_string())?;
Ok(format!("SRID={srid};{wkt}"))
}
pub fn to_geojson_value(&self) -> Result<serde_json::Value, String> {
let json = self.0.to_json().map_err(|e| e.to_string())?;
serde_json::from_str(&json).map_err(|e| e.to_string())
}
pub fn from_geojson_value(value: &serde_json::Value) -> Result<Geometry, String> {
let s = value.to_string();
Self::from_geojson_str(&s)
}
pub fn from_geojson_str(s: &str) -> Result<Geometry, String> {
use geozero::ToGeo;
use geozero::geojson::GeoJson;
let geom = GeoJson(s).to_geo().map_err(|e| e.to_string())?;
Ok(Geometry(geom))
}
}
impl From<GeoGeometry<f64>> for Geometry {
fn from(g: GeoGeometry<f64>) -> Self {
Geometry(g)
}
}
impl Serialize for Geometry {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let value = self.to_geojson_value().map_err(serde::ser::Error::custom)?;
value.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Geometry {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = serde_json::Value::deserialize(deserializer)?;
Geometry::from_geojson_value(&value).map_err(serde::de::Error::custom)
}
}
impl sqlx::Type<sqlx::Postgres> for Geometry {
fn type_info() -> sqlx::postgres::PgTypeInfo {
sqlx::postgres::PgTypeInfo::with_name("geometry")
}
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
use sqlx::TypeInfo;
let name = ty.name().to_ascii_lowercase();
name == "geometry" || name == "geography"
}
}
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for Geometry {
fn decode(
value: sqlx::postgres::PgValueRef<'r>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let decoded: geozero::wkb::Decode<GeoGeometry<f64>> = sqlx::Decode::decode(value)?;
decoded
.geometry
.map(Geometry)
.ok_or_else(|| "decoded a NULL/empty PostGIS geometry".into())
}
}
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Geometry {
fn encode_by_ref(
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
geozero::wkb::Encode(self.0.clone()).encode_by_ref(buf)
}
}
pub fn coerce_to_ewkt(value: &serde_json::Value, srid: i32) -> Result<String, String> {
match value {
serde_json::Value::String(s) => Ok(s.clone()),
serde_json::Value::Object(_) => {
let geom = Geometry::from_geojson_value(value)?;
geom.to_ewkt(srid)
}
other => Err(format!(
"expected a GeoJSON geometry object or a WKT/EWKT string, got {other}"
)),
}
}
pub fn geometry_to_geojson(geom: &Geometry) -> Result<serde_json::Value, String> {
geom.to_geojson_value()
}