osm_pbf2json 0.2.2

A parser/filter/processor for OSM protobuf bundles
Documentation
use super::geo::BoundaryGeometry;
use super::items::AdminBoundary;
use osm_boundaries_utils::build_boundary;
use osmpbfreader::objects::{OsmId, OsmObj};
use rstar::{RTreeObject, AABB};
use std::collections::BTreeMap;

impl RTreeObject for AdminBoundary {
    type Envelope = AABB<[f64; 2]>;

    fn envelope(&self) -> Self::Envelope {
        let (sw, ne) = self.geometry.sw_ne();
        AABB::from_corners(sw, ne)
    }
}

pub fn get_boundaries(objs: &BTreeMap<OsmId, OsmObj>) -> Vec<AdminBoundary> {
    objs.values()
        .filter_map(|obj| {
            let relation = obj.relation()?;
            let boundary = relation.tags.get("boundary")?;
            if boundary != "administrative" {
                return None;
            }
            let name = relation.tags.get("name")?.clone().into();
            let admin_level = relation.tags.get("admin_level")?.parse().ok()?;
            let multi_polygon = build_boundary(relation, objs)?;
            let geometry = BoundaryGeometry::new(multi_polygon).ok()?;
            let boundary = AdminBoundary {
                name,
                admin_level,
                geometry,
            };
            Some(boundary)
        })
        .collect()
}

#[cfg(test)]
mod get_boundaries {
    use super::super::test_helpers::create_objects;
    use super::*;
    use osmpbfreader::objects::{NodeId, OsmObj, RelationId, WayId};
    use rstar::RTree;

    fn bump_ids(objs: BTreeMap<OsmId, OsmObj>) -> BTreeMap<OsmId, OsmObj> {
        objs.into_iter()
            .map(|(key, value)| {
                let id = key.inner_id() + 1000;
                match value {
                    OsmObj::Node(mut node) => {
                        let node_id = NodeId(id);
                        node.id = node_id;
                        (OsmId::Node(node_id), OsmObj::Node(node))
                    }
                    OsmObj::Way(mut way) => {
                        let way_id = WayId(id);
                        way.id = way_id;
                        let node_ids = way
                            .nodes
                            .iter()
                            .map(|node_id| NodeId(node_id.0 + 1000))
                            .collect();
                        way.nodes = node_ids;
                        (OsmId::Way(way_id), OsmObj::Way(way))
                    }
                    OsmObj::Relation(mut relation) => {
                        let relation_id = RelationId(id);
                        for a_ref in relation.refs.iter_mut() {
                            let ref_id = a_ref.member.inner_id() + 1000;
                            a_ref.member = OsmId::Way(WayId(ref_id));
                        }
                        (OsmId::Relation(relation_id), OsmObj::Relation(relation))
                    }
                }
            })
            .collect()
    }

    fn build_coordinates(offset: f64) -> Vec<[f64; 2]> {
        vec![
            [offset, 52.],
            [offset + 1., 52.],
            [offset + 1., 53.],
            [offset, 53.],
        ]
    }

    #[test]
    fn geometry() {
        let tags = vec![
            ("boundary", "administrative"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let objects = create_objects(&tags, &coordinates);

        let boundary = get_boundaries(&objects).pop().unwrap();
        let coordinates = boundary.geometry.coordinates();
        assert_eq!(coordinates.len(), 1);
        assert_eq!(coordinates[0].len(), 1);
        assert_eq!(coordinates[0][0].len(), 5);
    }

    #[test]
    fn boundary_with_multiple_nodes() {
        let tags = vec![
            ("boundary", "administrative"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let objects = create_objects(&tags, &coordinates);

        let boundaries = get_boundaries(&objects);
        assert_eq!(boundaries.len(), 1);
    }

    #[test]
    fn relation_with_wrong_tags() {
        let tags = vec![
            ("boundary", "wrong"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let objects = create_objects(&tags, &coordinates);

        let boundaries = get_boundaries(&objects);
        assert_eq!(boundaries.len(), 0);
    }

    #[test]
    fn locate_line_string_contained_in_boundary() {
        let tags = vec![
            ("boundary", "administrative"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let objects = create_objects(&tags, &coordinates);

        let boundaries = get_boundaries(&objects);
        let tree = RTree::<AdminBoundary>::bulk_load(boundaries);
        let aabb = AABB::from_points(&vec![[13.25, 52.5], [13.74, 52.5]]);
        let matches = tree.locate_in_envelope_intersecting(&aabb);
        assert_eq!(matches.count(), 1);
    }

    #[test]
    fn locate_line_string_intersecting_boundary() {
        let tags = vec![
            ("boundary", "administrative"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let objects = create_objects(&tags, &coordinates);

        let boundaries = get_boundaries(&objects);
        let tree = RTree::<AdminBoundary>::bulk_load(boundaries);
        let aabb = AABB::from_points(&vec![[12.75, 52.5], [13.25, 52.5]]);
        let matches = tree.locate_in_envelope_intersecting(&aabb);
        assert_eq!(matches.count(), 1);
    }

    #[test]
    fn locate_line_string_out_of_boundary() {
        let tags = vec![
            ("boundary", "administrative"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let objects = create_objects(&tags, &coordinates);

        let boundaries = get_boundaries(&objects);
        let tree = RTree::<AdminBoundary>::bulk_load(boundaries);
        let aabb = AABB::from_points(&vec![[12.25, 52.5], [12.75, 52.5]]);
        let matches = tree.locate_in_envelope_intersecting(&aabb);
        assert_eq!(matches.count(), 0);
    }

    #[test]
    fn locate_line_string_intersecting_two_boundaries() {
        let tags = vec![
            ("boundary", "administrative"),
            ("name", "some_name"),
            ("admin_level", "11"),
        ];
        let coordinates = build_coordinates(13.);
        let mut objects_1 = create_objects(&tags, &coordinates);

        let coordinates = build_coordinates(12.);
        let objects_2 = bump_ids(create_objects(&tags, &coordinates));
        objects_1.extend(objects_2);
        let boundaries = get_boundaries(&objects_1);
        assert_eq!(boundaries.len(), 2);
        let tree = RTree::<AdminBoundary>::bulk_load(boundaries);
        let aabb = AABB::from_points(&vec![[13.25, 52.5], [13.75, 52.5]]);
        let matches = tree.locate_in_envelope_intersecting(&aabb);
        assert_eq!(matches.count(), 1);

        let aabb = AABB::from_points(&vec![[12.5, 52.5], [13.5, 52.5]]);
        let matches = tree.locate_in_envelope_intersecting(&aabb);
        assert_eq!(matches.count(), 2);
    }
}