1use crate::geometry::*;
6use rustial_math::GeoCoord;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
11pub enum GeoJsonError {
12 #[error("JSON parse error: {0}")]
14 Json(String),
15 #[error("invalid GeoJSON structure: {0}")]
17 Structure(String),
18}
19
20#[cfg(feature = "geojson")]
21impl From<serde_json::Error> for GeoJsonError {
22 fn from(e: serde_json::Error) -> Self {
23 GeoJsonError::Json(e.to_string())
24 }
25}
26
27#[cfg(feature = "geojson")]
29pub fn parse_geojson(input: &str) -> Result<FeatureCollection, GeoJsonError> {
30 let value: serde_json::Value = serde_json::from_str(input)?;
31 parse_value(&value)
32}
33
34#[cfg(feature = "geojson")]
35fn parse_value(value: &serde_json::Value) -> Result<FeatureCollection, GeoJsonError> {
36 let obj = value
37 .as_object()
38 .ok_or_else(|| GeoJsonError::Structure("expected object".into()))?;
39
40 let type_str = obj
41 .get("type")
42 .and_then(|v| v.as_str())
43 .ok_or_else(|| GeoJsonError::Structure("missing 'type'".into()))?;
44
45 match type_str {
46 "FeatureCollection" => {
47 let features_arr = obj
48 .get("features")
49 .and_then(|v| v.as_array())
50 .ok_or_else(|| GeoJsonError::Structure("missing 'features' array".into()))?;
51
52 let mut features = Vec::with_capacity(features_arr.len());
53 for f in features_arr {
54 features.push(parse_feature(f)?);
55 }
56 Ok(FeatureCollection { features })
57 }
58 "Feature" => {
59 let feature = parse_feature(value)?;
60 Ok(FeatureCollection {
61 features: vec![feature],
62 })
63 }
64 _ => {
65 let geom = parse_geometry(value)?;
67 Ok(FeatureCollection {
68 features: vec![Feature {
69 geometry: geom,
70 properties: std::collections::HashMap::new(),
71 }],
72 })
73 }
74 }
75}
76
77#[cfg(feature = "geojson")]
78fn parse_feature(value: &serde_json::Value) -> Result<Feature, GeoJsonError> {
79 let obj = value
80 .as_object()
81 .ok_or_else(|| GeoJsonError::Structure("feature is not an object".into()))?;
82
83 let geom_value = obj
84 .get("geometry")
85 .ok_or_else(|| GeoJsonError::Structure("missing 'geometry'".into()))?;
86
87 let geometry = parse_geometry(geom_value)?;
88
89 let mut properties = std::collections::HashMap::new();
90 if let Some(props) = obj.get("properties").and_then(|v| v.as_object()) {
91 for (key, val) in props {
92 let pv = match val {
93 serde_json::Value::Null => PropertyValue::Null,
94 serde_json::Value::Bool(b) => PropertyValue::Bool(*b),
95 serde_json::Value::Number(n) => PropertyValue::Number(n.as_f64().unwrap_or(0.0)),
96 serde_json::Value::String(s) => PropertyValue::String(s.clone()),
97 _ => PropertyValue::String(val.to_string()),
98 };
99 properties.insert(key.clone(), pv);
100 }
101 }
102
103 Ok(Feature {
104 geometry,
105 properties,
106 })
107}
108
109#[cfg(feature = "geojson")]
110fn parse_geometry(value: &serde_json::Value) -> Result<Geometry, GeoJsonError> {
111 let obj = value
112 .as_object()
113 .ok_or_else(|| GeoJsonError::Structure("geometry is not an object".into()))?;
114
115 let type_str = obj
116 .get("type")
117 .and_then(|v| v.as_str())
118 .ok_or_else(|| GeoJsonError::Structure("geometry missing 'type'".into()))?;
119
120 match type_str {
121 "Point" => {
122 let coords = get_coord_array(obj, "coordinates")?;
123 if coords.len() < 2 {
124 return Err(GeoJsonError::Structure("Point needs 2+ coords".into()));
125 }
126 Ok(Geometry::Point(Point {
127 coord: arr_to_geo(&coords),
128 }))
129 }
130 "LineString" => {
131 let coords = get_coord_arrays(obj, "coordinates")?;
132 let line = coords.iter().map(|c| arr_to_geo(c)).collect();
133 Ok(Geometry::LineString(LineString { coords: line }))
134 }
135 "Polygon" => {
136 let rings = get_rings(obj, "coordinates")?;
137 let exterior = rings
138 .first()
139 .ok_or_else(|| GeoJsonError::Structure("polygon needs at least one ring".into()))?
140 .iter()
141 .map(|c| arr_to_geo(c))
142 .collect();
143 let interiors = rings[1..]
144 .iter()
145 .map(|ring| ring.iter().map(|c| arr_to_geo(c)).collect())
146 .collect();
147 Ok(Geometry::Polygon(Polygon {
148 exterior,
149 interiors,
150 }))
151 }
152 "MultiPoint" => {
153 let coords = get_coord_arrays(obj, "coordinates")?;
154 let points = coords
155 .iter()
156 .map(|c| Point {
157 coord: arr_to_geo(c),
158 })
159 .collect();
160 Ok(Geometry::MultiPoint(MultiPoint { points }))
161 }
162 "MultiLineString" => {
163 let rings = get_rings(obj, "coordinates")?;
164 let lines = rings
165 .iter()
166 .map(|ring| LineString {
167 coords: ring.iter().map(|c| arr_to_geo(c)).collect(),
168 })
169 .collect();
170 Ok(Geometry::MultiLineString(MultiLineString { lines }))
171 }
172 "MultiPolygon" => {
173 let polys_raw = obj
174 .get("coordinates")
175 .and_then(|v| v.as_array())
176 .ok_or_else(|| GeoJsonError::Structure("missing 'coordinates'".into()))?;
177
178 let mut polygons = Vec::new();
179 for poly_val in polys_raw {
180 let rings: Vec<Vec<Vec<f64>>> = poly_val
181 .as_array()
182 .ok_or_else(|| GeoJsonError::Structure("invalid polygon ring".into()))?
183 .iter()
184 .map(|ring| {
185 ring.as_array()
186 .unwrap_or(&Vec::new())
187 .iter()
188 .map(|c| {
189 c.as_array()
190 .unwrap_or(&Vec::new())
191 .iter()
192 .filter_map(|v| v.as_f64())
193 .collect()
194 })
195 .collect()
196 })
197 .collect();
198
199 let exterior = rings
200 .first()
201 .map(|r| r.iter().map(|c| arr_to_geo(c)).collect())
202 .unwrap_or_default();
203 let interiors = rings[1..]
204 .iter()
205 .map(|ring| ring.iter().map(|c| arr_to_geo(c)).collect())
206 .collect();
207 polygons.push(Polygon {
208 exterior,
209 interiors,
210 });
211 }
212 Ok(Geometry::MultiPolygon(MultiPolygon { polygons }))
213 }
214 "GeometryCollection" => {
215 let geometries = obj
216 .get("geometries")
217 .and_then(|v| v.as_array())
218 .ok_or_else(|| GeoJsonError::Structure("missing 'geometries'".into()))?;
219 let mut geoms = Vec::new();
220 for g in geometries {
221 geoms.push(parse_geometry(g)?);
222 }
223 Ok(Geometry::GeometryCollection(geoms))
224 }
225 other => Err(GeoJsonError::Structure(format!(
226 "unknown geometry type: {other}"
227 ))),
228 }
229}
230
231#[cfg(feature = "geojson")]
232fn arr_to_geo(arr: &[f64]) -> GeoCoord {
233 let lon = if !arr.is_empty() { arr[0] } else { 0.0 };
234 let lat = if arr.len() > 1 { arr[1] } else { 0.0 };
235 let alt = if arr.len() > 2 { arr[2] } else { 0.0 };
236 GeoCoord::new(lat, lon, alt)
237}
238
239#[cfg(feature = "geojson")]
240fn get_coord_array(
241 obj: &serde_json::Map<String, serde_json::Value>,
242 key: &str,
243) -> Result<Vec<f64>, GeoJsonError> {
244 obj.get(key)
245 .and_then(|v| v.as_array())
246 .ok_or_else(|| GeoJsonError::Structure(format!("missing '{key}' array")))?
247 .iter()
248 .map(|v| {
249 v.as_f64()
250 .ok_or_else(|| GeoJsonError::Structure("coordinate is not a number".into()))
251 })
252 .collect()
253}
254
255#[cfg(feature = "geojson")]
256fn get_coord_arrays(
257 obj: &serde_json::Map<String, serde_json::Value>,
258 key: &str,
259) -> Result<Vec<Vec<f64>>, GeoJsonError> {
260 obj.get(key)
261 .and_then(|v| v.as_array())
262 .ok_or_else(|| GeoJsonError::Structure(format!("missing '{key}'")))?
263 .iter()
264 .map(|v| {
265 v.as_array()
266 .ok_or_else(|| GeoJsonError::Structure("expected array of coords".into()))?
267 .iter()
268 .map(|n| {
269 n.as_f64()
270 .ok_or_else(|| GeoJsonError::Structure("coord not a number".into()))
271 })
272 .collect()
273 })
274 .collect()
275}
276
277#[cfg(feature = "geojson")]
278fn get_rings(
279 obj: &serde_json::Map<String, serde_json::Value>,
280 key: &str,
281) -> Result<Vec<Vec<Vec<f64>>>, GeoJsonError> {
282 obj.get(key)
283 .and_then(|v| v.as_array())
284 .ok_or_else(|| GeoJsonError::Structure(format!("missing '{key}'")))?
285 .iter()
286 .map(|ring| {
287 ring.as_array()
288 .ok_or_else(|| GeoJsonError::Structure("ring is not an array".into()))?
289 .iter()
290 .map(|coord| {
291 coord
292 .as_array()
293 .ok_or_else(|| GeoJsonError::Structure("coord is not an array".into()))?
294 .iter()
295 .map(|n| {
296 n.as_f64().ok_or_else(|| {
297 GeoJsonError::Structure("coord component not a number".into())
298 })
299 })
300 .collect()
301 })
302 .collect()
303 })
304 .collect()
305}
306
307#[cfg(test)]
308#[cfg(feature = "geojson")]
309mod tests {
310 use super::*;
311
312 #[test]
313 fn parse_point() {
314 let json = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[17.0,51.1]},"properties":{"name":"Wroclaw"}}"#;
315 let fc = parse_geojson(json).unwrap();
316 assert_eq!(fc.len(), 1);
317 match &fc.features[0].geometry {
318 Geometry::Point(p) => {
319 assert!((p.coord.lat - 51.1).abs() < 1e-6);
320 assert!((p.coord.lon - 17.0).abs() < 1e-6);
321 }
322 _ => panic!("expected Point"),
323 }
324 }
325
326 #[test]
327 fn parse_feature_collection() {
328 let json = r#"{
329 "type": "FeatureCollection",
330 "features": [
331 {"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]},"properties":{}},
332 {"type":"Feature","geometry":{"type":"Point","coordinates":[1,1]},"properties":{}}
333 ]
334 }"#;
335 let fc = parse_geojson(json).unwrap();
336 assert_eq!(fc.len(), 2);
337 }
338
339 #[test]
340 fn parse_polygon() {
341 let json = r#"{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]},"properties":{}}"#;
342 let fc = parse_geojson(json).unwrap();
343 match &fc.features[0].geometry {
344 Geometry::Polygon(p) => {
345 assert_eq!(p.exterior.len(), 5);
346 assert!(p.interiors.is_empty());
347 }
348 _ => panic!("expected Polygon"),
349 }
350 }
351
352 #[test]
353 fn parse_linestring() {
354 let json = r#"{"type":"Feature","geometry":{"type":"LineString","coordinates":[[0,0],[1,1],[2,2]]},"properties":{}}"#;
355 let fc = parse_geojson(json).unwrap();
356 match &fc.features[0].geometry {
357 Geometry::LineString(ls) => {
358 assert_eq!(ls.coords.len(), 3);
359 }
360 _ => panic!("expected LineString"),
361 }
362 }
363}