Make the map editor be able to produce synthetic .osm, and use it to

create a map exemplifying the lane-changing problems of #382.
This commit is contained in:
Dustin Carlino 2020-11-11 10:44:45 -08:00
parent 2a546e528c
commit a3bcf5181c
7 changed files with 184 additions and 30 deletions

View File

@ -103,10 +103,10 @@ impl Bounds {
/// to the right and down (screen-drawing order, not Cartesian) in meters. /// to the right and down (screen-drawing order, not Cartesian) in meters.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GPSBounds { pub struct GPSBounds {
pub(crate) min_lon: f64, pub min_lon: f64,
pub(crate) min_lat: f64, pub min_lat: f64,
pub(crate) max_lon: f64, pub max_lon: f64,
pub(crate) max_lat: f64, pub max_lat: f64,
} }
impl GPSBounds { impl GPSBounds {

View File

@ -76,7 +76,7 @@ impl MainState {
Text::new().draw(ctx).named("current info"), Text::new().draw(ctx).named("current info"),
Widget::col(vec![ Widget::col(vec![
Btn::text_fg("quit").build_def(ctx, Key::Escape), 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), Btn::text_fg("preview all intersections").build_def(ctx, Key::G),
]), ]),
])) ]))
@ -185,9 +185,9 @@ impl widgetry::State<App> for MainState {
app.before_quit(ctx.canvas); app.before_quit(ctx.canvas);
std::process::exit(0); std::process::exit(0);
} }
"save raw map" => { "export to OSM" => {
// TODO Only do this for synthetic maps // TODO Only do this for synthetic maps
app.model.export(); app.model.export_to_osm();
} }
"preview all intersections" => { "preview all intersections" => {
if !app.model.intersection_geom { if !app.model.intersection_geom {

View File

@ -1,7 +1,10 @@
use std::collections::BTreeMap; use std::collections::{BTreeMap, HashMap};
use std::io::Write;
use abstutil::{MapName, Tags, Timer}; 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::raw::{OriginalRoad, RawBuilding, RawIntersection, RawMap, RawRoad};
use map_model::{osm, IntersectionType}; use map_model::{osm, IntersectionType};
use widgetry::{Color, EventCtx, Line, Text}; use widgetry::{Color, EventCtx, Line, Text};
@ -76,11 +79,7 @@ impl Model {
// General // General
impl Model { impl Model {
// TODO Only for truly synthetic maps... // TODO Only for truly synthetic maps...
pub fn export(&mut self) { pub fn export_to_osm(&mut self) {
if self.map.name.map == "" {
self.map.name.map = "new_synthetic_map".to_string();
}
// Shift the map to start at (0, 0) // Shift the map to start at (0, 0)
let bounds = self.compute_bounds(); let bounds = self.compute_bounds();
if bounds.min_x != 0.0 || bounds.min_y != 0.0 { if bounds.min_x != 0.0 || bounds.min_y != 0.0 {
@ -112,7 +111,7 @@ impl Model {
.gps_bounds .gps_bounds
.update(Pt2D::new(bounds.max_x, bounds.max_y).to_gps(&seattle_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 { fn compute_bounds(&self) -> Bounds {
@ -510,3 +509,54 @@ fn time_to_id() -> i64 {
// current time as seconds in wasm. // current time as seconds in wasm.
-5000 -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#"<?xml version='1.0' encoding='UTF-8'?>"#)?;
writeln!(f, r#"<osm>"#)?;
writeln!(
f,
r#"<!-- If you couldn't tell, this is a fake .osm file not representing the real world. -->"#
)?;
let b = &map.gps_bounds;
writeln!(
f,
r#" <bounds minlon="{}" maxlon="{}" minlat="{}" maxlat="{}"/>"#,
b.min_lon, b.max_lon, b.min_lat, b.max_lat
)?;
let mut pt_to_id: HashMap<HashablePt2D, osm::NodeID> = 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#" <node id="{}" lon="{}" lat="{}"/>"#,
id.0,
pt.x(),
pt.y()
)?;
}
for (id, r) in &map.roads {
writeln!(f, r#" <way id="{}">"#, id.osm_way_id.0)?;
for pt in &r.center_points {
// TODO Make new IDs if needed
writeln!(
f,
r#" <nd ref="{}"/>"#,
pt_to_id[&pt.to_hashable()].0
)?;
}
for (k, v) in r.osm_tags.inner() {
if !k.starts_with("abst:") {
writeln!(f, r#" <tag k="{}" v="{}"/>"#, k, v)?;
}
}
writeln!(f, r#" </way>"#)?;
}
writeln!(f, r#"</osm>"#)?;
Ok(())
}

View File

@ -0,0 +1,104 @@
<?xml version='1.0' encoding='UTF-8'?>
<osm>
<!-- If you couldn't tell, this is a fake .osm file not representing the real world. -->
<bounds minlon="-122.45322" maxlon="-122.44926543386632" minlat="47.72120717894802" maxlat="47.723277"/>
<node id="-1605119357" lon="-122.45151126345101" lat="47.72120717894802"/>
<node id="-1605119355" lon="-122.4514653240954" lat="47.721556505137436"/>
<node id="-1605119352" lon="-122.44924830402773" lat="47.72226323072633"/>
<node id="-1605119350" lon="-122.45011356808114" lat="47.722252367820644"/>
<node id="-1605119348" lon="-122.453224" lat="47.72224689454935"/>
<node id="-1605119346" lon="-122.45247066541864" lat="47.72225775745503"/>
<node id="-1605119341" lon="-122.45144121479004" lat="47.72225775745503">
<tag k="highway" v="traffic_signals"/>
</node>
<node id="-1605119336" lon="-122.45145843018425" lat="47.72284341277203"/>
<node id="-1605119328" lon="-122.45144699647818" lat="47.723277"/>
<way id="-1605119381">
<nd ref="-1605119350"/>
<nd ref="-1605119341"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="3"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="35 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119379">
<nd ref="-1605119352"/>
<nd ref="-1605119350"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="1"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119375">
<nd ref="-1605119341"/>
<nd ref="-1605119346"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="2"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119368">
<nd ref="-1605119346"/>
<nd ref="-1605119348"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="1"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119365">
<nd ref="-1605119336"/>
<nd ref="-1605119341"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="3"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119363">
<nd ref="-1605119328"/>
<nd ref="-1605119336"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="1"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119361">
<nd ref="-1605119341"/>
<nd ref="-1605119355"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="3"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
<way id="-1605119360">
<nd ref="-1605119355"/>
<nd ref="-1605119357"/>
<tag k="highway" v="residential"/>
<tag k="lanes" v="2"/>
<tag k="oneway" v="yes"/>
<tag k="maxspeed" v="25 mph"/>
<tag k="name" v="Streety McStreetFace"/>
<tag k="parking:lane:both" v="no_parking"/>
<tag k="sidewalk" v="both"/>
</way>
</osm>

View File

@ -1,12 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- Resembles https://www.openstreetmap.org/node/53086620: there's a left turn lane and a bike lane. --> <!-- Resembles https://www.openstreetmap.org/node/53086620: there's a left turn lane and a bike lane. -->
<osm> <osm>
<bounds minlon="0.0" maxlon="0.01" minlat="0.0" maxlat="0.01"/> <bounds minlon="0.0" maxlon="0.001" minlat="0.0" maxlat="0.001"/>
<node id="1" lon="0.005" lat="0.005"/> <node id="1" lon="0.0005" lat="0.0005"/>
<node id="2" lon="0.005" lat="-1.0"/> <node id="2" lon="0.0005" lat="-1.0"/>
<node id="3" lon="0.005" lat="1.0"/> <node id="3" lon="0.0005" lat="1.0"/>
<node id="4" lon="-0.1" lat="0.005"/> <node id="4" lon="-0.1" lat="0.0005"/>
<node id="5" lon="1.0" lat="0.005"/> <node id="5" lon="1.0" lat="0.0005"/>
<way id="100"> <way id="100">
<nd ref="1"/> <nd ref="1"/>
<nd ref="2"/> <nd ref="2"/>

View File

@ -1,12 +1,12 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<!-- Resembles https://www.openstreetmap.org/node/53187904 with multiple left turn lanes. --> <!-- Resembles https://www.openstreetmap.org/node/53187904 with multiple left turn lanes. -->
<osm> <osm>
<bounds minlon="0.0" maxlon="0.01" minlat="0.0" maxlat="0.01"/> <bounds minlon="0.0" maxlon="0.001" minlat="0.0" maxlat="0.001"/>
<node id="1" lon="0.005" lat="0.005"/> <node id="1" lon="0.0005" lat="0.0005"/>
<node id="2" lon="0.005" lat="-1.0"/> <node id="2" lon="0.0005" lat="-1.0"/>
<node id="3" lon="0.005" lat="1.0"/> <node id="3" lon="0.0005" lat="1.0"/>
<node id="4" lon="-0.1" lat="0.005"/> <node id="4" lon="-0.1" lat="0.0005"/>
<node id="5" lon="1.0" lat="0.005"/> <node id="5" lon="1.0" lat="0.0005"/>
<way id="100"> <way id="100">
<nd ref="1"/> <nd ref="1"/>
<nd ref="2"/> <nd ref="2"/>

View File

@ -19,9 +19,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
/// when they change. The goldenfiles (and changes to them) themselves aren't easy to understand, /// when they change. The goldenfiles (and changes to them) themselves aren't easy to understand,
/// but the test maps are. /// but the test maps are.
fn test_map_importer() -> Result<(), std::io::Error> { fn test_map_importer() -> Result<(), std::io::Error> {
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. // 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(abstutil::path(format!("../tests/input/{}.osm", name)));
let map = import_map(path);
// Enable to debug the result wih the normal GUI // Enable to debug the result wih the normal GUI
if false { if false {
map.save(); map.save();