1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use serde::ser::SerializeMap;
5use serde::Serialize;
6use serde_json::Value;
7use std::collections::HashMap;
8use std::fmt;
9use std::io::Read;
10
11#[derive(Debug)]
15pub enum GeoErrorKind {
16 InvalidType(String),
18 MalformedCoordinates(String),
20 InvalidRing(String),
22 Io(std::io::Error),
24 Json(serde_json::Error),
26}
27
28impl fmt::Display for GeoErrorKind {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::InvalidType(s) => write!(f, "invalid type: {}", s),
32 Self::MalformedCoordinates(s) => write!(f, "malformed coordinates: {}", s),
33 Self::InvalidRing(s) => write!(f, "invalid ring: {}", s),
34 Self::Io(e) => write!(f, "I/O error: {}", e),
35 Self::Json(e) => write!(f, "JSON error: {}", e),
36 }
37 }
38}
39
40#[derive(Debug)]
45pub struct GeoError {
46 pub kind: GeoErrorKind,
48 pub path: String,
50}
51
52impl fmt::Display for GeoError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "geojson error at {}: {}", self.path, self.kind)
55 }
56}
57
58impl std::error::Error for GeoError {}
59
60#[derive(Debug, Clone, PartialEq, Serialize)]
68pub struct Position(Vec<f64>);
69
70impl Position {
71 pub fn new(coords: Vec<f64>) -> Result<Position, GeoError> {
75 if coords.len() < 2 || coords.len() > 3 {
76 return Err(GeoError {
77 kind: GeoErrorKind::MalformedCoordinates(format!(
78 "position must have 2 or 3 elements, got {}",
79 coords.len()
80 )),
81 path: String::new(),
82 });
83 }
84 Ok(Position(coords))
85 }
86
87 pub fn longitude(&self) -> f64 {
89 self.0[0]
90 }
91 pub fn latitude(&self) -> f64 {
93 self.0[1]
94 }
95 pub fn altitude(&self) -> Option<f64> {
97 self.0.get(2).copied()
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize)]
105#[serde(tag = "type")]
106pub enum Geometry {
107 Point {
109 coordinates: Position,
111 },
112 MultiPoint {
114 coordinates: Vec<Position>,
116 },
117 LineString {
119 coordinates: Vec<Position>,
121 },
122 MultiLineString {
124 coordinates: Vec<Vec<Position>>,
126 },
127 Polygon {
129 coordinates: Vec<Vec<Position>>,
131 },
132 MultiPolygon {
134 coordinates: Vec<Vec<Vec<Position>>>,
136 },
137 GeometryCollection {
139 geometries: Vec<Geometry>,
141 },
142}
143
144#[derive(Debug, Clone, PartialEq)]
148pub struct Feature {
149 pub geometry: Option<Geometry>,
151 pub properties: Option<Value>,
153 pub id: Option<Value>,
155 pub bbox: Option<Vec<f64>>,
157 pub foreign_members: HashMap<String, Value>,
159}
160
161impl Serialize for Feature {
162 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
163 let mut state = serializer.serialize_map(None)?;
164 state.serialize_entry("type", "Feature")?;
165 state.serialize_entry("geometry", &self.geometry)?;
166 state.serialize_entry("properties", &self.properties)?;
167 if let Some(id) = &self.id {
168 state.serialize_entry("id", id)?;
169 }
170 if let Some(bbox) = &self.bbox {
171 state.serialize_entry("bbox", bbox)?;
172 }
173 for (k, v) in &self.foreign_members {
174 state.serialize_entry(k, v)?;
175 }
176 state.end()
177 }
178}
179
180#[derive(Debug, Clone, PartialEq)]
182pub struct FeatureCollection {
183 pub features: Vec<Feature>,
185 pub bbox: Option<Vec<f64>>,
187 pub foreign_members: HashMap<String, Value>,
189}
190
191impl Serialize for FeatureCollection {
192 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
193 let mut state = serializer.serialize_map(None)?;
194 state.serialize_entry("type", "FeatureCollection")?;
195 state.serialize_entry("features", &self.features)?;
196 if let Some(bbox) = &self.bbox {
197 state.serialize_entry("bbox", bbox)?;
198 }
199 for (k, v) in &self.foreign_members {
200 state.serialize_entry(k, v)?;
201 }
202 state.end()
203 }
204}
205
206#[derive(Debug, Clone, PartialEq)]
208pub enum GeoJson {
209 Feature(Feature),
211 FeatureCollection(FeatureCollection),
213 Geometry(Geometry),
215}
216
217impl Serialize for GeoJson {
218 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
219 match self {
220 Self::Feature(f) => f.serialize(serializer),
221 Self::FeatureCollection(fc) => fc.serialize(serializer),
222 Self::Geometry(g) => g.serialize(serializer),
223 }
224 }
225}
226
227pub fn parse(input: &str) -> Result<GeoJson, GeoError> {
244 let raw: Value = serde_json::from_str(input).map_err(|e| GeoError {
245 kind: GeoErrorKind::Json(e),
246 path: String::new(),
247 })?;
248 parse_value(&raw, "")
249}
250
251pub fn parse_reader<R: Read>(mut reader: R) -> Result<GeoJson, GeoError> {
253 let raw: Value = serde_json::from_reader(&mut reader).map_err(|e| GeoError {
254 kind: GeoErrorKind::Json(e),
255 path: String::new(),
256 })?;
257 parse_value(&raw, "")
258}
259
260pub fn to_json(value: &GeoJson) -> Result<String, GeoError> {
275 serde_json::to_string(value).map_err(|e| GeoError {
276 kind: GeoErrorKind::Json(e),
277 path: String::new(),
278 })
279}
280
281fn collect_extra(v: &Value, known: &[&str]) -> (Option<Vec<f64>>, HashMap<String, Value>) {
287 let bbox = v
288 .get("bbox")
289 .and_then(Value::as_array)
290 .map(|arr| arr.iter().filter_map(Value::as_f64).collect::<Vec<f64>>());
291 let foreign = v
292 .as_object()
293 .map(|obj| {
294 obj.iter()
295 .filter(|(k, _)| !known.contains(&k.as_str()))
296 .map(|(k, val)| (k.clone(), val.clone()))
297 .collect::<HashMap<String, Value>>()
298 })
299 .unwrap_or_default();
300 (bbox, foreign)
301}
302
303fn parse_value(v: &Value, path: &str) -> Result<GeoJson, GeoError> {
304 let type_str = v
305 .get("type")
306 .and_then(Value::as_str)
307 .ok_or_else(|| GeoError {
308 kind: GeoErrorKind::InvalidType("missing or non-string 'type' field".into()),
309 path: format!("{}.type", path),
310 })?;
311
312 match type_str {
313 "FeatureCollection" => {
314 let features_val =
315 v.get("features")
316 .and_then(Value::as_array)
317 .ok_or_else(|| GeoError {
318 kind: GeoErrorKind::InvalidType(
319 "FeatureCollection missing 'features' array".into(),
320 ),
321 path: format!("{}.features", path),
322 })?;
323 let mut features = Vec::with_capacity(features_val.len());
324 for (i, fv) in features_val.iter().enumerate() {
325 let prefix = if path.is_empty() {
326 String::new()
327 } else {
328 format!("{}.", path)
329 };
330 let fp = format!("{}features[{}]", prefix, i);
331 features.push(parse_feature(fv, &fp)?);
332 }
333 let (bbox, foreign_members) = collect_extra(v, &["type", "features", "bbox"]);
334 Ok(GeoJson::FeatureCollection(FeatureCollection {
335 features,
336 bbox,
337 foreign_members,
338 }))
339 }
340 "Feature" => {
341 let fp = if path.is_empty() {
342 String::new()
343 } else {
344 path.to_owned()
345 };
346 Ok(GeoJson::Feature(parse_feature(v, &fp)?))
347 }
348 _ => {
349 let geom = parse_geometry(v, path)?;
350 Ok(GeoJson::Geometry(geom))
351 }
352 }
353}
354
355fn parse_feature(v: &Value, path: &str) -> Result<Feature, GeoError> {
356 if v.get("type").and_then(Value::as_str) != Some("Feature") {
357 return Err(GeoError {
358 kind: GeoErrorKind::InvalidType(format!("expected 'Feature', got {:?}", v.get("type"))),
359 path: format!("{}.type", path),
360 });
361 }
362
363 let geometry = match v.get("geometry") {
364 None | Some(Value::Null) => None,
365 Some(geom_val) => {
366 let gp = format!("{}.geometry", path);
367 Some(parse_geometry(geom_val, &gp)?)
368 }
369 };
370
371 let properties = v.get("properties").cloned();
372 let id = v.get("id").cloned();
373 let (bbox, foreign_members) =
374 collect_extra(v, &["type", "geometry", "properties", "id", "bbox"]);
375
376 Ok(Feature {
377 geometry,
378 properties,
379 id,
380 bbox,
381 foreign_members,
382 })
383}
384
385fn parse_geometry(v: &Value, path: &str) -> Result<Geometry, GeoError> {
386 let type_str = v
387 .get("type")
388 .and_then(Value::as_str)
389 .ok_or_else(|| GeoError {
390 kind: GeoErrorKind::InvalidType("geometry missing 'type' field".into()),
391 path: format!("{}.type", path),
392 })?;
393
394 match type_str {
395 "Point" => {
396 let coords_path = format!("{}.coordinates", path);
397 let raw = coords_raw(v, &coords_path)?;
398 let pos = parse_position(raw, &coords_path)?;
399 Ok(Geometry::Point { coordinates: pos })
400 }
401 "MultiPoint" => {
402 let coords_path = format!("{}.coordinates", path);
403 let arr = coords_raw(v, &coords_path)?;
404 let positions = parse_position_array(arr, &coords_path)?;
405 Ok(Geometry::MultiPoint {
406 coordinates: positions,
407 })
408 }
409 "LineString" => {
410 let coords_path = format!("{}.coordinates", path);
411 let arr = coords_raw(v, &coords_path)?;
412 let positions = parse_position_array(arr, &coords_path)?;
413 Ok(Geometry::LineString {
414 coordinates: positions,
415 })
416 }
417 "MultiLineString" => {
418 let coords_path = format!("{}.coordinates", path);
419 let arr = coords_raw(v, &coords_path)?
420 .as_array()
421 .ok_or_else(|| GeoError {
422 kind: GeoErrorKind::MalformedCoordinates(
423 "expected array of line strings".into(),
424 ),
425 path: coords_path.clone(),
426 })?;
427 let mut lines = Vec::with_capacity(arr.len());
428 for (i, line_val) in arr.iter().enumerate() {
429 let lp = format!("{}[{}]", coords_path, i);
430 let line_arr = line_val.as_array().ok_or_else(|| GeoError {
431 kind: GeoErrorKind::MalformedCoordinates("expected array".into()),
432 path: lp.clone(),
433 })?;
434 lines.push(parse_position_array(line_val, &lp)?);
435 let _ = line_arr;
436 }
437 Ok(Geometry::MultiLineString { coordinates: lines })
438 }
439 "Polygon" => {
440 let coords_path = format!("{}.coordinates", path);
441 let rings = parse_rings(v, &coords_path)?;
442 Ok(Geometry::Polygon { coordinates: rings })
443 }
444 "MultiPolygon" => {
445 let coords_path = format!("{}.coordinates", path);
446 let arr = coords_raw(v, &coords_path)?
447 .as_array()
448 .ok_or_else(|| GeoError {
449 kind: GeoErrorKind::MalformedCoordinates("expected array of polygons".into()),
450 path: coords_path.clone(),
451 })?;
452 let mut polys = Vec::with_capacity(arr.len());
453 for (i, poly_val) in arr.iter().enumerate() {
454 let pp = format!("{}[{}]", coords_path, i);
455 let rings = parse_rings_value(poly_val, &pp)?;
456 polys.push(rings);
457 }
458 Ok(Geometry::MultiPolygon { coordinates: polys })
459 }
460 "GeometryCollection" => {
461 let geoms_val = v
462 .get("geometries")
463 .and_then(Value::as_array)
464 .ok_or_else(|| GeoError {
465 kind: GeoErrorKind::InvalidType(
466 "GeometryCollection missing 'geometries' array".into(),
467 ),
468 path: format!("{}.geometries", path),
469 })?;
470 let mut geoms = Vec::with_capacity(geoms_val.len());
471 for (i, gv) in geoms_val.iter().enumerate() {
472 let gp = format!("{}.geometries[{}]", path, i);
473 geoms.push(parse_geometry(gv, &gp)?);
474 }
475 Ok(Geometry::GeometryCollection { geometries: geoms })
476 }
477 other => Err(GeoError {
478 kind: GeoErrorKind::InvalidType(format!("unknown geometry type '{}'", other)),
479 path: format!("{}.type", path),
480 }),
481 }
482}
483
484fn coords_raw<'a>(v: &'a Value, path: &str) -> Result<&'a Value, GeoError> {
485 v.get("coordinates").ok_or_else(|| GeoError {
486 kind: GeoErrorKind::MalformedCoordinates("missing 'coordinates' field".into()),
487 path: path.to_owned(),
488 })
489}
490
491fn parse_position(v: &Value, path: &str) -> Result<Position, GeoError> {
492 let arr = v.as_array().ok_or_else(|| GeoError {
493 kind: GeoErrorKind::MalformedCoordinates("position must be an array".into()),
494 path: path.to_owned(),
495 })?;
496 if arr.len() < 2 || arr.len() > 3 {
497 return Err(GeoError {
498 kind: GeoErrorKind::MalformedCoordinates(format!(
499 "position must have 2 or 3 elements, got {}",
500 arr.len()
501 )),
502 path: path.to_owned(),
503 });
504 }
505 let coords: Result<Vec<f64>, _> = arr
506 .iter()
507 .map(|n| {
508 n.as_f64().ok_or_else(|| GeoError {
509 kind: GeoErrorKind::MalformedCoordinates("coordinate must be a number".into()),
510 path: path.to_owned(),
511 })
512 })
513 .collect();
514 Position::new(coords?)
515}
516
517fn parse_position_array(v: &Value, path: &str) -> Result<Vec<Position>, GeoError> {
518 let arr = v.as_array().ok_or_else(|| GeoError {
519 kind: GeoErrorKind::MalformedCoordinates("expected array of positions".into()),
520 path: path.to_owned(),
521 })?;
522 let mut positions = Vec::with_capacity(arr.len());
523 for (i, pv) in arr.iter().enumerate() {
524 let pp = format!("{}[{}]", path, i);
525 positions.push(parse_position(pv, &pp)?);
526 }
527 Ok(positions)
528}
529
530fn parse_rings(v: &Value, path: &str) -> Result<Vec<Vec<Position>>, GeoError> {
531 let arr = coords_raw(v, path)?.as_array().ok_or_else(|| GeoError {
532 kind: GeoErrorKind::MalformedCoordinates(
533 "polygon coordinates must be an array of rings".into(),
534 ),
535 path: path.to_owned(),
536 })?;
537 parse_ring_array(arr, path)
538}
539
540fn parse_rings_value(v: &Value, path: &str) -> Result<Vec<Vec<Position>>, GeoError> {
541 let arr = v.as_array().ok_or_else(|| GeoError {
542 kind: GeoErrorKind::MalformedCoordinates("polygon must be an array of rings".into()),
543 path: path.to_owned(),
544 })?;
545 parse_ring_array(arr, path)
546}
547
548fn parse_ring_array(arr: &[Value], path: &str) -> Result<Vec<Vec<Position>>, GeoError> {
549 let mut rings = Vec::with_capacity(arr.len());
550 for (i, ring_val) in arr.iter().enumerate() {
551 let rp = format!("{}[{}]", path, i);
552 let positions = parse_position_array(ring_val, &rp)?;
553 if positions.len() < 4 {
555 return Err(GeoError {
556 kind: GeoErrorKind::InvalidRing(format!(
557 "ring must have at least 4 positions, got {}",
558 positions.len()
559 )),
560 path: rp,
561 });
562 }
563 let first = &positions[0];
564 let last = &positions[positions.len() - 1];
565 if (first.longitude() - last.longitude()).abs() > f64::EPSILON
566 || (first.latitude() - last.latitude()).abs() > f64::EPSILON
567 {
568 return Err(GeoError {
569 kind: GeoErrorKind::InvalidRing(
570 "polygon ring is not closed (first position != last position)".into(),
571 ),
572 path: rp,
573 });
574 }
575 rings.push(positions);
576 }
577 Ok(rings)
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn parse_point() {
586 let s = r#"{"type":"Point","coordinates":[125.6,10.1]}"#;
587 let geo = parse(s).unwrap();
588 assert!(matches!(geo, GeoJson::Geometry(Geometry::Point { .. })));
589 }
590
591 #[test]
592 fn parse_point_with_altitude() {
593 let s = r#"{"type":"Point","coordinates":[0.0,0.0,100.0]}"#;
594 let geo = parse(s).unwrap();
595 if let GeoJson::Geometry(Geometry::Point { coordinates: p }) = geo {
596 assert_eq!(p.altitude(), Some(100.0));
597 } else {
598 panic!("expected point");
599 }
600 }
601
602 #[test]
603 fn parse_feature() {
604 let s = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]},"properties":{"name":"test"}}"#;
605 let geo = parse(s).unwrap();
606 assert!(matches!(geo, GeoJson::Feature(_)));
607 }
608
609 #[test]
610 fn parse_feature_collection() {
611 let s = r#"{
612 "type":"FeatureCollection",
613 "features":[
614 {"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":null}
615 ]
616 }"#;
617 let geo = parse(s).unwrap();
618 if let GeoJson::FeatureCollection(fc) = geo {
619 assert_eq!(fc.features.len(), 1);
620 } else {
621 panic!();
622 }
623 }
624
625 #[test]
626 fn parse_valid_polygon() {
627 let s = r#"{"type":"Polygon","coordinates":[[[0,0],[1,0],[1,1],[0,1],[0,0]]]}"#;
628 let geo = parse(s).unwrap();
629 assert!(matches!(geo, GeoJson::Geometry(Geometry::Polygon { .. })));
630 }
631
632 #[test]
633 fn error_on_wrong_coord_length() {
634 let s = r#"{"type":"Point","coordinates":[1,2,3,4]}"#;
635 let err = parse(s).unwrap_err();
636 assert!(matches!(err.kind, GeoErrorKind::MalformedCoordinates(_)));
637 assert!(err.path.contains("coordinates"));
638 }
639
640 #[test]
641 fn error_on_unclosed_polygon() {
642 let s = r#"{"type":"Polygon","coordinates":[[[0,0],[1,0],[1,1],[0,1]]]}"#;
643 let err = parse(s).unwrap_err();
644 assert!(matches!(err.kind, GeoErrorKind::InvalidRing(_)));
645 }
646
647 #[test]
648 fn error_on_short_ring() {
649 let s = r#"{"type":"Polygon","coordinates":[[[0,0],[1,0],[0,0]]]}"#;
650 let err = parse(s).unwrap_err();
651 assert!(matches!(err.kind, GeoErrorKind::InvalidRing(_)));
652 }
653
654 #[test]
655 fn error_on_missing_type() {
656 let s = r#"{"coordinates":[0,0]}"#;
657 let err = parse(s).unwrap_err();
658 assert!(matches!(err.kind, GeoErrorKind::InvalidType(_)));
659 }
660
661 #[test]
662 fn error_has_nonempty_path_for_nested() {
663 let s = r#"{
664 "type":"FeatureCollection",
665 "features":[
666 {"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3,4]},"properties":null}
667 ]
668 }"#;
669 let err = parse(s).unwrap_err();
670 assert!(
671 !err.path.is_empty(),
672 "path should be non-empty: {:?}",
673 err.path
674 );
675 }
676
677 #[test]
678 fn parse_linestring() {
679 let s = r#"{"type":"LineString","coordinates":[[0,0],[1,1],[2,2]]}"#;
680 assert!(matches!(
681 parse(s).unwrap(),
682 GeoJson::Geometry(Geometry::LineString { .. })
683 ));
684 }
685
686 #[test]
687 fn parse_geometry_collection() {
688 let s =
689 r#"{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[0,0]}]}"#;
690 assert!(matches!(
691 parse(s).unwrap(),
692 GeoJson::Geometry(Geometry::GeometryCollection { .. })
693 ));
694 }
695
696 #[test]
697 fn parse_reader_api() {
698 let data = br#"{"type":"Point","coordinates":[1.0,2.0]}"#;
699 let geo = parse_reader(data.as_slice()).unwrap();
700 assert!(matches!(geo, GeoJson::Geometry(Geometry::Point { .. })));
701 }
702
703 #[test]
704 fn position_constructor_rejects_short_vector() {
705 assert!(Position::new(vec![1.0]).is_err());
706 let p = Position::new(vec![1.0, 2.0]).unwrap();
707 assert_eq!(p.longitude(), 1.0);
708 assert_eq!(p.latitude(), 2.0);
709 assert_eq!(p.altitude(), None);
710 }
711
712 #[test]
713 fn feature_serializes_type_member() {
714 let s = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]},"properties":null}"#;
715 let geo = parse(s).unwrap();
716 let json = to_json(&geo).unwrap();
717 assert!(json.contains(r#""type":"Feature""#));
718 }
719
720 #[test]
721 fn feature_collection_round_trips() {
722 let s = r#"{
723 "type":"FeatureCollection",
724 "features":[
725 {"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":{"n":1}},
726 {"type":"Feature","geometry":null,"properties":null}
727 ]
728 }"#;
729 let geo = parse(s).unwrap();
730 let json = to_json(&geo).unwrap();
731 let again = parse(&json).unwrap();
732 assert_eq!(geo, again);
733 }
734
735 #[test]
736 fn bbox_is_preserved() {
737 let s = r#"{"type":"FeatureCollection","bbox":[0,0,10,10],"features":[]}"#;
738 let geo = parse(s).unwrap();
739 if let GeoJson::FeatureCollection(fc) = &geo {
740 assert_eq!(fc.bbox, Some(vec![0.0, 0.0, 10.0, 10.0]));
741 } else {
742 panic!("expected FeatureCollection");
743 }
744 let json = to_json(&geo).unwrap();
745 assert!(json.contains(r#""bbox":[0.0,0.0,10.0,10.0]"#));
746 assert_eq!(parse(&json).unwrap(), geo);
747 }
748
749 #[test]
750 fn foreign_members_are_preserved() {
751 let s = r#"{"type":"Feature","properties":null,"title":"hello","extra":42}"#;
752 let geo = parse(s).unwrap();
753 if let GeoJson::Feature(f) = &geo {
754 assert_eq!(
755 f.foreign_members.get("title"),
756 Some(&serde_json::json!("hello"))
757 );
758 assert_eq!(f.foreign_members.get("extra"), Some(&serde_json::json!(42)));
759 } else {
760 panic!("expected Feature");
761 }
762 let json = to_json(&geo).unwrap();
763 assert!(json.contains(r#""title":"hello""#));
764 assert!(json.contains(r#""extra":42"#));
765 }
766}