geojson/
geojson.rs

1// Copyright 2015 The GeoRust Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//  http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::errors::{Error, Result};
16use crate::{Feature, FeatureCollection, Geometry};
17use crate::{JsonObject, JsonValue};
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19use std::convert::TryFrom;
20use std::fmt;
21use std::iter::FromIterator;
22use std::str::FromStr;
23
24/// GeoJSON Objects
25///
26/// ```
27/// use std::convert::TryInto;
28/// use geojson::{Feature, GeoJson, Geometry, Value};
29/// use serde_json::json;
30/// let json_value = json!({
31///     "type": "Feature",
32///     "geometry": {
33///         "type": "Point",
34///         "coordinates": [102.0, 0.5]
35///     },
36///     "properties": null,
37/// });
38/// let feature: Feature = json_value.try_into().unwrap();
39///
40/// // Easily convert a feature to a GeoJson
41/// let geojson: GeoJson = feature.into();
42/// // and back again
43/// let feature2: Feature = geojson.try_into().unwrap();
44/// ```
45/// [GeoJSON Format Specification ยง 3](https://tools.ietf.org/html/rfc7946#section-3)
46#[derive(Clone, Debug, PartialEq)]
47pub enum GeoJson {
48    Geometry(Geometry),
49    Feature(Feature),
50    FeatureCollection(FeatureCollection),
51}
52
53impl<'a> From<&'a GeoJson> for JsonObject {
54    fn from(geojson: &'a GeoJson) -> JsonObject {
55        match *geojson {
56            GeoJson::Geometry(ref geometry) => geometry.into(),
57            GeoJson::Feature(ref feature) => feature.into(),
58            GeoJson::FeatureCollection(ref fc) => fc.into(),
59        }
60    }
61}
62
63impl From<GeoJson> for JsonValue {
64    fn from(geojson: GeoJson) -> JsonValue {
65        match geojson {
66            GeoJson::Geometry(geometry) => JsonValue::Object(JsonObject::from(&geometry)),
67            GeoJson::Feature(feature) => JsonValue::Object(JsonObject::from(&feature)),
68            GeoJson::FeatureCollection(fc) => JsonValue::Object(JsonObject::from(&fc)),
69        }
70    }
71}
72
73impl<G: Into<Geometry>> From<G> for GeoJson {
74    fn from(geometry: G) -> Self {
75        GeoJson::Geometry(geometry.into())
76    }
77}
78
79impl<G: Into<Geometry>> FromIterator<G> for GeoJson {
80    fn from_iter<I: IntoIterator<Item = G>>(iter: I) -> Self {
81        use crate::Value;
82        let geometries = iter.into_iter().map(|g| g.into()).collect();
83        let collection = Value::GeometryCollection(geometries);
84        GeoJson::Geometry(Geometry::new(collection))
85    }
86}
87
88impl From<Feature> for GeoJson {
89    fn from(feature: Feature) -> Self {
90        GeoJson::Feature(feature)
91    }
92}
93
94impl From<FeatureCollection> for GeoJson {
95    fn from(feature_collection: FeatureCollection) -> GeoJson {
96        GeoJson::FeatureCollection(feature_collection)
97    }
98}
99
100impl From<Vec<Feature>> for GeoJson {
101    fn from(features: Vec<Feature>) -> GeoJson {
102        GeoJson::from(features.into_iter().collect::<FeatureCollection>())
103    }
104}
105
106impl TryFrom<GeoJson> for Geometry {
107    type Error = Error;
108    fn try_from(value: GeoJson) -> Result<Self> {
109        match value {
110            GeoJson::Geometry(g) => Ok(g),
111            GeoJson::Feature(_) => Err(Error::ExpectedType {
112                expected: "Geometry".to_string(),
113                actual: "Feature".to_string(),
114            }),
115            GeoJson::FeatureCollection(_) => Err(Error::ExpectedType {
116                expected: "Geometry".to_string(),
117                actual: "FeatureCollection".to_string(),
118            }),
119        }
120    }
121}
122
123impl TryFrom<GeoJson> for Feature {
124    type Error = Error;
125    fn try_from(value: GeoJson) -> Result<Self> {
126        match value {
127            GeoJson::Geometry(_) => Err(Error::ExpectedType {
128                expected: "Feature".to_string(),
129                actual: "Geometry".to_string(),
130            }),
131            GeoJson::Feature(f) => Ok(f),
132            GeoJson::FeatureCollection(_) => Err(Error::ExpectedType {
133                expected: "Feature".to_string(),
134                actual: "FeatureCollection".to_string(),
135            }),
136        }
137    }
138}
139
140impl TryFrom<GeoJson> for FeatureCollection {
141    type Error = Error;
142    fn try_from(value: GeoJson) -> Result<Self> {
143        match value {
144            GeoJson::Geometry(_) => Err(Error::ExpectedType {
145                expected: "FeatureCollection".to_string(),
146                actual: "Geometry".to_string(),
147            }),
148            GeoJson::Feature(_) => Err(Error::ExpectedType {
149                expected: "FeatureCollection".to_string(),
150                actual: "Feature".to_string(),
151            }),
152            GeoJson::FeatureCollection(f) => Ok(f),
153        }
154    }
155}
156
157impl GeoJson {
158    pub fn from_json_object(object: JsonObject) -> Result<Self> {
159        Self::try_from(object)
160    }
161
162    /// Converts a JSON Value into a GeoJson object.
163    ///
164    /// # Example
165    /// ```
166    /// use std::convert::TryInto;
167    /// use geojson::{Feature, GeoJson, Geometry, Value};
168    /// use serde_json::json;
169    ///
170    /// let json_value = json!({
171    ///     "type": "Feature",
172    ///     "geometry": {
173    ///         "type": "Point",
174    ///         "coordinates": [102.0, 0.5]
175    ///     },
176    ///     "properties": null,
177    /// });
178    ///
179    /// assert!(json_value.is_object());
180    ///
181    /// let geojson: GeoJson = json_value.try_into().unwrap();
182    ///
183    /// assert_eq!(
184    ///     geojson,
185    ///     GeoJson::Feature(Feature {
186    ///         bbox: None,
187    ///         geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))),
188    ///         id: None,
189    ///         properties: None,
190    ///         foreign_members: None,
191    ///     })
192    /// );
193    /// ```
194    pub fn from_json_value(value: JsonValue) -> Result<Self> {
195        Self::try_from(value)
196    }
197
198    /// Convenience method to convert to a JSON Value. Uses `From`.
199    /// ```
200    /// use std::convert::TryFrom;
201    /// use geojson::GeoJson;
202    /// use serde_json::json;
203    ///
204    /// let geojson = GeoJson::try_from( json!({
205    ///        "type": "Feature",
206    ///        "geometry": {
207    ///            "type": "Point",
208    ///            "coordinates": [102.0, 0.5]
209    ///        },
210    ///        "properties": {},
211    ///     })).unwrap();
212    ///
213    /// let json_value = geojson.to_json_value();
214    /// assert_eq!(json_value,
215    ///     json!({
216    ///        "type": "Feature",
217    ///        "geometry": {
218    ///            "type": "Point",
219    ///            "coordinates": [102.0, 0.5]
220    ///        },
221    ///        "properties": {},
222    ///     })
223    ///    );
224    /// ```
225    pub fn to_json_value(self) -> JsonValue {
226        JsonValue::from(self)
227    }
228
229    // Deserialize a GeoJson object from an IO stream of JSON
230    pub fn from_reader<R>(rdr: R) -> serde_json::Result<Self>
231    where
232        R: std::io::Read,
233    {
234        serde_json::from_reader(rdr)
235    }
236
237    /// Convenience wrapper for [serde_json::to_string_pretty()]
238    pub fn to_string_pretty(self) -> Result<String> {
239        ::serde_json::to_string_pretty(&self)
240            .map_err(Error::MalformedJson)
241            .map(|s| s.to_string())
242    }
243}
244
245impl TryFrom<JsonObject> for GeoJson {
246    type Error = Error;
247
248    fn try_from(object: JsonObject) -> Result<Self> {
249        let type_ = match object.get("type") {
250            Some(JsonValue::String(t)) => Type::from_str(t),
251            _ => return Err(Error::GeometryUnknownType("type".to_owned())),
252        };
253        let type_ = type_.ok_or(Error::EmptyType)?;
254        match type_ {
255            Type::Feature => Feature::try_from(object).map(GeoJson::Feature),
256            Type::FeatureCollection => {
257                FeatureCollection::try_from(object).map(GeoJson::FeatureCollection)
258            }
259            _ => Geometry::try_from(object).map(GeoJson::Geometry),
260        }
261    }
262}
263
264impl TryFrom<JsonValue> for GeoJson {
265    type Error = Error;
266
267    fn try_from(value: JsonValue) -> Result<Self> {
268        if let JsonValue::Object(obj) = value {
269            Self::try_from(obj)
270        } else {
271            Err(Error::GeoJsonExpectedObject(value))
272        }
273    }
274}
275
276#[derive(PartialEq, Clone, Copy)]
277enum Type {
278    Point,
279    MultiPoint,
280    LineString,
281    MultiLineString,
282    Polygon,
283    MultiPolygon,
284    GeometryCollection,
285    Feature,
286    FeatureCollection,
287}
288
289impl Type {
290    fn from_str(s: &str) -> Option<Self> {
291        match s {
292            "Point" => Some(Type::Point),
293            "MultiPoint" => Some(Type::MultiPoint),
294            "LineString" => Some(Type::LineString),
295            "MultiLineString" => Some(Type::MultiLineString),
296            "Polygon" => Some(Type::Polygon),
297            "MultiPolygon" => Some(Type::MultiPolygon),
298            "GeometryCollection" => Some(Type::GeometryCollection),
299            "Feature" => Some(Type::Feature),
300            "FeatureCollection" => Some(Type::FeatureCollection),
301            _ => None,
302        }
303    }
304}
305
306impl Serialize for GeoJson {
307    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
308    where
309        S: Serializer,
310    {
311        match self {
312            GeoJson::Geometry(ref geometry) => geometry.serialize(serializer),
313            GeoJson::Feature(ref feature) => feature.serialize(serializer),
314            GeoJson::FeatureCollection(ref fc) => fc.serialize(serializer),
315        }
316    }
317}
318
319impl<'de> Deserialize<'de> for GeoJson {
320    fn deserialize<D>(deserializer: D) -> std::result::Result<GeoJson, D::Error>
321    where
322        D: Deserializer<'de>,
323    {
324        use serde::de::Error as SerdeError;
325
326        let val = JsonObject::deserialize(deserializer)?;
327
328        GeoJson::from_json_object(val).map_err(|e| D::Error::custom(e.to_string()))
329    }
330}
331
332/// # Example
333///```
334/// use geojson::GeoJson;
335/// use std::str::FromStr;
336///
337/// let geojson_str = r#"{
338///   "type": "FeatureCollection",
339///   "features": [
340///     {
341///       "type": "Feature",
342///       "properties": {},
343///       "geometry": {
344///         "type": "Point",
345///         "coordinates": [
346///           -0.13583511114120483,
347///           51.5218870403801
348///         ]
349///       }
350///     }
351///   ]
352/// }
353/// "#;
354/// let geo_json = GeoJson::from_str(&geojson_str).unwrap();
355/// if let GeoJson::FeatureCollection(collection) = geo_json {
356///     assert_eq!(1, collection.features.len());
357/// } else {
358///     panic!("expected feature collection");
359/// }
360/// ```
361impl FromStr for GeoJson {
362    type Err = Error;
363
364    fn from_str(s: &str) -> Result<Self> {
365        let object = get_object(s)?;
366
367        GeoJson::from_json_object(object)
368    }
369}
370
371fn get_object(s: &str) -> Result<JsonObject> {
372    match ::serde_json::from_str(s)? {
373        JsonValue::Object(object) => Ok(object),
374        other => Err(Error::ExpectedObjectValue(other)),
375    }
376}
377
378impl fmt::Display for GeoJson {
379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380        ::serde_json::to_string(self)
381            .map_err(|_| fmt::Error)
382            .and_then(|s| f.write_str(&s))
383    }
384}
385
386impl fmt::Display for Feature {
387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388        ::serde_json::to_string(self)
389            .map_err(|_| fmt::Error)
390            .and_then(|s| f.write_str(&s))
391    }
392}
393
394impl fmt::Display for Geometry {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        ::serde_json::to_string(self)
397            .map_err(|_| fmt::Error)
398            .and_then(|s| f.write_str(&s))
399    }
400}
401
402impl fmt::Display for FeatureCollection {
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        ::serde_json::to_string(self)
405            .map_err(|_| fmt::Error)
406            .and_then(|s| f.write_str(&s))
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use crate::{Error, Feature, FeatureCollection, GeoJson, Geometry, Value};
413    use serde_json::json;
414    use std::convert::TryInto;
415    use std::str::FromStr;
416
417    #[test]
418    fn test_geojson_from_reader() {
419        let json_str = r#"{
420            "type": "Feature",
421            "geometry": {
422                    "type": "Point",
423                    "coordinates": [102.0, 0.5]
424            },
425            "properties": null
426        }"#;
427
428        let g1 = GeoJson::from_reader(json_str.as_bytes()).unwrap();
429
430        let json_value = json!({
431            "type": "Feature",
432            "geometry": {
433                "type": "Point",
434                "coordinates": [102.0, 0.5]
435            },
436            "properties": null,
437        });
438
439        let g2: GeoJson = json_value.try_into().unwrap();
440
441        assert_eq!(g1, g2);
442    }
443
444    #[test]
445    fn test_geojson_from_value() {
446        let json_value = json!({
447            "type": "Feature",
448            "geometry": {
449                "type": "Point",
450                "coordinates": [102.0, 0.5]
451            },
452            "properties": null,
453        });
454
455        assert!(json_value.is_object());
456
457        let geojson: GeoJson = json_value.try_into().unwrap();
458
459        assert_eq!(
460            geojson,
461            GeoJson::Feature(Feature {
462                bbox: None,
463                geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))),
464                id: None,
465                properties: None,
466                foreign_members: None,
467            })
468        );
469    }
470
471    #[test]
472    fn test_geojson_from_features() {
473        let features: Vec<Feature> = vec![
474            Value::Point(vec![0., 0., 0.]).into(),
475            Value::Point(vec![1., 1., 1.]).into(),
476        ];
477
478        let geojson: GeoJson = features.into();
479        assert_eq!(
480            geojson,
481            GeoJson::FeatureCollection(FeatureCollection {
482                features: vec![
483                    Feature {
484                        bbox: None,
485                        geometry: Some(Geometry::new(Value::Point(vec![0., 0., 0.]))),
486                        id: None,
487                        properties: None,
488                        foreign_members: None,
489                    },
490                    Feature {
491                        bbox: None,
492                        geometry: Some(Geometry::new(Value::Point(vec![1., 1., 1.]))),
493                        id: None,
494                        properties: None,
495                        foreign_members: None,
496                    },
497                ],
498                bbox: None,
499                foreign_members: None,
500            })
501        );
502    }
503
504    #[test]
505    fn test_missing_properties_key() {
506        let json_value = json!({
507            "type": "Feature",
508            "geometry": {
509                "type": "Point",
510                "coordinates": [102.0, 0.5]
511            },
512        });
513
514        assert!(json_value.is_object());
515
516        let geojson: GeoJson = json_value.try_into().unwrap();
517        assert_eq!(
518            geojson,
519            GeoJson::Feature(Feature {
520                bbox: None,
521                geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))),
522                id: None,
523                properties: None,
524                foreign_members: None,
525            })
526        );
527    }
528
529    #[test]
530    fn test_invalid_json() {
531        let geojson_str = r#"{
532           "type": "FeatureCollection",
533           "features": [
534             !INTENTIONAL_TYPO! {
535               "type": "Feature",
536               "properties": {},
537               "geometry": {
538                 "type": "Point",
539                 "coordinates": [
540                   -0.13583511114120483,
541                   51.5218870403801
542                 ]
543               }
544             }
545           ]
546        }"#;
547        assert!(matches!(
548            GeoJson::from_str(geojson_str),
549            Err(Error::MalformedJson(_))
550        ))
551    }
552}