use crate::error::PostgisError;
use crate::geometry::Geometry;
use async_trait::async_trait;
#[async_trait]
pub trait PostgisExt: Send + Sync {
async fn st_distance(&self, g1: &Geometry, g2: &Geometry) -> Result<f64, PostgisError>;
async fn st_contains(&self, outer: &Geometry, inner: &Geometry) -> Result<bool, PostgisError>;
async fn st_within(&self, inner: &Geometry, outer: &Geometry) -> Result<bool, PostgisError>;
async fn st_intersects(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
async fn st_area(&self, geom: &Geometry) -> Result<f64, PostgisError>;
async fn st_length(&self, geom: &Geometry) -> Result<f64, PostgisError>;
async fn st_buffer(&self, geom: &Geometry, distance: f64) -> Result<Geometry, PostgisError>;
async fn st_union(&self, g1: &Geometry, g2: &Geometry) -> Result<Geometry, PostgisError>;
async fn add_geometry_column(
&self,
table: &str,
column: &str,
srid: i32,
geom_type: &str,
dim: &str,
) -> Result<(), PostgisError>;
async fn create_spatial_index(&self, table: &str, column: &str) -> Result<(), PostgisError>;
}
#[derive(Debug, Clone)]
pub enum PostgisProvider {
Memory,
Stub,
#[cfg(feature = "real-postgis")]
RealPg(RealPgConfig),
}
#[cfg(feature = "real-postgis")]
#[derive(Debug, Clone, Default)]
pub struct RealPgConfig {
pub host: String,
pub port: u16,
pub database: String,
pub username: String,
pub password: String,
}
pub enum PostgisWrapper {
Memory(crate::memory::MemoryPostgis),
Stub(crate::stub::StubPostgis),
#[cfg(feature = "real-postgis")]
RealPg(Box<crate::real_postgis::RealPostgis>),
}
#[async_trait]
impl PostgisExt for PostgisWrapper {
async fn st_distance(&self, g1: &Geometry, g2: &Geometry) -> Result<f64, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_distance(g1, g2).await,
PostgisWrapper::Stub(p) => p.st_distance(g1, g2).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_distance(g1, g2).await,
}
}
async fn st_contains(&self, outer: &Geometry, inner: &Geometry) -> Result<bool, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_contains(outer, inner).await,
PostgisWrapper::Stub(p) => p.st_contains(outer, inner).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_contains(outer, inner).await,
}
}
async fn st_within(&self, inner: &Geometry, outer: &Geometry) -> Result<bool, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_within(inner, outer).await,
PostgisWrapper::Stub(p) => p.st_within(inner, outer).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_within(inner, outer).await,
}
}
async fn st_intersects(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_intersects(g1, g2).await,
PostgisWrapper::Stub(p) => p.st_intersects(g1, g2).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_intersects(g1, g2).await,
}
}
async fn st_area(&self, geom: &Geometry) -> Result<f64, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_area(geom).await,
PostgisWrapper::Stub(p) => p.st_area(geom).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_area(geom).await,
}
}
async fn st_length(&self, geom: &Geometry) -> Result<f64, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_length(geom).await,
PostgisWrapper::Stub(p) => p.st_length(geom).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_length(geom).await,
}
}
async fn st_buffer(&self, geom: &Geometry, distance: f64) -> Result<Geometry, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_buffer(geom, distance).await,
PostgisWrapper::Stub(p) => p.st_buffer(geom, distance).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_buffer(geom, distance).await,
}
}
async fn st_union(&self, g1: &Geometry, g2: &Geometry) -> Result<Geometry, PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.st_union(g1, g2).await,
PostgisWrapper::Stub(p) => p.st_union(g1, g2).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.st_union(g1, g2).await,
}
}
async fn add_geometry_column(
&self,
table: &str,
column: &str,
srid: i32,
geom_type: &str,
dim: &str,
) -> Result<(), PostgisError> {
match self {
PostgisWrapper::Memory(p) => {
p.add_geometry_column(table, column, srid, geom_type, dim)
.await
}
PostgisWrapper::Stub(p) => {
p.add_geometry_column(table, column, srid, geom_type, dim)
.await
}
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => {
p.add_geometry_column(table, column, srid, geom_type, dim)
.await
}
}
}
async fn create_spatial_index(&self, table: &str, column: &str) -> Result<(), PostgisError> {
match self {
PostgisWrapper::Memory(p) => p.create_spatial_index(table, column).await,
PostgisWrapper::Stub(p) => p.create_spatial_index(table, column).await,
#[cfg(feature = "real-postgis")]
PostgisWrapper::RealPg(p) => p.create_spatial_index(table, column).await,
}
}
}
pub struct PostgisBuilder {
provider: PostgisProvider,
}
impl PostgisBuilder {
pub fn new(provider: PostgisProvider) -> Self {
Self { provider }
}
pub fn build(self) -> Result<PostgisWrapper, PostgisError> {
match self.provider {
PostgisProvider::Memory => {
Ok(PostgisWrapper::Memory(crate::memory::MemoryPostgis::new()))
}
PostgisProvider::Stub => Ok(PostgisWrapper::Stub(crate::stub::StubPostgis::new())),
#[cfg(feature = "real-postgis")]
PostgisProvider::RealPg(config) => {
let real = crate::real_postgis::RealPostgis::new(config)?;
Ok(PostgisWrapper::RealPg(Box::new(real)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Point;
#[tokio::test]
async fn test_wrapper_memory_dispatch() {
let wrapper = PostgisBuilder::new(PostgisProvider::Memory)
.build()
.expect("build memory failed");
let p1 = Geometry::Point(Point::new(0.0, 0.0));
let p2 = Geometry::Point(Point::new(3.0, 4.0));
let dist = wrapper
.st_distance(&p1, &p2)
.await
.expect("distance failed");
assert!(
dist > 500_000.0 && dist < 600_000.0,
"expected ~555km, got {}",
dist
);
}
#[tokio::test]
async fn test_wrapper_stub_dispatch() {
let wrapper = PostgisBuilder::new(PostgisProvider::Stub)
.build()
.expect("build stub failed");
let p1 = Geometry::Point(Point::new(0.0, 0.0));
let p2 = Geometry::Point(Point::new(3.0, 4.0));
let _ = wrapper
.st_distance(&p1, &p2)
.await
.expect("stub distance failed");
}
}