Skip to main content

maps_engine_rust/
geojson.rs

1//! Minimal GeoJSON types. Port of `geojson.ts`.
2
3/// A GeoJSON position: [lon, lat] (+ optional altitude).
4#[derive(Debug, Clone, PartialEq)]
5pub struct Position(pub f64, pub f64);
6
7/// GeoJSON geometry.
8#[derive(Debug, Clone, PartialEq)]
9pub enum Geometry {
10    Point(Position),
11    LineString(Vec<Position>),
12    Polygon(Vec<Vec<Position>>),
13    MultiPoint(Vec<Position>),
14    MultiLineString(Vec<Vec<Position>>),
15    MultiPolygon(Vec<Vec<Vec<Position>>>),
16}
17
18impl Geometry {
19    /// Bounding box as (west, south, east, north).
20    pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
21        let mut pts: Vec<&Position> = Vec::new();
22        fn collect<'a>(g: &'a Geometry, out: &mut Vec<&'a Position>) {
23            match g {
24                Geometry::Point(p) => out.push(p),
25                Geometry::LineString(v) | Geometry::MultiPoint(v) => out.extend(v),
26                Geometry::Polygon(v) | Geometry::MultiLineString(v) => {
27                    for ring in v {
28                        out.extend(ring)
29                    }
30                }
31                Geometry::MultiPolygon(v) => {
32                    for poly in v {
33                        for ring in poly {
34                            out.extend(ring)
35                        }
36                    }
37                }
38            }
39        }
40        collect(self, &mut pts);
41        if pts.is_empty() {
42            return None;
43        }
44        let mut w = f64::INFINITY;
45        let mut s = f64::INFINITY;
46        let mut e = f64::NEG_INFINITY;
47        let mut n = f64::NEG_INFINITY;
48        for p in pts {
49            w = w.min(p.0);
50            e = e.max(p.0);
51            s = s.min(p.1);
52            n = n.max(p.1);
53        }
54        Some((w, s, e, n))
55    }
56}
57
58/// A GeoJSON feature with string properties.
59#[derive(Debug, Clone)]
60pub struct Feature {
61    pub geometry: Geometry,
62    pub properties: Vec<(String, String)>,
63}
64
65impl Feature {
66    pub fn new(geometry: Geometry) -> Self {
67        Feature {
68            geometry,
69            properties: Vec::new(),
70        }
71    }
72
73    pub fn prop(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
74        self.properties.push((k.into(), v.into()));
75        self
76    }
77
78    pub fn get(&self, k: &str) -> Option<&str> {
79        self.properties
80            .iter()
81            .find(|(key, _)| key == k)
82            .map(|(_, v)| v.as_str())
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn bbox_of_polygon() {
92        let g = Geometry::Polygon(vec![vec![
93            Position(0.0, 0.0),
94            Position(2.0, 0.0),
95            Position(2.0, 3.0),
96            Position(0.0, 3.0),
97        ]]);
98        assert_eq!(g.bbox(), Some((0.0, 0.0, 2.0, 3.0)));
99    }
100
101    #[test]
102    fn feature_properties() {
103        let f = Feature::new(Geometry::Point(Position(105.85, 21.02))).prop("name", "Hà Nội");
104        assert_eq!(f.get("name"), Some("Hà Nội"));
105        assert_eq!(f.get("missing"), None);
106    }
107}