geojson/
feature_reader.rs

1use crate::de::deserialize_feature_collection;
2use crate::{Feature, Result};
3
4use serde::de::DeserializeOwned;
5
6use std::io::Read;
7
8/// Enumerates individual Features from a GeoJSON FeatureCollection
9pub struct FeatureReader<R> {
10    reader: R,
11}
12
13impl<R: Read> FeatureReader<R> {
14    /// Create a FeatureReader from the given `reader`.
15    pub fn from_reader(reader: R) -> Self {
16        Self { reader }
17    }
18
19    /// Iterate over the individual [`Feature`s](Feature) of a FeatureCollection.
20    ///
21    /// If instead you'd like to deserialize directly to your own struct, see [`FeatureReader::deserialize`].
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// let feature_collection_string = r#"{
27    ///      "type": "FeatureCollection",
28    ///      "features": [
29    ///          {
30    ///            "type": "Feature",
31    ///            "geometry": { "type": "Point", "coordinates": [125.6, 10.1] },
32    ///            "properties": {
33    ///              "name": "Dinagat Islands",
34    ///              "age": 123
35    ///            }
36    ///          },
37    ///          {
38    ///            "type": "Feature",
39    ///            "geometry": { "type": "Point", "coordinates": [2.3, 4.5] },
40    ///            "properties": {
41    ///              "name": "Neverland",
42    ///              "age": 456
43    ///            }
44    ///          }
45    ///      ]
46    /// }"#
47    /// .as_bytes();
48    /// let io_reader = std::io::BufReader::new(feature_collection_string);
49    ///
50    /// use geojson::FeatureReader;
51    /// let feature_reader = FeatureReader::from_reader(io_reader);
52    /// for feature in feature_reader.features() {
53    ///     let feature = feature.expect("valid geojson feature");
54    ///
55    ///     let name = feature.property("name").unwrap().as_str().unwrap();
56    ///     let age = feature.property("age").unwrap().as_u64().unwrap();
57    ///
58    ///     if name == "Dinagat Islands" {
59    ///         assert_eq!(123, age);
60    ///     } else if name == "Neverland" {
61    ///         assert_eq!(456, age);
62    ///     } else {
63    ///         panic!("unexpected name: {}", name);
64    ///     }
65    /// }
66    /// ```
67    pub fn features(self) -> impl Iterator<Item = Result<Feature>> {
68        #[allow(deprecated)]
69        crate::FeatureIterator::new(self.reader)
70    }
71
72    /// Deserialize the features of FeatureCollection into your own custom
73    /// struct using the [`serde`](../../serde) crate.
74    ///
75    /// # Examples
76    ///
77    /// Your struct must implement or derive [`serde::Deserialize`].
78    ///
79    /// If you have enabled the `geo-types` feature, which is enabled by default, you can
80    /// deserialize directly to a useful geometry type.
81    ///
82    /// ```rust,ignore
83    /// use geojson::{FeatureReader, de::deserialize_geometry};
84    ///
85    /// #[derive(serde::Deserialize)]
86    /// struct MyStruct {
87    ///     #[serde(deserialize_with = "deserialize_geometry")]
88    ///     geometry: geo_types::Point<f64>,
89    ///     name: String,
90    ///     age: u64,
91    /// }
92    /// ```
93    ///
94    /// Then you can deserialize the FeatureCollection directly to your type.
95    #[cfg_attr(feature = "geo-types", doc = "```")]
96    #[cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
97    /// let feature_collection_string = r#"{
98    ///     "type": "FeatureCollection",
99    ///     "features": [
100    ///         {
101    ///            "type": "Feature",
102    ///            "geometry": { "type": "Point", "coordinates": [125.6, 10.1] },
103    ///            "properties": {
104    ///              "name": "Dinagat Islands",
105    ///              "age": 123
106    ///            }
107    ///         },
108    ///         {
109    ///            "type": "Feature",
110    ///            "geometry": { "type": "Point", "coordinates": [2.3, 4.5] },
111    ///            "properties": {
112    ///              "name": "Neverland",
113    ///              "age": 456
114    ///            }
115    ///          }
116    ///    ]
117    /// }"#.as_bytes();
118    /// let io_reader = std::io::BufReader::new(feature_collection_string);
119    /// #
120    /// # use geojson::{FeatureReader, de::deserialize_geometry};
121    /// #
122    /// # #[derive(serde::Deserialize)]
123    /// # struct MyStruct {
124    /// #     #[serde(deserialize_with = "deserialize_geometry")]
125    /// #     geometry: geo_types::Point<f64>,
126    /// #     name: String,
127    /// #     age: u64,
128    /// # }
129    ///
130    /// let feature_reader = FeatureReader::from_reader(io_reader);
131    /// for feature in feature_reader.deserialize::<MyStruct>().unwrap() {
132    ///     let my_struct = feature.expect("valid geojson feature");
133    ///
134    ///     if my_struct.name == "Dinagat Islands" {
135    ///         assert_eq!(123, my_struct.age);
136    ///     } else if my_struct.name == "Neverland" {
137    ///         assert_eq!(456, my_struct.age);
138    ///     } else {
139    ///         panic!("unexpected name: {}", my_struct.name);
140    ///     }
141    /// }
142    /// ```
143    ///
144    /// If you're not using [`geo-types`](geo_types), you can deserialize to a `geojson::Geometry` instead.
145    /// ```rust,ignore
146    /// use serde::Deserialize;
147    /// #[derive(Deserialize)]
148    /// struct MyStruct {
149    ///     geometry: geojson::Geometry,
150    ///     name: String,
151    ///     age: u64,
152    /// }
153    /// ```
154    pub fn deserialize<D: DeserializeOwned>(self) -> Result<impl Iterator<Item = Result<D>>> {
155        deserialize_feature_collection(self.reader)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    use serde::Deserialize;
164    use serde_json::json;
165
166    #[derive(Deserialize)]
167    struct MyRecord {
168        geometry: crate::Geometry,
169        name: String,
170        age: u64,
171    }
172
173    fn feature_collection_string() -> String {
174        json!({
175            "type": "FeatureCollection",
176            "features": [
177                {
178                  "type": "Feature",
179                  "geometry": {
180                    "type": "Point",
181                    "coordinates": [125.6, 10.1]
182                  },
183                  "properties": {
184                    "name": "Dinagat Islands",
185                    "age": 123
186                  }
187                },
188                {
189                  "type": "Feature",
190                  "geometry": {
191                    "type": "Point",
192                    "coordinates": [2.3, 4.5]
193                  },
194                  "properties": {
195                    "name": "Neverland",
196                    "age": 456
197                  }
198                }
199            ]
200        })
201        .to_string()
202    }
203
204    #[test]
205    #[cfg(feature = "geo-types")]
206    fn deserialize_into_type() {
207        let feature_collection_string = feature_collection_string();
208        let mut bytes_reader = feature_collection_string.as_bytes();
209        let feature_reader = FeatureReader::from_reader(&mut bytes_reader);
210
211        let records: Vec<MyRecord> = feature_reader
212            .deserialize()
213            .expect("a valid feature collection")
214            .map(|result| result.expect("a valid feature"))
215            .collect();
216
217        assert_eq!(records.len(), 2);
218
219        assert_eq!(
220            records[0].geometry,
221            (&geo_types::point!(x: 125.6, y: 10.1)).into()
222        );
223        assert_eq!(records[0].name, "Dinagat Islands");
224        assert_eq!(records[0].age, 123);
225
226        assert_eq!(
227            records[1].geometry,
228            (&geo_types::point!(x: 2.3, y: 4.5)).into()
229        );
230        assert_eq!(records[1].name, "Neverland");
231        assert_eq!(records[1].age, 456);
232    }
233}