use serde::{Deserialize, Serialize};
use crate::crs::CoordRefSys;
use crate::measures::Measures;
pub type Position = Vec<f64>;
pub type Bbox = Vec<f64>;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Geometry {
Point {
coordinates: Position,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiPoint {
coordinates: Vec<Position>,
#[serde(flatten)]
meta: GeometryMeta,
},
LineString {
coordinates: Vec<Position>,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiLineString {
coordinates: Vec<Vec<Position>>,
#[serde(flatten)]
meta: GeometryMeta,
},
Polygon {
coordinates: Vec<Vec<Position>>,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiPolygon {
coordinates: Vec<Vec<Vec<Position>>>,
#[serde(flatten)]
meta: GeometryMeta,
},
GeometryCollection {
geometries: Vec<Geometry>,
#[serde(flatten)]
meta: GeometryMeta,
},
Polyhedron {
coordinates: Vec<Vec<Vec<Vec<Position>>>>,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiPolyhedron {
coordinates: Vec<Vec<Vec<Vec<Vec<Position>>>>>,
#[serde(flatten)]
meta: GeometryMeta,
},
Prism {
base: Box<Geometry>,
#[serde(skip_serializing_if = "Option::is_none", default)]
lower: Option<f64>,
upper: f64,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiPrism {
prisms: Vec<Geometry>,
#[serde(flatten)]
meta: GeometryMeta,
},
CircularString {
coordinates: Vec<Position>,
#[serde(flatten)]
meta: GeometryMeta,
},
CompoundCurve {
geometries: Vec<Geometry>,
#[serde(flatten)]
meta: GeometryMeta,
},
CurvePolygon {
geometries: Vec<Geometry>,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiCurve {
geometries: Vec<Geometry>,
#[serde(flatten)]
meta: GeometryMeta,
},
MultiSurface {
geometries: Vec<Geometry>,
#[serde(flatten)]
meta: GeometryMeta,
},
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct GeometryMeta {
#[serde(
rename = "coordRefSys",
skip_serializing_if = "Option::is_none",
default
)]
pub coord_ref_sys: Option<CoordRefSys>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub measures: Option<Measures>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub bbox: Option<Bbox>,
#[serde(flatten)]
pub foreign_members: crate::JsonObject,
}
impl Geometry {
pub fn geometry_type(&self) -> &'static str {
match self {
Geometry::Point { .. } => "Point",
Geometry::MultiPoint { .. } => "MultiPoint",
Geometry::LineString { .. } => "LineString",
Geometry::MultiLineString { .. } => "MultiLineString",
Geometry::Polygon { .. } => "Polygon",
Geometry::MultiPolygon { .. } => "MultiPolygon",
Geometry::GeometryCollection { .. } => "GeometryCollection",
Geometry::Polyhedron { .. } => "Polyhedron",
Geometry::MultiPolyhedron { .. } => "MultiPolyhedron",
Geometry::Prism { .. } => "Prism",
Geometry::MultiPrism { .. } => "MultiPrism",
Geometry::CircularString { .. } => "CircularString",
Geometry::CompoundCurve { .. } => "CompoundCurve",
Geometry::CurvePolygon { .. } => "CurvePolygon",
Geometry::MultiCurve { .. } => "MultiCurve",
Geometry::MultiSurface { .. } => "MultiSurface",
}
}
pub fn is_geojson(&self) -> bool {
matches!(
self,
Geometry::Point { .. }
| Geometry::MultiPoint { .. }
| Geometry::LineString { .. }
| Geometry::MultiLineString { .. }
| Geometry::Polygon { .. }
| Geometry::MultiPolygon { .. }
| Geometry::GeometryCollection { .. }
)
}
pub fn meta(&self) -> &GeometryMeta {
match self {
Geometry::Point { meta, .. }
| Geometry::MultiPoint { meta, .. }
| Geometry::LineString { meta, .. }
| Geometry::MultiLineString { meta, .. }
| Geometry::Polygon { meta, .. }
| Geometry::MultiPolygon { meta, .. }
| Geometry::GeometryCollection { meta, .. }
| Geometry::Polyhedron { meta, .. }
| Geometry::MultiPolyhedron { meta, .. }
| Geometry::Prism { meta, .. }
| Geometry::MultiPrism { meta, .. }
| Geometry::CircularString { meta, .. }
| Geometry::CompoundCurve { meta, .. }
| Geometry::CurvePolygon { meta, .. }
| Geometry::MultiCurve { meta, .. }
| Geometry::MultiSurface { meta, .. } => meta,
}
}
pub fn point(coordinates: impl Into<Position>) -> Self {
Geometry::Point {
coordinates: coordinates.into(),
meta: GeometryMeta::default(),
}
}
}
#[cfg(feature = "geojson")]
mod geojson_interop {
use super::{Geometry, GeometryMeta};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NotGeoJson(pub &'static str);
impl std::fmt::Display for NotGeoJson {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"geometry type `{}` cannot be represented as GeoJSON",
self.0
)
}
}
impl std::error::Error for NotGeoJson {}
fn pos_in(p: geojson::Position) -> super::Position {
p.as_slice().to_vec()
}
fn line_in(v: Vec<geojson::Position>) -> Vec<super::Position> {
v.into_iter().map(pos_in).collect()
}
fn poly_in(v: Vec<Vec<geojson::Position>>) -> Vec<Vec<super::Position>> {
v.into_iter().map(line_in).collect()
}
fn line_out(v: Vec<super::Position>) -> Vec<geojson::Position> {
v.into_iter().map(Into::into).collect()
}
fn poly_out(v: Vec<Vec<super::Position>>) -> Vec<Vec<geojson::Position>> {
v.into_iter().map(line_out).collect()
}
impl From<geojson::Geometry> for Geometry {
fn from(g: geojson::Geometry) -> Self {
let meta = GeometryMeta {
bbox: g.bbox,
..Default::default()
};
match g.value {
geojson::GeometryValue::Point { coordinates } => Geometry::Point {
coordinates: pos_in(coordinates),
meta,
},
geojson::GeometryValue::MultiPoint { coordinates } => Geometry::MultiPoint {
coordinates: line_in(coordinates),
meta,
},
geojson::GeometryValue::LineString { coordinates } => Geometry::LineString {
coordinates: line_in(coordinates),
meta,
},
geojson::GeometryValue::MultiLineString { coordinates } => {
Geometry::MultiLineString {
coordinates: coordinates.into_iter().map(line_in).collect(),
meta,
}
}
geojson::GeometryValue::Polygon { coordinates } => Geometry::Polygon {
coordinates: poly_in(coordinates),
meta,
},
geojson::GeometryValue::MultiPolygon { coordinates } => Geometry::MultiPolygon {
coordinates: coordinates.into_iter().map(poly_in).collect(),
meta,
},
geojson::GeometryValue::GeometryCollection { geometries } => {
Geometry::GeometryCollection {
geometries: geometries.into_iter().map(Geometry::from).collect(),
meta,
}
}
}
}
}
impl TryFrom<Geometry> for geojson::Geometry {
type Error = NotGeoJson;
fn try_from(g: Geometry) -> Result<Self, Self::Error> {
let bbox = g.meta().bbox.clone();
let value = match g {
Geometry::Point { coordinates, .. } => geojson::GeometryValue::Point {
coordinates: coordinates.into(),
},
Geometry::MultiPoint { coordinates, .. } => geojson::GeometryValue::MultiPoint {
coordinates: line_out(coordinates),
},
Geometry::LineString { coordinates, .. } => geojson::GeometryValue::LineString {
coordinates: line_out(coordinates),
},
Geometry::MultiLineString { coordinates, .. } => {
geojson::GeometryValue::MultiLineString {
coordinates: coordinates.into_iter().map(line_out).collect(),
}
}
Geometry::Polygon { coordinates, .. } => geojson::GeometryValue::Polygon {
coordinates: poly_out(coordinates),
},
Geometry::MultiPolygon { coordinates, .. } => {
geojson::GeometryValue::MultiPolygon {
coordinates: coordinates.into_iter().map(poly_out).collect(),
}
}
Geometry::GeometryCollection { geometries, .. } => {
geojson::GeometryValue::GeometryCollection {
geometries: geometries
.into_iter()
.map(geojson::Geometry::try_from)
.collect::<Result<Vec<_>, _>>()?,
}
}
other => return Err(NotGeoJson(other.geometry_type())),
};
Ok(geojson::Geometry {
bbox,
value,
foreign_members: None,
})
}
}
}
#[cfg(feature = "geojson")]
pub use geojson_interop::NotGeoJson;