From a3bcf5181c175e017a47410320cc8c2ee77d22d6 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 11 Nov 2020 10:44:45 -0800 Subject: [PATCH] Make the map editor be able to produce synthetic .osm, and use it to create a map exemplifying the lane-changing problems of #382. --- geom/src/bounds.rs | 8 +- map_editor/src/main.rs | 6 +- map_editor/src/model.rs | 66 ++++++++++++-- tests/input/lane_selection.osm | 104 +++++++++++++++++++++++ tests/input/left_turn_and_bike_lane.osm | 12 +-- tests/input/multiple_left_turn_lanes.osm | 12 +-- tests/src/main.rs | 6 +- 7 files changed, 184 insertions(+), 30 deletions(-) create mode 100644 tests/input/lane_selection.osm diff --git a/geom/src/bounds.rs b/geom/src/bounds.rs index 74a5abc501..837a0735c4 100644 --- a/geom/src/bounds.rs +++ b/geom/src/bounds.rs @@ -103,10 +103,10 @@ impl Bounds { /// to the right and down (screen-drawing order, not Cartesian) in meters. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GPSBounds { - pub(crate) min_lon: f64, - pub(crate) min_lat: f64, - pub(crate) max_lon: f64, - pub(crate) max_lat: f64, + pub min_lon: f64, + pub min_lat: f64, + pub max_lon: f64, + pub max_lat: f64, } impl GPSBounds { diff --git a/map_editor/src/main.rs b/map_editor/src/main.rs index e1cd1b30c3..ca7769aef4 100644 --- a/map_editor/src/main.rs +++ b/map_editor/src/main.rs @@ -76,7 +76,7 @@ impl MainState { Text::new().draw(ctx).named("current info"), Widget::col(vec![ Btn::text_fg("quit").build_def(ctx, Key::Escape), - Btn::text_fg("save raw map").build_def(ctx, None), + Btn::text_fg("export to OSM").build_def(ctx, None), Btn::text_fg("preview all intersections").build_def(ctx, Key::G), ]), ])) @@ -185,9 +185,9 @@ impl widgetry::State for MainState { app.before_quit(ctx.canvas); std::process::exit(0); } - "save raw map" => { + "export to OSM" => { // TODO Only do this for synthetic maps - app.model.export(); + app.model.export_to_osm(); } "preview all intersections" => { if !app.model.intersection_geom { diff --git a/map_editor/src/model.rs b/map_editor/src/model.rs index b918a90046..d06d9ca4ff 100644 --- a/map_editor/src/model.rs +++ b/map_editor/src/model.rs @@ -1,7 +1,10 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; +use std::io::Write; use abstutil::{MapName, Tags, Timer}; -use geom::{Bounds, Circle, Distance, FindClosest, GPSBounds, LonLat, PolyLine, Polygon, Pt2D}; +use geom::{ + Bounds, Circle, Distance, FindClosest, GPSBounds, HashablePt2D, LonLat, PolyLine, Polygon, Pt2D, +}; use map_model::raw::{OriginalRoad, RawBuilding, RawIntersection, RawMap, RawRoad}; use map_model::{osm, IntersectionType}; use widgetry::{Color, EventCtx, Line, Text}; @@ -76,11 +79,7 @@ impl Model { // General impl Model { // TODO Only for truly synthetic maps... - pub fn export(&mut self) { - if self.map.name.map == "" { - self.map.name.map = "new_synthetic_map".to_string(); - } - + pub fn export_to_osm(&mut self) { // Shift the map to start at (0, 0) let bounds = self.compute_bounds(); if bounds.min_x != 0.0 || bounds.min_y != 0.0 { @@ -112,7 +111,7 @@ impl Model { .gps_bounds .update(Pt2D::new(bounds.max_x, bounds.max_y).to_gps(&seattle_bounds)); - self.map.save(); + dump_to_osm(&self.map).unwrap(); } fn compute_bounds(&self) -> Bounds { @@ -510,3 +509,54 @@ fn time_to_id() -> i64 { // current time as seconds in wasm. -5000 } + +/// Express a RawMap as a .osm file. Why not just save the RawMap? The format may change over time, +/// and even if a RawMap is saved as JSON, manually updating it is annoying. This is used to create +/// synthetic maps that will never go bad -- there will always be a pipeline to import a .osm file, +/// so actually, .osm is a stable-over-time format. +fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> { + let mut f = std::fs::File::create("synthetic_export.osm")?; + writeln!(f, r#""#)?; + writeln!(f, r#""#)?; + writeln!( + f, + r#""# + )?; + let b = &map.gps_bounds; + writeln!( + f, + r#" "#, + b.min_lon, b.max_lon, b.min_lat, b.max_lat + )?; + let mut pt_to_id: HashMap = HashMap::new(); + for (id, i) in &map.intersections { + pt_to_id.insert(i.point.to_hashable(), *id); + let pt = i.point.to_gps(b); + writeln!( + f, + r#" "#, + id.0, + pt.x(), + pt.y() + )?; + } + for (id, r) in &map.roads { + writeln!(f, r#" "#, id.osm_way_id.0)?; + for pt in &r.center_points { + // TODO Make new IDs if needed + writeln!( + f, + r#" "#, + pt_to_id[&pt.to_hashable()].0 + )?; + } + for (k, v) in r.osm_tags.inner() { + if !k.starts_with("abst:") { + writeln!(f, r#" "#, k, v)?; + } + } + writeln!(f, r#" "#)?; + } + writeln!(f, r#""#)?; + Ok(()) +} diff --git a/tests/input/lane_selection.osm b/tests/input/lane_selection.osm new file mode 100644 index 0000000000..83761f158a --- /dev/null +++ b/tests/input/lane_selection.osm @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/input/left_turn_and_bike_lane.osm b/tests/input/left_turn_and_bike_lane.osm index 1a9602165c..16e3db4b4d 100644 --- a/tests/input/left_turn_and_bike_lane.osm +++ b/tests/input/left_turn_and_bike_lane.osm @@ -1,12 +1,12 @@ - - - - - - + + + + + + diff --git a/tests/input/multiple_left_turn_lanes.osm b/tests/input/multiple_left_turn_lanes.osm index 3c75daba6a..43e05e9182 100644 --- a/tests/input/multiple_left_turn_lanes.osm +++ b/tests/input/multiple_left_turn_lanes.osm @@ -1,12 +1,12 @@ - - - - - - + + + + + + diff --git a/tests/src/main.rs b/tests/src/main.rs index 89ec39f0a5..0795894b1a 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -19,9 +19,9 @@ fn main() -> Result<(), Box> { /// when they change. The goldenfiles (and changes to them) themselves aren't easy to understand, /// but the test maps are. fn test_map_importer() -> Result<(), std::io::Error> { - // TODO It's kind of a hack to reference the crate's directory relative to the data dir. - for path in abstutil::list_dir(abstutil::path("../tests/input")) { - let map = import_map(path); + for name in vec!["left_turn_and_bike_lane", "multiple_left_turn_lanes"] { + // TODO It's kind of a hack to reference the crate's directory relative to the data dir. + let map = import_map(abstutil::path(format!("../tests/input/{}.osm", name))); // Enable to debug the result wih the normal GUI if false { map.save();