mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-18 11:51:42 +03:00
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:
parent
2a546e528c
commit
a3bcf5181c
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
104
tests/input/lane_selection.osm
Normal file
104
tests/input/lane_selection.osm
Normal 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>
|
@ -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"/>
|
||||||
|
@ -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"/>
|
||||||
|
@ -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();
|
||||||
|
Loading…
Reference in New Issue
Block a user