maps-engine-rust 0.1.0

Zero-dependency map engine core in Rust — Web Mercator projection, tiles, geofencing, clustering, navigation math.
Documentation
//! Minimal GeoJSON types. Port of `geojson.ts`.

/// A GeoJSON position: [lon, lat] (+ optional altitude).
#[derive(Debug, Clone, PartialEq)]
pub struct Position(pub f64, pub f64);

/// GeoJSON geometry.
#[derive(Debug, Clone, PartialEq)]
pub enum Geometry {
    Point(Position),
    LineString(Vec<Position>),
    Polygon(Vec<Vec<Position>>),
    MultiPoint(Vec<Position>),
    MultiLineString(Vec<Vec<Position>>),
    MultiPolygon(Vec<Vec<Vec<Position>>>),
}

impl Geometry {
    /// Bounding box as (west, south, east, north).
    pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
        let mut pts: Vec<&Position> = Vec::new();
        fn collect<'a>(g: &'a Geometry, out: &mut Vec<&'a Position>) {
            match g {
                Geometry::Point(p) => out.push(p),
                Geometry::LineString(v) | Geometry::MultiPoint(v) => out.extend(v),
                Geometry::Polygon(v) | Geometry::MultiLineString(v) => {
                    for ring in v {
                        out.extend(ring)
                    }
                }
                Geometry::MultiPolygon(v) => {
                    for poly in v {
                        for ring in poly {
                            out.extend(ring)
                        }
                    }
                }
            }
        }
        collect(self, &mut pts);
        if pts.is_empty() {
            return None;
        }
        let mut w = f64::INFINITY;
        let mut s = f64::INFINITY;
        let mut e = f64::NEG_INFINITY;
        let mut n = f64::NEG_INFINITY;
        for p in pts {
            w = w.min(p.0);
            e = e.max(p.0);
            s = s.min(p.1);
            n = n.max(p.1);
        }
        Some((w, s, e, n))
    }
}

/// A GeoJSON feature with string properties.
#[derive(Debug, Clone)]
pub struct Feature {
    pub geometry: Geometry,
    pub properties: Vec<(String, String)>,
}

impl Feature {
    pub fn new(geometry: Geometry) -> Self {
        Feature {
            geometry,
            properties: Vec::new(),
        }
    }

    pub fn prop(mut self, k: impl Into<String>, v: impl Into<String>) -> Self {
        self.properties.push((k.into(), v.into()));
        self
    }

    pub fn get(&self, k: &str) -> Option<&str> {
        self.properties
            .iter()
            .find(|(key, _)| key == k)
            .map(|(_, v)| v.as_str())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn bbox_of_polygon() {
        let g = Geometry::Polygon(vec![vec![
            Position(0.0, 0.0),
            Position(2.0, 0.0),
            Position(2.0, 3.0),
            Position(0.0, 3.0),
        ]]);
        assert_eq!(g.bbox(), Some((0.0, 0.0, 2.0, 3.0)));
    }

    #[test]
    fn feature_properties() {
        let f = Feature::new(Geometry::Point(Position(105.85, 21.02))).prop("name", "Hà Nội");
        assert_eq!(f.get("name"), Some("Hà Nội"));
        assert_eq!(f.get("missing"), None);
    }
}