geojson/
feature.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 std::convert::TryFrom;
16use std::str::FromStr;
17
18use crate::errors::{Error, Result};
19use crate::{util, Feature, Geometry, Value};
20use crate::{JsonObject, JsonValue};
21use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
22
23impl From<Geometry> for Feature {
24    fn from(geom: Geometry) -> Feature {
25        Feature {
26            bbox: geom.bbox.clone(),
27            foreign_members: geom.foreign_members.clone(),
28            geometry: Some(geom),
29            id: None,
30            properties: None,
31        }
32    }
33}
34
35impl From<Value> for Feature {
36    fn from(val: Value) -> Feature {
37        Feature {
38            bbox: None,
39            foreign_members: None,
40            geometry: Some(Geometry::from(val)),
41            id: None,
42            properties: None,
43        }
44    }
45}
46
47impl FromStr for Feature {
48    type Err = Error;
49
50    fn from_str(s: &str) -> Result<Self> {
51        Self::try_from(crate::GeoJson::from_str(s)?)
52    }
53}
54
55impl<'a> From<&'a Feature> for JsonObject {
56    fn from(feature: &'a Feature) -> JsonObject {
57        // The unwrap() should never panic, because Feature contains only JSON-serializable types
58        match serde_json::to_value(feature).unwrap() {
59            serde_json::Value::Object(obj) => obj,
60            value => {
61                // Panic should never happen, because `impl Serialize for Feature` always produces an
62                // Object
63                panic!(
64                    "serializing Feature should result in an Object, but got something {:?}",
65                    value
66                )
67            }
68        }
69    }
70}
71
72impl Feature {
73    pub fn from_json_object(object: JsonObject) -> Result<Self> {
74        Self::try_from(object)
75    }
76
77    pub fn from_json_value(value: JsonValue) -> Result<Self> {
78        Self::try_from(value)
79    }
80
81    /// Return the value of this property, if it's set
82    pub fn property(&self, key: impl AsRef<str>) -> Option<&JsonValue> {
83        self.properties
84            .as_ref()
85            .and_then(|props| props.get(key.as_ref()))
86    }
87
88    /// Return true iff this key is set
89    pub fn contains_property(&self, key: impl AsRef<str>) -> bool {
90        match &self.properties {
91            None => false,
92            Some(props) => props.contains_key(key.as_ref()),
93        }
94    }
95
96    /// Set a property to this value, overwriting any possible older value
97    pub fn set_property(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
98        let key: String = key.into();
99        let value: JsonValue = value.into();
100        if self.properties.is_none() {
101            self.properties = Some(JsonObject::new());
102        }
103
104        self.properties.as_mut().unwrap().insert(key, value);
105    }
106
107    /// Removes a key from the `properties` map, returning the value at the key if the key
108    /// was previously in the `properties` map.
109    pub fn remove_property(&mut self, key: impl AsRef<str>) -> Option<JsonValue> {
110        self.properties
111            .as_mut()
112            .and_then(|props| props.remove(key.as_ref()))
113    }
114
115    /// The number of properties
116    pub fn len_properties(&self) -> usize {
117        match &self.properties {
118            None => 0,
119            Some(props) => props.len(),
120        }
121    }
122
123    /// Returns an iterator over all the properties
124    pub fn properties_iter(&self) -> Box<dyn ExactSizeIterator<Item = (&String, &JsonValue)> + '_> {
125        match self.properties.as_ref() {
126            None => Box::new(std::iter::empty()),
127            Some(props) => Box::new(props.iter()),
128        }
129    }
130}
131
132impl TryFrom<JsonObject> for Feature {
133    type Error = Error;
134
135    fn try_from(mut object: JsonObject) -> Result<Self> {
136        let res = &*util::expect_type(&mut object)?;
137        match res {
138            "Feature" => Ok(Feature {
139                geometry: util::get_geometry(&mut object)?,
140                properties: util::get_properties(&mut object)?,
141                id: util::get_id(&mut object)?,
142                bbox: util::get_bbox(&mut object)?,
143                foreign_members: util::get_foreign_members(object)?,
144            }),
145            _ => Err(Error::NotAFeature(res.to_string())),
146        }
147    }
148}
149
150impl TryFrom<JsonValue> for Feature {
151    type Error = Error;
152
153    fn try_from(value: JsonValue) -> Result<Self> {
154        if let JsonValue::Object(obj) = value {
155            Self::try_from(obj)
156        } else {
157            Err(Error::GeoJsonExpectedObject(value))
158        }
159    }
160}
161
162impl Serialize for Feature {
163    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
164    where
165        S: Serializer,
166    {
167        let mut map = serializer.serialize_map(None)?;
168        map.serialize_entry("type", "Feature")?;
169        map.serialize_entry("geometry", &self.geometry)?;
170        map.serialize_entry("properties", &self.properties)?;
171        if let Some(ref bbox) = self.bbox {
172            map.serialize_entry("bbox", bbox)?;
173        }
174        if let Some(ref id) = self.id {
175            map.serialize_entry("id", id)?;
176        }
177        if let Some(ref foreign_members) = self.foreign_members {
178            for (key, value) in foreign_members {
179                map.serialize_entry(key, value)?;
180            }
181        }
182        map.end()
183    }
184}
185
186impl<'de> Deserialize<'de> for Feature {
187    fn deserialize<D>(deserializer: D) -> std::result::Result<Feature, D::Error>
188    where
189        D: Deserializer<'de>,
190    {
191        use serde::de::Error as SerdeError;
192
193        let val = JsonObject::deserialize(deserializer)?;
194
195        Feature::from_json_object(val).map_err(|e| D::Error::custom(e.to_string()))
196    }
197}
198
199/// Feature identifier
200///
201/// [GeoJSON Format Specification ยง 3.2](https://tools.ietf.org/html/rfc7946#section-3.2)
202#[derive(Clone, Debug, PartialEq)]
203pub enum Id {
204    String(String),
205    Number(serde_json::Number),
206}
207
208impl Serialize for Id {
209    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
210    where
211        S: Serializer,
212    {
213        match self {
214            Id::String(ref s) => s.serialize(serializer),
215            Id::Number(ref n) => n.serialize(serializer),
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use crate::JsonObject;
223    use crate::{feature, Error, Feature, GeoJson, Geometry, Value};
224    use serde_json::json;
225
226    use std::str::FromStr;
227
228    fn feature_json_str() -> &'static str {
229        "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{}}"
230    }
231
232    fn properties() -> Option<JsonObject> {
233        Some(JsonObject::new())
234    }
235
236    fn feature() -> Feature {
237        crate::Feature {
238            geometry: Some(Geometry {
239                value: value(),
240                bbox: None,
241                foreign_members: None,
242            }),
243            properties: properties(),
244            bbox: None,
245            id: None,
246            foreign_members: None,
247        }
248    }
249
250    fn value() -> Value {
251        Value::Point(vec![1.1, 2.1])
252    }
253
254    fn geometry() -> Geometry {
255        Geometry::new(value())
256    }
257
258    fn encode(feature: &Feature) -> String {
259        serde_json::to_string(&feature).unwrap()
260    }
261
262    fn decode(json_string: String) -> GeoJson {
263        json_string.parse().unwrap()
264    }
265
266    #[test]
267    fn encode_decode_feature() {
268        let feature = feature();
269
270        // Test encoding
271        let json_string = encode(&feature);
272        assert_eq!(json_string, feature_json_str());
273
274        // Test decoding
275        let decoded_feature = match decode(json_string) {
276            GeoJson::Feature(f) => f,
277            _ => unreachable!(),
278        };
279        assert_eq!(decoded_feature, feature);
280    }
281
282    #[test]
283    fn try_from_value() {
284        use serde_json::json;
285        use std::convert::TryInto;
286
287        let json_value = json!({
288            "type": "Feature",
289            "geometry": {
290                "type": "Point",
291                "coordinates": [1.1, 2.1]
292            },
293            "properties": null,
294        });
295        assert!(json_value.is_object());
296
297        let feature: Feature = json_value.try_into().unwrap();
298        assert_eq!(
299            feature,
300            Feature {
301                bbox: None,
302                geometry: Some(geometry()),
303                id: None,
304                properties: None,
305                foreign_members: None,
306            }
307        )
308    }
309
310    #[test]
311    fn null_bbox() {
312        let geojson_str = r#"{
313            "geometry": null,
314            "bbox": null,
315            "properties":{},
316            "type":"Feature"
317        }"#;
318        let geojson = geojson_str.parse::<GeoJson>().unwrap();
319        let feature = match geojson {
320            GeoJson::Feature(feature) => feature,
321            _ => unimplemented!(),
322        };
323        assert!(feature.bbox.is_none());
324    }
325
326    #[test]
327    fn test_display_feature() {
328        let f = feature().to_string();
329        assert_eq!(f, "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{}}");
330    }
331
332    #[test]
333    fn feature_json_null_geometry() {
334        let geojson_str = r#"{
335            "geometry": null,
336            "properties":{},
337            "type":"Feature"
338        }"#;
339        let geojson = geojson_str.parse::<GeoJson>().unwrap();
340        let feature = match geojson {
341            GeoJson::Feature(feature) => feature,
342            _ => unimplemented!(),
343        };
344        assert!(feature.geometry.is_none());
345    }
346
347    #[test]
348    fn feature_json_invalid_geometry() {
349        let geojson_str = r#"{"geometry":3.14,"properties":{},"type":"Feature"}"#;
350        match geojson_str.parse::<GeoJson>().unwrap_err() {
351            Error::FeatureInvalidGeometryValue(_) => (),
352            _ => unreachable!(),
353        }
354    }
355
356    #[test]
357    fn encode_decode_feature_with_id_number() {
358        let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"id\":0}";
359        let feature = crate::Feature {
360            geometry: Some(Geometry {
361                value: Value::Point(vec![1.1, 2.1]),
362                bbox: None,
363                foreign_members: None,
364            }),
365            properties: properties(),
366            bbox: None,
367            id: Some(feature::Id::Number(0.into())),
368            foreign_members: None,
369        };
370        // Test encode
371        let json_string = encode(&feature);
372        assert_eq!(json_string, feature_json_str);
373
374        // Test decode
375        let decoded_feature = match decode(feature_json_str.into()) {
376            GeoJson::Feature(f) => f,
377            _ => unreachable!(),
378        };
379        assert_eq!(decoded_feature, feature);
380    }
381
382    #[test]
383    fn encode_decode_feature_with_id_string() {
384        let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"id\":\"foo\"}";
385        let feature = crate::Feature {
386            geometry: Some(Geometry {
387                value: Value::Point(vec![1.1, 2.1]),
388                bbox: None,
389                foreign_members: None,
390            }),
391            properties: properties(),
392            bbox: None,
393            id: Some(feature::Id::String("foo".into())),
394            foreign_members: None,
395        };
396        // Test encode
397        let json_string = encode(&feature);
398        assert_eq!(json_string, feature_json_str);
399
400        // Test decode
401        let decoded_feature = match decode(feature_json_str.into()) {
402            GeoJson::Feature(f) => f,
403            _ => unreachable!(),
404        };
405        assert_eq!(decoded_feature, feature);
406    }
407
408    #[test]
409    fn decode_feature_with_invalid_id_type_object() {
410        let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":{},\"properties\":{},\"type\":\"Feature\"}";
411        assert!(matches!(
412            feature_json_str.parse::<GeoJson>(),
413            Err(Error::FeatureInvalidIdentifierType(_))
414        ));
415    }
416
417    #[test]
418    fn decode_feature_with_invalid_id_type_null() {
419        let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":null,\"properties\":{},\"type\":\"Feature\"}";
420        assert!(matches!(
421            feature_json_str.parse::<GeoJson>(),
422            Err(Error::FeatureInvalidIdentifierType(_))
423        ));
424    }
425
426    #[test]
427    fn encode_decode_feature_with_foreign_member() {
428        use crate::JsonObject;
429        use serde_json;
430        let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"other_member\":\"some_value\"}";
431
432        let mut foreign_members = JsonObject::new();
433        foreign_members.insert(
434            String::from("other_member"),
435            serde_json::to_value("some_value").unwrap(),
436        );
437        let feature = crate::Feature {
438            geometry: Some(Geometry {
439                value: Value::Point(vec![1.1, 2.1]),
440                bbox: None,
441                foreign_members: None,
442            }),
443            properties: properties(),
444            bbox: None,
445            id: None,
446            foreign_members: Some(foreign_members),
447        };
448        // Test encode
449        let json_string = encode(&feature);
450        assert_eq!(json_string, feature_json_str);
451
452        // Test decode
453        let decoded_feature = match decode(feature_json_str.into()) {
454            GeoJson::Feature(f) => f,
455            _ => unreachable!(),
456        };
457        assert_eq!(decoded_feature, feature);
458    }
459
460    #[test]
461    fn encode_decode_feature_with_null_properties() {
462        let feature_json_str = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[1.1,2.1]},"properties":null}"#;
463
464        let feature = crate::Feature {
465            geometry: Some(Value::Point(vec![1.1, 2.1]).into()),
466            properties: None,
467            bbox: None,
468            id: None,
469            foreign_members: None,
470        };
471        // Test encode
472        let json_string = encode(&feature);
473        assert_eq!(json_string, feature_json_str);
474
475        // Test decode
476        let decoded_feature = match decode(feature_json_str.into()) {
477            GeoJson::Feature(f) => f,
478            _ => unreachable!(),
479        };
480        assert_eq!(decoded_feature, feature);
481    }
482
483    #[test]
484    fn feature_ergonomic_property_access() {
485        use serde_json::json;
486
487        let mut feature = feature();
488
489        assert_eq!(feature.len_properties(), 0);
490        assert_eq!(feature.property("foo"), None);
491        assert!(!feature.contains_property("foo"));
492        assert_eq!(feature.properties_iter().collect::<Vec<_>>(), vec![]);
493
494        feature.set_property("foo", 12);
495        assert_eq!(feature.property("foo"), Some(&json!(12)));
496        assert_eq!(feature.len_properties(), 1);
497        assert!(feature.contains_property("foo"));
498        assert_eq!(
499            feature.properties_iter().collect::<Vec<_>>(),
500            vec![(&"foo".to_string(), &json!(12))]
501        );
502
503        assert_eq!(Some(json!(12)), feature.remove_property("foo"));
504        assert_eq!(feature.property("foo"), None);
505        assert_eq!(feature.len_properties(), 0);
506        assert!(!feature.contains_property("foo"));
507        assert_eq!(feature.properties_iter().collect::<Vec<_>>(), vec![]);
508    }
509
510    #[test]
511    fn test_from_str_ok() {
512        let feature_json = json!({
513            "type": "Feature",
514            "geometry": {
515                "type": "Point",
516                "coordinates": [125.6, 10.1]
517            },
518            "properties": {
519                "name": "Dinagat Islands"
520            }
521        })
522        .to_string();
523
524        let feature = Feature::from_str(&feature_json).unwrap();
525        assert_eq!("Dinagat Islands", feature.property("name").unwrap());
526    }
527
528    #[test]
529    fn test_from_str_with_unexpected_type() {
530        let geometry_json = json!({
531            "type": "Point",
532            "coordinates": [125.6, 10.1]
533        })
534        .to_string();
535
536        let actual_failure = Feature::from_str(&geometry_json).unwrap_err();
537        match actual_failure {
538            Error::ExpectedType { actual, expected } => {
539                assert_eq!(actual, "Geometry");
540                assert_eq!(expected, "Feature");
541            }
542            e => panic!("unexpected error: {}", e),
543        };
544    }
545}