use crate::errors::{Error, Result};
use crate::{Feature, FeatureCollection, Geometry};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
use std::iter::FromIterator;
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged, try_from = "deserialize::RawGeoJson")]
pub enum GeoJson {
Geometry(Geometry),
Feature(Feature),
FeatureCollection(FeatureCollection),
}
impl<G: Into<Geometry>> From<G> for GeoJson {
fn from(geometry: G) -> Self {
GeoJson::Geometry(geometry.into())
}
}
impl<G: Into<Geometry>> FromIterator<G> for GeoJson {
fn from_iter<I: IntoIterator<Item = G>>(iter: I) -> Self {
let geometry_collection = Geometry::new_geometry_collection(iter);
GeoJson::Geometry(geometry_collection)
}
}
impl From<Feature> for GeoJson {
fn from(feature: Feature) -> Self {
GeoJson::Feature(feature)
}
}
impl From<FeatureCollection> for GeoJson {
fn from(feature_collection: FeatureCollection) -> GeoJson {
GeoJson::FeatureCollection(feature_collection)
}
}
impl From<Vec<Feature>> for GeoJson {
fn from(features: Vec<Feature>) -> GeoJson {
GeoJson::from(features.into_iter().collect::<FeatureCollection>())
}
}
impl TryFrom<GeoJson> for Geometry {
type Error = Error;
fn try_from(value: GeoJson) -> Result<Self> {
match value {
GeoJson::Geometry(g) => Ok(g),
GeoJson::Feature(_) => Err(Error::ExpectedType {
expected: "Geometry".to_string(),
actual: "Feature".to_string(),
}),
GeoJson::FeatureCollection(_) => Err(Error::ExpectedType {
expected: "Geometry".to_string(),
actual: "FeatureCollection".to_string(),
}),
}
}
}
impl TryFrom<GeoJson> for Feature {
type Error = Error;
fn try_from(value: GeoJson) -> Result<Self> {
match value {
GeoJson::Geometry(_) => Err(Error::ExpectedType {
expected: "Feature".to_string(),
actual: "Geometry".to_string(),
}),
GeoJson::Feature(f) => Ok(f),
GeoJson::FeatureCollection(_) => Err(Error::ExpectedType {
expected: "Feature".to_string(),
actual: "FeatureCollection".to_string(),
}),
}
}
}
impl TryFrom<GeoJson> for FeatureCollection {
type Error = Error;
fn try_from(value: GeoJson) -> Result<Self> {
match value {
GeoJson::Geometry(_) => Err(Error::ExpectedType {
expected: "FeatureCollection".to_string(),
actual: "Geometry".to_string(),
}),
GeoJson::Feature(_) => Err(Error::ExpectedType {
expected: "FeatureCollection".to_string(),
actual: "Feature".to_string(),
}),
GeoJson::FeatureCollection(f) => Ok(f),
}
}
}
impl GeoJson {
pub fn from_reader<R>(rdr: R) -> serde_json::Result<Self>
where
R: std::io::Read,
{
serde_json::from_reader(rdr)
}
pub fn to_string_pretty(self) -> Result<String> {
::serde_json::to_string_pretty(&self)
.map_err(Error::MalformedGeoJson)
.map(|s| s.to_string())
}
}
impl FromStr for GeoJson {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Ok(serde_json::from_str(s)?)
}
}
impl fmt::Display for GeoJson {
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 fmt::Display for Feature {
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 fmt::Display for Geometry {
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 fmt::Display for FeatureCollection {
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))
}
}
mod deserialize {
use crate::geometry::deserialize::{Coordinates, GeometryType, RawGeometry};
use crate::util::normalize_foreign_members;
use crate::{Bbox, Error, Feature, FeatureCollection, GeoJson, Geometry, JsonObject, feature};
use serde::Deserialize;
use std::convert::TryFrom;
#[derive(Debug, Clone, PartialEq, Deserialize)]
enum GeoJsonType {
Feature,
FeatureCollection,
Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon,
GeometryCollection,
}
impl GeoJsonType {
fn as_geometry_type(&self) -> Option<GeometryType> {
match self {
GeoJsonType::Point => Some(GeometryType::Point),
GeoJsonType::LineString => Some(GeometryType::LineString),
GeoJsonType::Polygon => Some(GeometryType::Polygon),
GeoJsonType::MultiPoint => Some(GeometryType::MultiPoint),
GeoJsonType::MultiLineString => Some(GeometryType::MultiLineString),
GeoJsonType::MultiPolygon => Some(GeometryType::MultiPolygon),
GeoJsonType::GeometryCollection => Some(GeometryType::GeometryCollection),
GeoJsonType::Feature | GeoJsonType::FeatureCollection => None,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(expecting = "GeoJson object")]
pub(crate) struct RawGeoJson {
r#type: GeoJsonType,
bbox: Option<Bbox>,
coordinates: Option<Coordinates>,
geometries: Option<Vec<Geometry>>,
features: Option<Vec<Feature>>,
id: Option<feature::Id>,
geometry: Option<Geometry>,
properties: Option<JsonObject>,
#[serde(flatten)]
foreign_members: Option<JsonObject>,
}
impl TryFrom<RawGeoJson> for GeoJson {
type Error = Error;
fn try_from(mut raw: RawGeoJson) -> crate::Result<Self> {
normalize_foreign_members(&mut raw.foreign_members);
match raw.r#type {
GeoJsonType::FeatureCollection => {
let features = raw.features.ok_or_else(|| {
use serde::de::Error as _;
Error::MalformedGeoJson(serde_json::Error::missing_field("features"))
})?;
Ok(GeoJson::FeatureCollection(FeatureCollection {
bbox: raw.bbox,
features,
foreign_members: raw.foreign_members,
}))
}
GeoJsonType::Feature => Ok(GeoJson::Feature(Feature {
bbox: raw.bbox,
geometry: raw.geometry,
id: raw.id,
properties: raw.properties,
foreign_members: raw.foreign_members,
})),
geojson_type => {
let geometry_type = geojson_type.as_geometry_type().expect(
"as_geometry_type returns Some for all variants except Feature/FeatureCollection",
);
let raw_geom = RawGeometry {
r#type: geometry_type,
coordinates: raw.coordinates,
geometries: raw.geometries,
bbox: raw.bbox,
foreign_members: raw.foreign_members,
};
Ok(GeoJson::Geometry(Geometry::try_from(raw_geom)?))
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{Error, Feature, FeatureCollection, GeoJson, Geometry};
use serde_json::json;
use std::str::FromStr;
#[test]
fn test_geojson_from_reader() {
let json_str = r#"{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": null
}"#;
let actual = GeoJson::from_reader(json_str.as_bytes()).unwrap();
let actual_as_json = serde_json::to_value(actual).unwrap();
let expected = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": null
});
assert_eq!(actual_as_json, expected);
}
#[test]
fn test_geojson_from_features() {
let features: Vec<Feature> = vec![
Geometry::new_point([0., 0., 0.]).into(),
Geometry::new_point([1., 1., 1.]).into(),
];
let geojson: GeoJson = features.into();
assert_eq!(
geojson,
GeoJson::FeatureCollection(FeatureCollection {
features: vec![
Feature {
bbox: None,
geometry: Some(Geometry::new_point([0., 0., 0.])),
id: None,
properties: None,
foreign_members: None,
},
Feature {
bbox: None,
geometry: Some(Geometry::new_point([1., 1., 1.])),
id: None,
properties: None,
foreign_members: None,
},
],
bbox: None,
foreign_members: None,
})
);
}
#[test]
fn test_missing_properties_key() {
let json_str = json!({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
})
.to_string();
let geojson = GeoJson::from_str(&json_str).unwrap();
assert_eq!(
geojson,
GeoJson::Feature(Feature {
bbox: None,
geometry: Some(Geometry::new_point([102.0, 0.5])),
id: None,
properties: None,
foreign_members: None,
})
);
}
#[test]
fn test_invalid_json() {
let geojson_str = r#"{
"type": "FeatureCollection",
"features": [
!INTENTIONAL_TYPO! {
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
-0.13583511114120483,
51.5218870403801
]
}
}
]
}"#;
assert!(matches!(
GeoJson::from_str(geojson_str),
Err(Error::MalformedGeoJson(_))
))
}
#[test]
fn geojson_with_invalid_type() {
let geojson_str = json!({
"type": "TYPO-FeatureCollection",
"features": []
})
.to_string();
let err = GeoJson::from_str(&geojson_str).unwrap_err();
assert!(matches!(err, Error::MalformedGeoJson(_)));
}
#[test]
fn geojson_with_missing_type() {
let geojson_str = json!({
"features": []
})
.to_string();
let err = GeoJson::from_str(&geojson_str).unwrap_err();
assert!(matches!(err, Error::MalformedGeoJson(_)));
}
#[test]
fn not_an_object() {
let geojson_str = "[]";
let err = GeoJson::from_str(geojson_str).unwrap_err();
assert!(matches!(err, Error::MalformedGeoJson(_)));
assert!(format!("{err}").contains("expected GeoJson object"));
}
}