geojson/
feature_collection.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 serde::ser::SerializeMap;
16use std::convert::TryFrom;
17use std::iter::FromIterator;
18use std::str::FromStr;
19
20use crate::errors::{Error, Result};
21use crate::{util, Bbox, Feature};
22use crate::{JsonObject, JsonValue};
23use serde::{Deserialize, Deserializer, Serialize, Serializer};
24
25/// Feature Collection Objects
26///
27/// [GeoJSON Format Specification § 3.3](https://tools.ietf.org/html/rfc7946#section-3.3)
28///
29/// # Examples
30///
31/// Serialization:
32///
33/// ```
34/// use geojson::FeatureCollection;
35/// use geojson::GeoJson;
36///
37/// let feature_collection = FeatureCollection {
38///     bbox: None,
39///     features: vec![],
40///     foreign_members: None,
41/// };
42///
43/// let serialized = GeoJson::from(feature_collection).to_string();
44///
45/// assert_eq!(
46///     serialized,
47///     "{\"type\":\"FeatureCollection\",\"features\":[]}"
48/// );
49/// ```
50///
51/// Collect from an iterator:
52///
53/// ```rust
54/// use geojson::{Feature, FeatureCollection, Value};
55///
56/// let fc: FeatureCollection = (0..10)
57///     .map(|idx| -> Feature {
58///         let c = idx as f64;
59///         Value::Point(vec![1.0 * c, 2.0 * c, 3.0 * c]).into()
60///     })
61///     .collect();
62/// assert_eq!(fc.features.len(), 10);
63/// ```
64#[derive(Clone, Debug, Default, PartialEq)]
65pub struct FeatureCollection {
66    /// Bounding Box
67    ///
68    /// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5)
69    pub bbox: Option<Bbox>,
70    pub features: Vec<Feature>,
71    /// Foreign Members
72    ///
73    /// [GeoJSON Format Specification § 6](https://tools.ietf.org/html/rfc7946#section-6)
74    pub foreign_members: Option<JsonObject>,
75}
76
77impl IntoIterator for FeatureCollection {
78    type Item = Feature;
79    type IntoIter = std::vec::IntoIter<Feature>;
80
81    fn into_iter(self) -> Self::IntoIter {
82        self.features.into_iter()
83    }
84}
85
86impl<'a> IntoIterator for &'a FeatureCollection {
87    type Item = &'a Feature;
88    type IntoIter = std::slice::Iter<'a, Feature>;
89
90    fn into_iter(self) -> Self::IntoIter {
91        IntoIterator::into_iter(&self.features)
92    }
93}
94
95impl<'a> From<&'a FeatureCollection> for JsonObject {
96    fn from(fc: &'a FeatureCollection) -> JsonObject {
97        // The unwrap() should never panic, because FeatureCollection contains only JSON-serializable types
98        match serde_json::to_value(fc).unwrap() {
99            serde_json::Value::Object(obj) => obj,
100            value => {
101                // Panic should never happen, because `impl Serialize for FeatureCollection` always produces an
102                // Object
103                panic!("serializing FeatureCollection should result in an Object, but got something {:?}", value)
104            }
105        }
106    }
107}
108
109impl FeatureCollection {
110    pub fn from_json_object(object: JsonObject) -> Result<Self> {
111        Self::try_from(object)
112    }
113
114    pub fn from_json_value(value: JsonValue) -> Result<Self> {
115        Self::try_from(value)
116    }
117}
118
119impl TryFrom<JsonObject> for FeatureCollection {
120    type Error = Error;
121
122    fn try_from(mut object: JsonObject) -> Result<Self> {
123        match util::expect_type(&mut object)? {
124            ref type_ if type_ == "FeatureCollection" => Ok(FeatureCollection {
125                bbox: util::get_bbox(&mut object)?,
126                features: util::get_features(&mut object)?,
127                foreign_members: util::get_foreign_members(object)?,
128            }),
129            type_ => Err(Error::ExpectedType {
130                expected: "FeatureCollection".to_owned(),
131                actual: type_,
132            }),
133        }
134    }
135}
136
137impl TryFrom<JsonValue> for FeatureCollection {
138    type Error = Error;
139
140    fn try_from(value: JsonValue) -> Result<Self> {
141        if let JsonValue::Object(obj) = value {
142            Self::try_from(obj)
143        } else {
144            Err(Error::GeoJsonExpectedObject(value))
145        }
146    }
147}
148
149impl FromStr for FeatureCollection {
150    type Err = Error;
151
152    fn from_str(s: &str) -> Result<Self> {
153        Self::try_from(crate::GeoJson::from_str(s)?)
154    }
155}
156
157impl Serialize for FeatureCollection {
158    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
159    where
160        S: Serializer,
161    {
162        let mut map = serializer.serialize_map(None)?;
163        map.serialize_entry("type", "FeatureCollection")?;
164        map.serialize_entry("features", &self.features)?;
165
166        if let Some(ref bbox) = self.bbox {
167            map.serialize_entry("bbox", bbox)?;
168        }
169
170        if let Some(ref foreign_members) = self.foreign_members {
171            for (key, value) in foreign_members {
172                map.serialize_entry(key, value)?;
173            }
174        }
175
176        map.end()
177    }
178}
179
180impl<'de> Deserialize<'de> for FeatureCollection {
181    fn deserialize<D>(deserializer: D) -> std::result::Result<FeatureCollection, D::Error>
182    where
183        D: Deserializer<'de>,
184    {
185        use serde::de::Error as SerdeError;
186
187        let val = JsonObject::deserialize(deserializer)?;
188
189        FeatureCollection::from_json_object(val).map_err(|e| D::Error::custom(e.to_string()))
190    }
191}
192
193/// Create a [`FeatureCollection`] using the [`collect`]
194/// method on an iterator of `Feature`s. If every item
195/// contains a bounding-box of the same dimension, then the
196/// output has a bounding-box of the union of them.
197/// Otherwise, the output will not have a bounding-box.
198///
199/// [`collect`]: std::iter::Iterator::collect
200impl FromIterator<Feature> for FeatureCollection {
201    fn from_iter<T: IntoIterator<Item = Feature>>(iter: T) -> Self {
202        let mut bbox = Some(vec![]);
203
204        let features = iter
205            .into_iter()
206            .inspect(|feat| {
207                // Try to compute the bounding-box
208
209                let (curr_bbox, curr_len) = match &mut bbox {
210                    Some(curr_bbox) => {
211                        let curr_len = curr_bbox.len();
212                        (curr_bbox, curr_len)
213                    }
214                    None => {
215                        // implies we can't compute a
216                        // bounding-box for this collection
217                        return;
218                    }
219                };
220
221                match &feat.bbox {
222                    None => {
223                        bbox = None;
224                    }
225                    Some(fbox) if fbox.is_empty() || fbox.len() % 2 != 0 => {
226                        bbox = None;
227                    }
228                    Some(fbox) if curr_len == 0 => {
229                        // First iteration: just copy values from fbox
230                        curr_bbox.clone_from(fbox);
231                    }
232                    Some(fbox) if curr_len != fbox.len() => {
233                        bbox = None;
234                    }
235                    Some(fbox) => {
236                        // Update bbox by computing min/max
237                        curr_bbox.iter_mut().zip(fbox.iter()).enumerate().for_each(
238                            |(idx, (bc, fc))| {
239                                if idx < curr_len / 2 {
240                                    // These are the min coords
241                                    *bc = fc.min(*bc);
242                                } else {
243                                    *bc = fc.max(*bc);
244                                }
245                            },
246                        );
247                    }
248                };
249            })
250            .collect();
251        FeatureCollection {
252            bbox,
253            features,
254            foreign_members: None,
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use crate::{Error, Feature, FeatureCollection, Value};
262    use serde_json::json;
263
264    use std::str::FromStr;
265
266    #[test]
267    fn test_fc_from_iterator() {
268        let features: Vec<Feature> = vec![
269            {
270                let mut feat: Feature = Value::Point(vec![0., 0., 0.]).into();
271                feat.bbox = Some(vec![-1., -1., -1., 1., 1., 1.]);
272                feat
273            },
274            {
275                let mut feat: Feature =
276                    Value::MultiPoint(vec![vec![10., 10., 10.], vec![11., 11., 11.]]).into();
277                feat.bbox = Some(vec![10., 10., 10., 11., 11., 11.]);
278                feat
279            },
280        ];
281
282        let fc: FeatureCollection = features.into_iter().collect();
283        assert_eq!(fc.features.len(), 2);
284        assert_eq!(fc.bbox, Some(vec![-1., -1., -1., 11., 11., 11.]));
285    }
286
287    fn feature_collection_json() -> String {
288        json!({ "type": "FeatureCollection", "features": [
289        {
290            "type": "Feature",
291            "geometry": {
292                "type": "Point",
293                "coordinates": [11.1, 22.2]
294            },
295            "properties": {
296                "name": "Downtown"
297            }
298        },
299        {
300            "type": "Feature",
301            "geometry": {
302                "type": "Point",
303                "coordinates": [33.3, 44.4]
304            },
305            "properties": {
306                "name": "Uptown"
307            }
308        },
309        ]})
310        .to_string()
311    }
312
313    #[test]
314    fn test_from_str_ok() {
315        let feature_collection = FeatureCollection::from_str(&feature_collection_json()).unwrap();
316        assert_eq!(2, feature_collection.features.len());
317    }
318
319    #[test]
320    fn iter_features() {
321        let feature_collection = FeatureCollection::from_str(&feature_collection_json()).unwrap();
322
323        let mut names: Vec<String> = vec![];
324        for feature in &feature_collection {
325            let name = feature
326                .property("name")
327                .unwrap()
328                .as_str()
329                .unwrap()
330                .to_string();
331            names.push(name);
332        }
333
334        assert_eq!(names, vec!["Downtown", "Uptown"]);
335    }
336
337    #[test]
338    fn test_from_str_with_unexpected_type() {
339        let geometry_json = json!({
340            "type": "Point",
341            "coordinates": [125.6, 10.1]
342        })
343        .to_string();
344
345        let actual_failure = FeatureCollection::from_str(&geometry_json).unwrap_err();
346        match actual_failure {
347            Error::ExpectedType { actual, expected } => {
348                assert_eq!(actual, "Geometry");
349                assert_eq!(expected, "FeatureCollection");
350            }
351            e => panic!("unexpected error: {}", e),
352        };
353    }
354}