Skip to main content

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 serde::{Deserialize, Serialize};
18use std::convert::TryFrom;
19use std::fmt;
20use std::iter::FromIterator;
21use std::str::FromStr;
22
23/// GeoJSON Objects
24///
25/// ```
26/// use geojson::{Feature, FeatureCollection, GeoJson, Geometry};
27/// use std::convert::TryInto;
28///
29/// // Parsing a Geometry
30/// let geometry_str = r#"{
31///    "type": "Point",
32///    "coordinates": [102.0, 0.5]
33/// }"#;
34/// let geometry: Geometry = geometry_str.parse().unwrap();
35///
36/// // Parsing a Feature
37/// let feature_str = r#"{
38///     "type": "Feature",
39///     "geometry": {
40///         "type": "Point",
41///         "coordinates": [102.0, 0.5]
42///     },
43///     "properties": null
44/// }"#;
45/// let feature: Feature = feature_str.parse().unwrap();
46///
47/// // Parsing a FeatureCollection
48/// let feature_collection_str = r#"{
49///     "type": "FeatureCollection",
50///     "features": [{
51///         "type": "Feature",
52///         "geometry": {
53///             "type": "Point",
54///             "coordinates": [102.0, 0.5]
55///         },
56///         "properties": null
57///     }]
58/// }"#;
59/// let feature_collection: FeatureCollection = feature_collection_str.parse().unwrap();
60///
61/// // When you're not sure what you're getting, use `GeoJson`.
62/// for geojson_str in [feature_str, geometry_str, feature_collection_str] {
63///     let geojson: GeoJson = geojson_str.parse().unwrap();
64///     match geojson {
65///         GeoJson::Geometry(geometry) => println!("geometry: {geometry:?}"),
66///         GeoJson::Feature(feature) => println!("feature: {feature:?}"),
67///         GeoJson::FeatureCollection(feature_collection) => {
68///             println!("feature_collection: {feature_collection:?}")
69///         }
70///     }
71/// }
72///
73/// // Easily convert a `Feature`, `FeatureCollection`, or `Geometry` into a `GeoJson`
74/// let geojson: GeoJson = feature.into();
75/// // Fallibly convert a GeoJson to its inner variant
76/// let feature: Feature = geojson.try_into().expect("expected a Feature");
77/// ```
78/// [GeoJSON Format Specification ยง 3](https://tools.ietf.org/html/rfc7946#section-3)
79#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
80#[serde(untagged, try_from = "deserialize::RawGeoJson")]
81pub enum GeoJson {
82    Geometry(Geometry),
83    Feature(Feature),
84    FeatureCollection(FeatureCollection),
85}
86
87impl<G: Into<Geometry>> From<G> for GeoJson {
88    fn from(geometry: G) -> Self {
89        GeoJson::Geometry(geometry.into())
90    }
91}
92
93impl<G: Into<Geometry>> FromIterator<G> for GeoJson {
94    fn from_iter<I: IntoIterator<Item = G>>(iter: I) -> Self {
95        let geometry_collection = Geometry::new_geometry_collection(iter);
96        GeoJson::Geometry(geometry_collection)
97    }
98}
99
100impl From<Feature> for GeoJson {
101    fn from(feature: Feature) -> Self {
102        GeoJson::Feature(feature)
103    }
104}
105
106impl From<FeatureCollection> for GeoJson {
107    fn from(feature_collection: FeatureCollection) -> GeoJson {
108        GeoJson::FeatureCollection(feature_collection)
109    }
110}
111
112impl From<Vec<Feature>> for GeoJson {
113    fn from(features: Vec<Feature>) -> GeoJson {
114        GeoJson::from(features.into_iter().collect::<FeatureCollection>())
115    }
116}
117
118impl TryFrom<GeoJson> for Geometry {
119    type Error = Error;
120    fn try_from(value: GeoJson) -> Result<Self> {
121        match value {
122            GeoJson::Geometry(g) => Ok(g),
123            GeoJson::Feature(_) => Err(Error::ExpectedType {
124                expected: "Geometry".to_string(),
125                actual: "Feature".to_string(),
126            }),
127            GeoJson::FeatureCollection(_) => Err(Error::ExpectedType {
128                expected: "Geometry".to_string(),
129                actual: "FeatureCollection".to_string(),
130            }),
131        }
132    }
133}
134
135impl TryFrom<GeoJson> for Feature {
136    type Error = Error;
137    fn try_from(value: GeoJson) -> Result<Self> {
138        match value {
139            GeoJson::Geometry(_) => Err(Error::ExpectedType {
140                expected: "Feature".to_string(),
141                actual: "Geometry".to_string(),
142            }),
143            GeoJson::Feature(f) => Ok(f),
144            GeoJson::FeatureCollection(_) => Err(Error::ExpectedType {
145                expected: "Feature".to_string(),
146                actual: "FeatureCollection".to_string(),
147            }),
148        }
149    }
150}
151
152impl TryFrom<GeoJson> for FeatureCollection {
153    type Error = Error;
154    fn try_from(value: GeoJson) -> Result<Self> {
155        match value {
156            GeoJson::Geometry(_) => Err(Error::ExpectedType {
157                expected: "FeatureCollection".to_string(),
158                actual: "Geometry".to_string(),
159            }),
160            GeoJson::Feature(_) => Err(Error::ExpectedType {
161                expected: "FeatureCollection".to_string(),
162                actual: "Feature".to_string(),
163            }),
164            GeoJson::FeatureCollection(f) => Ok(f),
165        }
166    }
167}
168
169impl GeoJson {
170    // Deserialize a GeoJson object from an IO stream of JSON
171    pub fn from_reader<R>(rdr: R) -> serde_json::Result<Self>
172    where
173        R: std::io::Read,
174    {
175        serde_json::from_reader(rdr)
176    }
177
178    /// Convenience wrapper for [serde_json::to_string_pretty()]
179    pub fn to_string_pretty(self) -> Result<String> {
180        ::serde_json::to_string_pretty(&self)
181            .map_err(Error::MalformedGeoJson)
182            .map(|s| s.to_string())
183    }
184}
185
186/// # Example
187///```
188/// use geojson::GeoJson;
189/// use std::str::FromStr;
190///
191/// let geojson_str = r#"{
192///   "type": "FeatureCollection",
193///   "features": [
194///     {
195///       "type": "Feature",
196///       "properties": {},
197///       "geometry": {
198///         "type": "Point",
199///         "coordinates": [
200///           -0.13583511114120483,
201///           51.5218870403801
202///         ]
203///       }
204///     }
205///   ]
206/// }
207/// "#;
208/// let geo_json = GeoJson::from_str(&geojson_str).unwrap();
209/// if let GeoJson::FeatureCollection(collection) = geo_json {
210///     assert_eq!(1, collection.features.len());
211/// } else {
212///     panic!("expected feature collection");
213/// }
214/// ```
215impl FromStr for GeoJson {
216    type Err = Error;
217
218    fn from_str(s: &str) -> Result<Self> {
219        Ok(serde_json::from_str(s)?)
220    }
221}
222
223impl fmt::Display for GeoJson {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        ::serde_json::to_string(self)
226            .map_err(|_| fmt::Error)
227            .and_then(|s| f.write_str(&s))
228    }
229}
230
231impl fmt::Display for Feature {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        ::serde_json::to_string(self)
234            .map_err(|_| fmt::Error)
235            .and_then(|s| f.write_str(&s))
236    }
237}
238
239impl fmt::Display for Geometry {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        ::serde_json::to_string(self)
242            .map_err(|_| fmt::Error)
243            .and_then(|s| f.write_str(&s))
244    }
245}
246
247impl fmt::Display for FeatureCollection {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        ::serde_json::to_string(self)
250            .map_err(|_| fmt::Error)
251            .and_then(|s| f.write_str(&s))
252    }
253}
254
255mod deserialize {
256    use crate::geometry::deserialize::{Coordinates, GeometryType, RawGeometry};
257    use crate::util::normalize_foreign_members;
258    use crate::{Bbox, Error, Feature, FeatureCollection, GeoJson, Geometry, JsonObject, feature};
259    use serde::Deserialize;
260    use std::convert::TryFrom;
261
262    #[derive(Debug, Clone, PartialEq, Deserialize)]
263    enum GeoJsonType {
264        Feature,
265        FeatureCollection,
266        Point,
267        LineString,
268        Polygon,
269        MultiPoint,
270        MultiLineString,
271        MultiPolygon,
272        GeometryCollection,
273    }
274
275    impl GeoJsonType {
276        /// Convert to GeometryType if this is a geometry variant
277        fn as_geometry_type(&self) -> Option<GeometryType> {
278            match self {
279                GeoJsonType::Point => Some(GeometryType::Point),
280                GeoJsonType::LineString => Some(GeometryType::LineString),
281                GeoJsonType::Polygon => Some(GeometryType::Polygon),
282                GeoJsonType::MultiPoint => Some(GeometryType::MultiPoint),
283                GeoJsonType::MultiLineString => Some(GeometryType::MultiLineString),
284                GeoJsonType::MultiPolygon => Some(GeometryType::MultiPolygon),
285                GeoJsonType::GeometryCollection => Some(GeometryType::GeometryCollection),
286                GeoJsonType::Feature | GeoJsonType::FeatureCollection => None,
287            }
288        }
289    }
290
291    /// Internal struct for deserializing any GeoJSON object before converting to GeoJson.
292    /// This captures all possible fields that can appear in any GeoJSON object type.
293    #[derive(Debug, Clone, Deserialize)]
294    #[serde(expecting = "GeoJson object")]
295    pub(crate) struct RawGeoJson {
296        r#type: GeoJsonType,
297
298        // Common field
299        bbox: Option<Bbox>,
300
301        // Geometry field (except GeometryCollection)
302        coordinates: Option<Coordinates>,
303
304        // GeometryCollection field
305        geometries: Option<Vec<Geometry>>,
306
307        // FeatureCollection field
308        features: Option<Vec<Feature>>,
309
310        // Feature fields
311        id: Option<feature::Id>,
312        geometry: Option<Geometry>,
313        properties: Option<JsonObject>,
314
315        // Foreign members (captures all other fields)
316        #[serde(flatten)]
317        foreign_members: Option<JsonObject>,
318    }
319
320    impl TryFrom<RawGeoJson> for GeoJson {
321        type Error = Error;
322
323        fn try_from(mut raw: RawGeoJson) -> crate::Result<Self> {
324            normalize_foreign_members(&mut raw.foreign_members);
325
326            match raw.r#type {
327                GeoJsonType::FeatureCollection => {
328                    let features = raw.features.ok_or_else(|| {
329                        use serde::de::Error as _;
330                        Error::MalformedGeoJson(serde_json::Error::missing_field("features"))
331                    })?;
332                    Ok(GeoJson::FeatureCollection(FeatureCollection {
333                        bbox: raw.bbox,
334                        features,
335                        foreign_members: raw.foreign_members,
336                    }))
337                }
338
339                GeoJsonType::Feature => Ok(GeoJson::Feature(Feature {
340                    bbox: raw.bbox,
341                    geometry: raw.geometry,
342                    id: raw.id,
343                    properties: raw.properties,
344                    foreign_members: raw.foreign_members,
345                })),
346
347                // Delegate all geometry types to RawGeometry
348                geojson_type => {
349                    let geometry_type = geojson_type.as_geometry_type().expect(
350                        "as_geometry_type returns Some for all variants except Feature/FeatureCollection",
351                    );
352                    let raw_geom = RawGeometry {
353                        r#type: geometry_type,
354                        coordinates: raw.coordinates,
355                        geometries: raw.geometries,
356                        bbox: raw.bbox,
357                        foreign_members: raw.foreign_members,
358                    };
359                    Ok(GeoJson::Geometry(Geometry::try_from(raw_geom)?))
360                }
361            }
362        }
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use crate::{Error, Feature, FeatureCollection, GeoJson, Geometry};
369    use serde_json::json;
370    use std::str::FromStr;
371
372    #[test]
373    fn test_geojson_from_reader() {
374        let json_str = r#"{
375            "type": "Feature",
376            "geometry": {
377                "type": "Point",
378                "coordinates": [102.0, 0.5]
379            },
380            "properties": null
381        }"#;
382
383        let actual = GeoJson::from_reader(json_str.as_bytes()).unwrap();
384        let actual_as_json = serde_json::to_value(actual).unwrap();
385        let expected = json!({
386            "type": "Feature",
387            "geometry": {
388                "type": "Point",
389                "coordinates": [102.0, 0.5]
390            },
391            "properties": null
392        });
393        assert_eq!(actual_as_json, expected);
394    }
395
396    #[test]
397    fn test_geojson_from_features() {
398        let features: Vec<Feature> = vec![
399            Geometry::new_point([0., 0., 0.]).into(),
400            Geometry::new_point([1., 1., 1.]).into(),
401        ];
402
403        let geojson: GeoJson = features.into();
404        assert_eq!(
405            geojson,
406            GeoJson::FeatureCollection(FeatureCollection {
407                features: vec![
408                    Feature {
409                        bbox: None,
410                        geometry: Some(Geometry::new_point([0., 0., 0.])),
411                        id: None,
412                        properties: None,
413                        foreign_members: None,
414                    },
415                    Feature {
416                        bbox: None,
417                        geometry: Some(Geometry::new_point([1., 1., 1.])),
418                        id: None,
419                        properties: None,
420                        foreign_members: None,
421                    },
422                ],
423                bbox: None,
424                foreign_members: None,
425            })
426        );
427    }
428
429    #[test]
430    fn test_missing_properties_key() {
431        let json_str = json!({
432            "type": "Feature",
433            "geometry": {
434                "type": "Point",
435                "coordinates": [102.0, 0.5]
436            },
437        })
438        .to_string();
439
440        let geojson = GeoJson::from_str(&json_str).unwrap();
441        assert_eq!(
442            geojson,
443            GeoJson::Feature(Feature {
444                bbox: None,
445                geometry: Some(Geometry::new_point([102.0, 0.5])),
446                id: None,
447                properties: None,
448                foreign_members: None,
449            })
450        );
451    }
452
453    #[test]
454    fn test_invalid_json() {
455        let geojson_str = r#"{
456           "type": "FeatureCollection",
457           "features": [
458             !INTENTIONAL_TYPO! {
459               "type": "Feature",
460               "properties": {},
461               "geometry": {
462                 "type": "Point",
463                 "coordinates": [
464                   -0.13583511114120483,
465                   51.5218870403801
466                 ]
467               }
468             }
469           ]
470        }"#;
471        assert!(matches!(
472            GeoJson::from_str(geojson_str),
473            Err(Error::MalformedGeoJson(_))
474        ))
475    }
476
477    #[test]
478    fn geojson_with_invalid_type() {
479        let geojson_str = json!({
480           "type": "TYPO-FeatureCollection",
481            "features": []
482        })
483        .to_string();
484
485        let err = GeoJson::from_str(&geojson_str).unwrap_err();
486        assert!(matches!(err, Error::MalformedGeoJson(_)));
487    }
488
489    #[test]
490    fn geojson_with_missing_type() {
491        let geojson_str = json!({
492            "features": []
493        })
494        .to_string();
495
496        let err = GeoJson::from_str(&geojson_str).unwrap_err();
497        assert!(matches!(err, Error::MalformedGeoJson(_)));
498    }
499
500    #[test]
501    fn not_an_object() {
502        let geojson_str = "[]";
503        let err = GeoJson::from_str(geojson_str).unwrap_err();
504        assert!(matches!(err, Error::MalformedGeoJson(_)));
505        assert!(format!("{err}").contains("expected GeoJson object"));
506    }
507}