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}