From bdb5e99e2b3db51518a9122fc17ea56b009cdb71 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Tue, 16 Jul 2024 11:19:20 +0100 Subject: [PATCH] Allow multipolygons as clipping boundaries when converting from OSM --- CHANGES.md | 2 ++ osm-to-route-snapper/src/lib.rs | 27 ++++++++++++++++++++------- user_guide.md | 5 +++-- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3f0ee28..3253cd1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ changes. ## Unreleased +- The OSM importer can now handle MultiPolygon boundaries for clipping + ## 0.4.1 - Removed `fetchWithProgress`, which is a general-purpose utility method that diff --git a/osm-to-route-snapper/src/lib.rs b/osm-to-route-snapper/src/lib.rs index db31da3..baf0693 100644 --- a/osm-to-route-snapper/src/lib.rs +++ b/osm-to-route-snapper/src/lib.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use anyhow::Result; use geo::{ - BooleanOps, Contains, Coord, HaversineLength, Intersects, LineString, MultiLineString, Polygon, + BooleanOps, Contains, Coord, HaversineLength, Intersects, LineString, MultiLineString, + MultiPolygon, }; use log::{debug, info}; use osm_reader::{Element, WayID}; @@ -10,7 +11,7 @@ use osm_reader::{Element, WayID}; use route_snapper_graph::{Edge, NodeID, RouteSnapperMap}; /// Convert input OSM PBF or XML data into a RouteSnapperMap, extracting all highway center-lines. -/// If a boundary polygon is specified, clips roads to this boundary. +/// If a boundary polygon or multipolygon is specified, clips roads to this boundary. pub fn convert_osm( input_bytes: Vec, boundary_gj: Option, @@ -27,7 +28,14 @@ pub fn convert_osm( let mut boundary = None; if let Some(gj_string) = boundary_gj { let gj: geojson::Feature = gj_string.parse()?; - let boundary_geo: Polygon = gj.try_into()?; + let boundary_geo: MultiPolygon = if matches!( + gj.geometry.as_ref().unwrap().value, + geojson::Value::Polygon(_) + ) { + MultiPolygon(vec![gj.try_into()?]) + } else { + gj.try_into()? + }; boundary = Some(boundary_geo); } @@ -83,7 +91,7 @@ fn scrape_elements( fn split_edges( nodes: HashMap, ways: HashMap, - boundary: Option<&Polygon>, + boundary: Option<&MultiPolygon>, ) -> RouteSnapperMap { let mut map = RouteSnapperMap { nodes: Vec::new(), @@ -118,7 +126,9 @@ fn split_edges( let mut add_road = true; if let Some(boundary) = boundary { // If this road doesn't intersect the boundary at all, skip it - if !boundary.contains(&geometry) && !boundary.exterior().intersects(&geometry) { + if !boundary.contains(&geometry) + && !boundary.iter().any(|p| p.exterior().intersects(&geometry)) + { add_road = false; } } @@ -161,11 +171,14 @@ fn split_edges( map } -fn clip(map: &mut RouteSnapperMap, boundary: Polygon) { +fn clip(map: &mut RouteSnapperMap, boundary: MultiPolygon) { // Fix edges crossing the boundary. Edges totally outside the boundary are skipped earlier // during split_edges. for edge in &mut map.edges { - if boundary.exterior().intersects(&edge.geometry) { + if boundary + .iter() + .any(|p| p.exterior().intersects(&edge.geometry)) + { let invert = false; let mut multi_line_string = boundary.clip(&MultiLineString::from(edge.geometry.clone()), invert); diff --git a/user_guide.md b/user_guide.md index 4f04500..7e00508 100644 --- a/user_guide.md +++ b/user_guide.md @@ -13,8 +13,9 @@ file from OpenStreetMap data. The easiest way to do this for smaller areas is [in your web browser](https://dabreegster.github.io/route_snapper/import.html). For larger areas, you need an `.osm.xml` or `.osm.pbf` file, and optionally a -GeoJSON file with one polygon representing the boundary of your area. You'll -need to [install Rust](https://www.rust-lang.org/tools/install) to run this: +GeoJSON file with one polygon or multipolygon representing the boundary of your +area. You'll need to [install Rust](https://www.rust-lang.org/tools/install) to +run this: ``` cd osm-to-route-snapper