mod ewkb;
pub mod linestring;
pub mod multilinestring;
pub mod multipoint;
pub mod multipolygon;
pub mod point;
pub mod polygon;
pub use linestring::LineString;
pub use multilinestring::MultiLineString;
pub use multipoint::MultiPoint;
pub use multipolygon::MultiPolygon;
pub use point::GeoPoint;
pub use polygon::Polygon;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GeoError {
#[error("invalid latitude {0}: must be finite and in -90.0..=90.0")]
InvalidLatitude(f64),
#[error("invalid longitude {0}: must be finite and in -180.0..=180.0")]
InvalidLongitude(f64),
#[error("malformed EWKB: {0}")]
MalformedEwkb(String),
#[error("unexpected SRID {0}: Djogi requires SRID 4326 (WGS-84)")]
UnexpectedSrid(u32),
#[cfg(feature = "spatial")]
#[error("invalid LineString: got {got} point(s), need at least {need}")]
InvalidLineString {
got: usize,
need: usize,
},
#[cfg(feature = "spatial")]
#[error("invalid Polygon: {reason}")]
InvalidPolygon {
reason: &'static str,
},
#[cfg(feature = "spatial")]
#[error("invalid MultiPoint: {reason}")]
InvalidMultiPoint {
reason: &'static str,
},
#[cfg(feature = "spatial")]
#[error("invalid MultiPolygon: {reason}")]
InvalidMultiPolygon {
reason: &'static str,
},
#[cfg(feature = "spatial")]
#[error("invalid MultiLineString: {reason}")]
InvalidMultiLineString {
reason: &'static str,
},
}
#[cfg(feature = "spatial")]
pub(crate) const GEOGRAPHY_TYPE_NAME: &str = "geography";
#[cfg(feature = "spatial")]
pub(crate) fn accepts_geography(ty: &postgres_types::Type) -> bool {
ty.name() == GEOGRAPHY_TYPE_NAME
}
#[cfg(feature = "spatial")]
macro_rules! impl_geography_codec {
($t:ty, $encode_into:path) => {
impl ::postgres_types::ToSql for $t {
fn to_sql(
&self,
_ty: &::postgres_types::Type,
out: &mut ::bytes::BytesMut,
) -> ::std::result::Result<
::postgres_types::IsNull,
::std::boxed::Box<
dyn ::std::error::Error + ::std::marker::Sync + ::std::marker::Send,
>,
> {
$encode_into(self, out);
Ok(::postgres_types::IsNull::No)
}
fn accepts(ty: &::postgres_types::Type) -> bool {
$crate::geo::accepts_geography(ty)
}
::postgres_types::to_sql_checked!();
}
impl<'a> ::postgres_types::FromSql<'a> for $t {
fn from_sql(
_ty: &::postgres_types::Type,
raw: &'a [u8],
) -> ::std::result::Result<
Self,
::std::boxed::Box<
dyn ::std::error::Error + ::std::marker::Sync + ::std::marker::Send,
>,
> {
<$t>::from_ewkb_bytes(raw).map_err(|e| ::std::boxed::Box::new(e) as _)
}
fn accepts(ty: &::postgres_types::Type) -> bool {
$crate::geo::accepts_geography(ty)
}
}
};
}
#[cfg(feature = "spatial")]
pub(crate) use impl_geography_codec;
#[cfg(feature = "spatial")]
mod sealed_value {
pub trait Sealed {}
}
#[cfg(feature = "spatial")]
pub trait GeographyValue: sealed_value::Sealed {
const GEO_TYPE_WORD: u32;
const SUBTYPE: crate::descriptor::GeographySubtype;
fn to_ewkb_bytes(&self) -> Vec<u8>;
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError>
where
Self: Sized;
}
#[cfg(feature = "spatial")]
impl sealed_value::Sealed for GeoPoint {}
#[cfg(feature = "spatial")]
impl GeographyValue for GeoPoint {
const GEO_TYPE_WORD: u32 = 0x20000001;
const SUBTYPE: crate::descriptor::GeographySubtype = crate::descriptor::GeographySubtype::Point;
fn to_ewkb_bytes(&self) -> Vec<u8> {
GeoPoint::to_ewkb_bytes(*self)
}
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError> {
GeoPoint::from_ewkb_bytes(bytes)
}
}
#[cfg(feature = "spatial")]
impl sealed_value::Sealed for LineString {}
#[cfg(feature = "spatial")]
impl GeographyValue for LineString {
const GEO_TYPE_WORD: u32 = 0x20000002;
const SUBTYPE: crate::descriptor::GeographySubtype =
crate::descriptor::GeographySubtype::LineString;
fn to_ewkb_bytes(&self) -> Vec<u8> {
LineString::to_ewkb_bytes(self)
}
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError> {
ewkb::decode_linestring(bytes)
}
}
#[cfg(feature = "spatial")]
impl sealed_value::Sealed for Polygon {}
#[cfg(feature = "spatial")]
impl GeographyValue for Polygon {
const GEO_TYPE_WORD: u32 = 0x20000003;
const SUBTYPE: crate::descriptor::GeographySubtype =
crate::descriptor::GeographySubtype::Polygon;
fn to_ewkb_bytes(&self) -> Vec<u8> {
Polygon::to_ewkb_bytes(self)
}
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError> {
ewkb::decode_polygon(bytes)
}
}
#[cfg(feature = "spatial")]
impl sealed_value::Sealed for MultiPoint {}
#[cfg(feature = "spatial")]
impl GeographyValue for MultiPoint {
const GEO_TYPE_WORD: u32 = 0x20000004;
const SUBTYPE: crate::descriptor::GeographySubtype =
crate::descriptor::GeographySubtype::MultiPoint;
fn to_ewkb_bytes(&self) -> Vec<u8> {
MultiPoint::to_ewkb_bytes(self)
}
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError> {
ewkb::decode_multipoint(bytes)
}
}
#[cfg(feature = "spatial")]
impl sealed_value::Sealed for MultiLineString {}
#[cfg(feature = "spatial")]
impl GeographyValue for MultiLineString {
const GEO_TYPE_WORD: u32 = 0x20000005;
const SUBTYPE: crate::descriptor::GeographySubtype =
crate::descriptor::GeographySubtype::MultiLineString;
fn to_ewkb_bytes(&self) -> Vec<u8> {
MultiLineString::to_ewkb_bytes(self)
}
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError> {
ewkb::decode_multilinestring(bytes)
}
}
#[cfg(feature = "spatial")]
impl sealed_value::Sealed for MultiPolygon {}
#[cfg(feature = "spatial")]
impl GeographyValue for MultiPolygon {
const GEO_TYPE_WORD: u32 = 0x20000006;
const SUBTYPE: crate::descriptor::GeographySubtype =
crate::descriptor::GeographySubtype::MultiPolygon;
fn to_ewkb_bytes(&self) -> Vec<u8> {
MultiPolygon::to_ewkb_bytes(self)
}
fn from_ewkb_bytes(bytes: &[u8]) -> Result<Self, GeoError> {
ewkb::decode_multipolygon(bytes)
}
}
#[cfg(feature = "spatial")]
mod sealed_spatial_column_value {
pub trait Sealed {}
}
#[cfg(feature = "spatial")]
pub trait SpatialColumnValue: sealed_spatial_column_value::Sealed {}
#[cfg(feature = "spatial")]
impl<G> sealed_spatial_column_value::Sealed for G where G: GeographyValue {}
#[cfg(feature = "spatial")]
impl<G> SpatialColumnValue for G where G: GeographyValue {}
#[cfg(feature = "spatial")]
impl<G> sealed_spatial_column_value::Sealed for Option<G> where G: GeographyValue {}
#[cfg(feature = "spatial")]
impl<G> SpatialColumnValue for Option<G> where G: GeographyValue {}
#[cfg(all(test, feature = "spatial"))]
mod geography_value_tests {
use super::*;
fn takes_geo<G: GeographyValue>() {}
#[test]
fn geopoint_is_geography_value() {
takes_geo::<GeoPoint>();
}
#[test]
fn linestring_is_geography_value() {
takes_geo::<LineString>();
}
#[test]
fn polygon_is_geography_value() {
takes_geo::<Polygon>();
}
#[test]
fn multipoint_is_geography_value() {
takes_geo::<MultiPoint>();
}
#[test]
fn multilinestring_is_geography_value() {
takes_geo::<MultiLineString>();
}
#[test]
fn multipolygon_is_geography_value() {
takes_geo::<MultiPolygon>();
}
}