From f187cf4bd85720668b4a6c5e7b77dffb0556c1a6 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 29 Oct 2020 12:00:34 -0700 Subject: [PATCH] Add an API call to export intersection geometry to geojson, for experimental streetmix3d integration. To get an example: > cargo run --bin headless -- --port=1234 > curl http://localhost:1234/map/get-intersection-geometry?id=291 --- Cargo.lock | 2 ++ book/src/dev/api.md | 4 +++ geom/src/polyline.rs | 11 +++++++++ headless/Cargo.toml | 2 ++ headless/src/main.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index dd678431ed..25938e72cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1278,6 +1278,7 @@ name = "headless" version = "0.1.0" dependencies = [ "abstutil", + "geojson", "geom", "hyper", "lazy_static", @@ -1286,6 +1287,7 @@ dependencies = [ "rand", "rand_xorshift", "serde", + "serde_json", "sim", "tokio", "url", diff --git a/book/src/dev/api.md b/book/src/dev/api.md index d0cd2c9102..f65aa29880 100644 --- a/book/src/dev/api.md +++ b/book/src/dev/api.md @@ -90,6 +90,10 @@ are missing, etc. A summary of the commands available so far: `--edits=name_of_edits`. - **GET /map/get-edit-road-command?id=123**: Returns an object that can be modified and then added to map edits. + - **GET /map/get-intersection-geometry?id=123**: Returns a GeoJSON object with + one feature for the intersection and a feature for all connecting roads. The + polygon coordinates are measured in meters, with the origin centered at the + intersection's center. ## Working with the map model diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 6efd924d87..b81fb1c56a 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -75,6 +75,17 @@ impl PolyLine { PolyLine::new(pts) } + /// Like make_polygons, but make sure the points actually form a ring. + pub fn to_thick_ring(&self, width: Distance) -> Ring { + let mut side1 = self.shift_with_sharp_angles(width / 2.0, MITER_THRESHOLD); + let mut side2 = self.shift_with_sharp_angles(-width / 2.0, MITER_THRESHOLD); + side2.reverse(); + side1.extend(side2); + side1.push(side1[0]); + side1.dedup(); + Ring::must_new(side1) + } + pub fn to_thick_boundary( &self, self_width: Distance, diff --git a/headless/Cargo.toml b/headless/Cargo.toml index 0446f159e6..37c23a6422 100644 --- a/headless/Cargo.toml +++ b/headless/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] abstutil = { path = "../abstutil" } +geojson = "0.19.0" geom = { path = "../geom" } hyper = "0.13.8" lazy_static = "1.4.0" @@ -14,6 +15,7 @@ map_model = { path = "../map_model" } rand = "0.7.0" rand_xorshift = "0.2.0" serde = "1.0.116" +serde_json = "1.0.57" sim = { path = "../sim" } tokio = { version = "0.2.22", features = ["full"] } url = "2.1.1" diff --git a/headless/src/main.rs b/headless/src/main.rs index 8be8c742f2..f8511e20ee 100644 --- a/headless/src/main.rs +++ b/headless/src/main.rs @@ -325,6 +325,10 @@ fn handle_command( &map.edit_road_cmd(r, |_| {}).to_perma(map), )) } + "/map/get-intersection-geometry" => { + let i = IntersectionID(params["id"].parse::()?); + Ok(abstutil::to_json(&export_geometry(map, i))) + } _ => Err("Unknown command".into()), } } @@ -415,3 +419,57 @@ impl LoadSim { (map, sim) } } + +fn export_geometry(map: &Map, i: IntersectionID) -> geojson::GeoJson { + use geojson::{Feature, FeatureCollection, GeoJson, Geometry, Value}; + + let i = map.get_i(i); + // Translate all geometry to center around the intersection, with distances in meters. + let center = i.polygon.center(); + + // The intersection itself + let mut props = serde_json::Map::new(); + props.insert("type".to_string(), "intersection".into()); + props.insert("id".to_string(), i.orig_id.to_string().into()); + let mut features = vec![Feature { + bbox: None, + geometry: Some(Geometry::new(Value::Polygon(vec![i + .polygon + .translate(-center.x(), -center.y()) + .points() + .iter() + .map(|pt| vec![pt.x(), pt.y()]) + .collect()]))), + id: None, + properties: Some(props), + foreign_members: None, + }]; + + // Each connected road + for r in &i.roads { + let r = map.get_r(*r); + let mut props = serde_json::Map::new(); + props.insert("type".to_string(), "road".into()); + props.insert("id".to_string(), r.orig_id.osm_way_id.to_string().into()); + features.push(Feature { + bbox: None, + geometry: Some(Geometry::new(Value::Polygon(vec![r + .center_pts + .to_thick_ring(2.0 * r.get_half_width(map)) + .into_points() + .into_iter() + .map(|pt| pt.offset(-center.x(), -center.y())) + .map(|pt| vec![pt.x(), pt.y()]) + .collect()]))), + id: None, + properties: Some(props), + foreign_members: None, + }); + } + + GeoJson::from(FeatureCollection { + bbox: None, + features, + foreign_members: None, + }) +}