use std::str::FromStr;
use std::{convert::TryFrom, fmt};
use crate::errors::{Error, Result};
use crate::{Bbox, LineStringType, PointType, PolygonType, Position};
use crate::{JsonObject, JsonValue};
use serde::{Deserialize, Serialize};
#[deprecated(note = "Renamed to GeometryValue")]
pub type Value = GeometryValue;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum GeometryValue {
Point { coordinates: PointType },
MultiPoint { coordinates: Vec<PointType> },
LineString { coordinates: LineStringType },
MultiLineString { coordinates: Vec<LineStringType> },
Polygon { coordinates: PolygonType },
MultiPolygon { coordinates: Vec<PolygonType> },
GeometryCollection { geometries: Vec<Geometry> },
}
impl GeometryValue {
pub fn type_name(&self) -> &'static str {
match self {
GeometryValue::Point { .. } => "Point",
GeometryValue::MultiPoint { .. } => "MultiPoint",
GeometryValue::LineString { .. } => "LineString",
GeometryValue::MultiLineString { .. } => "MultiLineString",
GeometryValue::Polygon { .. } => "Polygon",
GeometryValue::MultiPolygon { .. } => "MultiPolygon",
GeometryValue::GeometryCollection { .. } => "GeometryCollection",
}
}
pub fn new_point(value: impl Into<Position>) -> GeometryValue {
GeometryValue::Point {
coordinates: value.into(),
}
}
pub fn new_line_string(value: impl IntoIterator<Item = impl Into<Position>>) -> GeometryValue {
let coordinates: Vec<Position> = value.into_iter().map(Into::into).collect();
GeometryValue::LineString { coordinates }
}
pub fn new_multi_point(value: impl IntoIterator<Item = impl Into<Position>>) -> GeometryValue {
let coordinates: Vec<Position> = value.into_iter().map(Into::into).collect();
GeometryValue::MultiPoint { coordinates }
}
pub fn new_multi_line_string(
value: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Position>>>,
) -> GeometryValue {
let coordinates: Vec<Vec<Position>> = value
.into_iter()
.map(|line_string| line_string.into_iter().map(Into::into).collect())
.collect();
GeometryValue::MultiLineString { coordinates }
}
pub fn new_polygon(
value: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Position>>>,
) -> GeometryValue {
let coordinates: Vec<Vec<Position>> = value
.into_iter()
.map(|ring| ring.into_iter().map(Into::into).collect())
.collect();
GeometryValue::Polygon { coordinates }
}
pub fn new_multi_polygon(
value: impl IntoIterator<
Item = impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Position>>>,
>,
) -> GeometryValue {
let coordinates: Vec<Vec<Vec<Position>>> = value
.into_iter()
.map(|polygon| {
polygon
.into_iter()
.map(|ring| ring.into_iter().map(Into::into).collect())
.collect()
})
.collect();
GeometryValue::MultiPolygon { coordinates }
}
pub fn new_geometry_collection(
value: impl IntoIterator<Item = impl Into<Geometry>>,
) -> GeometryValue {
let geometries: Vec<Geometry> = value.into_iter().map(Into::into).collect();
GeometryValue::GeometryCollection { geometries }
}
}
impl fmt::Display for GeometryValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
::serde_json::to_string(self)
.map_err(|_| fmt::Error)
.and_then(|s| f.write_str(&s))
}
}
impl<'a> From<&'a GeometryValue> for JsonValue {
fn from(value: &'a GeometryValue) -> JsonValue {
::serde_json::to_value(value).unwrap()
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "deserialize::RawGeometry")]
pub struct Geometry {
#[serde(skip_serializing_if = "Option::is_none")]
pub bbox: Option<Bbox>,
#[serde(flatten)]
pub value: GeometryValue,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub foreign_members: Option<JsonObject>,
}
impl Geometry {
pub fn new(value: GeometryValue) -> Self {
Geometry {
bbox: None,
value,
foreign_members: None,
}
}
pub fn new_point(value: impl Into<Position>) -> Geometry {
Self::new(GeometryValue::new_point(value))
}
pub fn new_line_string(value: impl IntoIterator<Item = impl Into<Position>>) -> Geometry {
Self::new(GeometryValue::new_line_string(value))
}
pub fn new_multi_point(value: impl IntoIterator<Item = impl Into<Position>>) -> Geometry {
Self::new(GeometryValue::new_multi_point(value))
}
pub fn new_multi_line_string(
value: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Position>>>,
) -> Geometry {
Self::new(GeometryValue::new_multi_line_string(value))
}
pub fn new_polygon(
value: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Position>>>,
) -> Geometry {
Self::new(GeometryValue::new_polygon(value))
}
pub fn new_multi_polygon(
value: impl IntoIterator<
Item = impl IntoIterator<Item = impl IntoIterator<Item = impl Into<Position>>>,
>,
) -> Geometry {
Self::new(GeometryValue::new_multi_polygon(value))
}
pub fn new_geometry_collection(
value: impl IntoIterator<Item = impl Into<Geometry>>,
) -> Geometry {
Self::new(GeometryValue::new_geometry_collection(value))
}
}
impl FromStr for Geometry {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(serde_json::from_str(s)?)
}
}
impl<V> From<V> for Geometry
where
V: Into<GeometryValue>,
{
fn from(v: V) -> Geometry {
Geometry::new(v.into())
}
}
pub(crate) mod deserialize {
use super::*;
use crate::util::normalize_foreign_members;
use serde::de::{Deserializer, SeqAccess, Visitor};
use std::fmt::{Display, Formatter};
use tinyvec::TinyVec;
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum GeometryType {
Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon,
GeometryCollection,
}
impl Display for GeometryType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
GeometryType::Point => "Point",
GeometryType::LineString => "LineString",
GeometryType::Polygon => "Polygon",
GeometryType::MultiPoint => "MultiPoint",
GeometryType::MultiLineString => "MultiLineString",
GeometryType::MultiPolygon => "MultiPolygon",
GeometryType::GeometryCollection => "GeometryCollection",
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum Coordinates {
ZeroDimensional(Position),
OneDimensional(Vec<Position>),
TwoDimensional(Vec<Vec<Position>>),
ThreeDimensional(Vec<Vec<Vec<Position>>>),
}
impl Coordinates {
fn dimensions(&self) -> u8 {
match self {
Coordinates::ZeroDimensional(_) => 0,
Coordinates::OneDimensional(_) => 1,
Coordinates::TwoDimensional(_) => 2,
Coordinates::ThreeDimensional(_) => 3,
}
}
}
impl<'de> Deserialize<'de> for Coordinates {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum CoordsElement {
Float(f64),
Coords(Coordinates),
}
impl<'de> Deserialize<'de> for CoordsElement {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct CoordsElementVisitor;
impl<'de> Visitor<'de> for CoordsElementVisitor {
type Value = CoordsElement;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a coordinate element (number or array)")
}
fn visit_i64<E>(self, value: i64) -> std::result::Result<CoordsElement, E> {
Ok(CoordsElement::Float(value as f64))
}
fn visit_u64<E>(self, value: u64) -> std::result::Result<CoordsElement, E> {
Ok(CoordsElement::Float(value as f64))
}
fn visit_f64<E>(self, value: f64) -> std::result::Result<CoordsElement, E> {
Ok(CoordsElement::Float(value))
}
fn visit_seq<A>(
self,
mut seq: A,
) -> std::result::Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let coords = match seq.next_element::<CoordsElement>()? {
None => Coordinates::OneDimensional(vec![]),
Some(CoordsElement::Float(first)) => {
let mut floats = TinyVec::<[f64; 2]>::new();
floats.push(first);
while let Some(next) = seq.next_element::<f64>()? {
floats.push(next);
}
Coordinates::ZeroDimensional(Position::from(floats))
}
Some(CoordsElement::Coords(coords)) => match coords {
Coordinates::ZeroDimensional(first) => {
let mut positions_1d = vec![first];
while let Some(next) = seq.next_element::<Position>()? {
positions_1d.push(next);
}
Coordinates::OneDimensional(positions_1d)
}
Coordinates::OneDimensional(positions_1d) => {
let mut positions_2d = vec![positions_1d];
while let Some(next) =
seq.next_element::<Vec<Position>>()?
{
positions_2d.push(next);
}
Coordinates::TwoDimensional(positions_2d)
}
Coordinates::TwoDimensional(positions_2d) => {
let mut positions_3d = vec![positions_2d];
while let Some(next) =
seq.next_element::<Vec<Vec<Position>>>()?
{
positions_3d.push(next);
}
Coordinates::ThreeDimensional(positions_3d)
}
Coordinates::ThreeDimensional(_) => {
return Err(serde::de::Error::custom(
"coordinate nesting too deep",
));
}
},
};
Ok(CoordsElement::Coords(coords))
}
}
deserializer.deserialize_any(CoordsElementVisitor)
}
}
match CoordsElement::deserialize(deserializer)? {
CoordsElement::Float(_) => {
Err(serde::de::Error::custom("expected array, got number"))
}
CoordsElement::Coords(coords) => Ok(coords),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(expecting = "Geometry object")]
pub(crate) struct RawGeometry {
pub(crate) r#type: GeometryType,
#[serde(default)]
pub(crate) coordinates: Option<Coordinates>,
#[serde(default)]
pub(crate) geometries: Option<Vec<Geometry>>,
#[serde(default)]
pub(crate) bbox: Option<Bbox>,
#[serde(flatten)]
pub(crate) foreign_members: Option<JsonObject>,
}
impl TryFrom<RawGeometry> for Geometry {
type Error = Error;
fn try_from(mut raw: RawGeometry) -> Result<Self> {
normalize_foreign_members(&mut raw.foreign_members);
let value = match (raw.r#type, raw.coordinates, raw.geometries) {
(GeometryType::Point, Some(Coordinates::ZeroDimensional(coordinates)), None) => {
if coordinates.len() < 2 {
return Err(Error::PositionTooShort(coordinates.len()));
}
GeometryValue::Point { coordinates }
}
(GeometryType::Point, Some(Coordinates::OneDimensional(coordinates)), None)
if coordinates.is_empty() =>
{
return Err(Error::PositionTooShort(0));
}
(
GeometryType::LineString,
Some(Coordinates::OneDimensional(coordinates)),
None,
) => GeometryValue::LineString { coordinates },
(GeometryType::Polygon, Some(Coordinates::TwoDimensional(coordinates)), None) => {
GeometryValue::Polygon { coordinates }
}
(GeometryType::Polygon, Some(Coordinates::OneDimensional(coordinates)), None)
if coordinates.is_empty() =>
{
GeometryValue::Polygon {
coordinates: vec![],
}
}
(
GeometryType::MultiPoint,
Some(Coordinates::OneDimensional(coordinates)),
None,
) => GeometryValue::MultiPoint { coordinates },
(
GeometryType::MultiLineString,
Some(Coordinates::TwoDimensional(coordinates)),
None,
) => GeometryValue::MultiLineString { coordinates },
(
GeometryType::MultiLineString,
Some(Coordinates::OneDimensional(coordinates)),
None,
) if coordinates.is_empty() => GeometryValue::MultiLineString {
coordinates: vec![],
},
(
GeometryType::MultiPolygon,
Some(Coordinates::ThreeDimensional(coordinates)),
None,
) => GeometryValue::MultiPolygon { coordinates },
(
GeometryType::MultiPolygon,
Some(Coordinates::OneDimensional(coordinates)),
None,
) if coordinates.is_empty() => GeometryValue::MultiPolygon {
coordinates: vec![],
},
(GeometryType::GeometryCollection, _, Some(geometries)) => {
GeometryValue::GeometryCollection { geometries }
}
(GeometryType::GeometryCollection, _, None) => {
return Err(Error::GeometryCollectionWithoutGeometriesKey);
}
(
geometry_type @ (GeometryType::Point
| GeometryType::LineString
| GeometryType::Polygon
| GeometryType::MultiPoint
| GeometryType::MultiLineString
| GeometryType::MultiPolygon),
coords,
_,
) => {
return if let Some(coords) = coords {
let dimensions = coords.dimensions();
Err(Error::InvalidGeometryDimensions {
geometry_type,
dimensions,
})
} else {
Err(Error::GeometryWithoutCoordinatesKey { geometry_type })
};
}
};
Ok(Geometry {
bbox: raw.bbox,
value,
foreign_members: raw.foreign_members,
})
}
}
}
#[cfg(test)]
mod tests {
use crate::{FeatureCollection, GeoJson, Geometry, GeometryValue, JsonObject};
use serde_json::json;
use std::str::FromStr;
fn encode(geometry: &Geometry) -> String {
serde_json::to_string(&geometry).unwrap()
}
fn decode(json_string: String) -> GeoJson {
json_string.parse().unwrap()
}
#[test]
fn encode_decode_geometry() {
let geometry_json_str = "{\"type\":\"Point\",\"coordinates\":[1.1,2.1]}";
let geometry = Geometry::new_point([1.1, 2.1]);
let json_string = encode(&geometry);
assert_eq!(json_string, geometry_json_str);
let decoded_geometry = match decode(json_string) {
GeoJson::Geometry(g) => g,
_ => unreachable!(),
};
assert_eq!(decoded_geometry, geometry);
}
#[test]
fn parsing() {
let geojson_str = json!({
"type": "Point",
"coordinates": [1.1, 2.1]
})
.to_string();
let geometry_1: Geometry = geojson_str.parse().unwrap();
let geometry_2: Geometry = serde_json::from_str(&geojson_str).unwrap();
assert_eq!(geometry_1, geometry_2);
let GeoJson::Geometry(geometry_3): GeoJson = geojson_str.parse().unwrap() else {
panic!("unexpected GeoJSON type");
};
let GeoJson::Geometry(geometry_4): GeoJson = serde_json::from_str(&geojson_str).unwrap()
else {
panic!("unexpected GeoJSON type");
};
assert_eq!(geometry_3, geometry_4);
assert_eq!(geometry_1, geometry_4);
}
#[test]
fn wrong_type() {
let geojson_str = json!({
"type": "FeatureCollection",
"features": []
})
.to_string();
FeatureCollection::from_str(&geojson_str).unwrap();
Geometry::from_str(&geojson_str).unwrap_err();
serde_json::from_str::<Geometry>(&geojson_str).unwrap_err();
}
#[test]
fn test_geometry_display() {
let geometry = Geometry::new_line_string([[0.0, 0.1], [0.1, 0.2], [0.2, 0.3]]);
assert_eq!(
geometry.to_string(),
"{\"type\":\"LineString\",\"coordinates\":[[0.0,0.1],[0.1,0.2],[0.2,0.3]]}"
);
}
#[test]
fn test_value_display() {
let v = GeometryValue::new_line_string([[0.0, 0.1], [0.1, 0.2], [0.2, 0.3]]);
assert_eq!(
r#"{"type":"LineString","coordinates":[[0.0,0.1],[0.1,0.2],[0.2,0.3]]}"#,
v.to_string()
);
}
#[test]
fn encode_decode_geometry_with_foreign_member() {
let geometry_json_str =
"{\"type\":\"Point\",\"coordinates\":[1.1,2.1],\"other_member\":true}";
let mut foreign_members = JsonObject::new();
foreign_members.insert(
String::from("other_member"),
serde_json::to_value(true).unwrap(),
);
let geometry = Geometry {
value: GeometryValue::new_point([1.1, 2.1]),
bbox: None,
foreign_members: Some(foreign_members),
};
let json_string = encode(&geometry);
assert_eq!(json_string, geometry_json_str);
let decoded_geometry = match decode(geometry_json_str.into()) {
GeoJson::Geometry(g) => g,
_ => unreachable!(),
};
assert_eq!(decoded_geometry, geometry);
}
#[test]
fn encode_decode_geometry_collection() {
let geometry_collection = Geometry::new_geometry_collection([
Geometry::new_point([100.0, 0.0]),
Geometry::new_line_string([[101.0, 0.0], [102.0, 1.0]]),
]);
let geometry_collection_string = "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[100.0,0.0]},{\"type\":\"LineString\",\"coordinates\":[[101.0,0.0],[102.0,1.0]]}]}";
let json_string = encode(&geometry_collection);
assert_eq!(json_string, geometry_collection_string);
let decoded_geometry = match decode(geometry_collection_string.into()) {
GeoJson::Geometry(g) => g,
_ => unreachable!(),
};
assert_eq!(decoded_geometry, geometry_collection);
}
#[test]
fn test_from_str_ok() {
let geometry_json = json!({
"type": "Point",
"coordinates": [125.6f64, 10.1]
})
.to_string();
let geometry = Geometry::from_str(&geometry_json).unwrap();
assert!(matches!(geometry.value, GeometryValue::Point { .. }));
}
#[test]
fn test_reject_too_few_coordinates() {
let err = Geometry::from_str(r#"{"type": "Point", "coordinates": []}"#).unwrap_err();
assert!(
err.to_string()
.contains("A position must contain two or more elements, but got `0`")
);
let err = Geometry::from_str(r#"{"type": "Point", "coordinates": [23.42]}"#).unwrap_err();
assert!(
err.to_string()
.contains("A position must contain two or more elements, but got `1`")
);
}
}