From 3422877d3de9fb2087f7184cec38fe6ee9e8a61d Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Thu, 17 Jan 2019 08:46:31 -0800 Subject: [PATCH] adding a stable ID for raw roads and intersections. way easier to refer to things in the midst of deletions. --- convert_osm/src/lib.rs | 18 ++++---- convert_osm/src/osm.rs | 24 ++++++---- convert_osm/src/remove_disconnected.rs | 56 ++++++++++++----------- convert_osm/src/split_ways.rs | 43 ++++++++++------- editor/src/plugins/debug/debug_objects.rs | 9 +++- ezgui/src/runner.rs | 6 +-- map_model/src/intersection.rs | 3 +- map_model/src/make/half_map.rs | 6 ++- map_model/src/make/merge_intersections.rs | 1 + map_model/src/raw_data.rs | 19 +++++--- map_model/src/road.rs | 3 +- synthetic/src/model.rs | 44 ++++++++++-------- tests/src/map_conversion.rs | 1 + 13 files changed, 138 insertions(+), 95 deletions(-) diff --git a/convert_osm/src/lib.rs b/convert_osm/src/lib.rs index d7ad2b1aa1..65ee2cc792 100644 --- a/convert_osm/src/lib.rs +++ b/convert_osm/src/lib.rs @@ -58,8 +58,7 @@ pub struct Flags { pub fn convert(flags: &Flags, timer: &mut abstutil::Timer) -> raw_data::Map { let elevation = Elevation::new(&flags.elevation).expect("loading .hgt failed"); - let raw_map = osm::osm_to_raw_roads(&flags.osm, timer); - let mut map = split_ways::split_up_roads(raw_map, &elevation); + let mut map = split_ways::split_up_roads(osm::osm_to_raw_roads(&flags.osm, timer), &elevation); remove_disconnected::remove_disconnected_roads(&mut map, timer); let gps_bounds = map.get_gps_bounds(); @@ -111,7 +110,7 @@ pub fn convert(flags: &Flags, timer: &mut abstutil::Timer) -> raw_data::Map { // intersection let closest_intersection = map .intersections - .iter_mut() + .values_mut() .min_by_key(|i| NotNan::new(distance(i)).unwrap()) .unwrap(); let dist = distance(closest_intersection); @@ -139,8 +138,9 @@ pub fn convert(flags: &Flags, timer: &mut abstutil::Timer) -> raw_data::Map { fn use_parking_hints(map: &mut raw_data::Map, shapes: ExtraShapes, gps_bounds: &GPSBounds) { // Match shapes with the nearest road + direction (true for forwards) - let mut closest: FindClosest<(usize, bool)> = FindClosest::new(&gps_bounds.to_bounds()); - for (idx, r) in map.roads.iter().enumerate() { + let mut closest: FindClosest<(raw_data::StableRoadID, bool)> = + FindClosest::new(&gps_bounds.to_bounds()); + for (id, r) in &map.roads { let pts = PolyLine::new( r.points .iter() @@ -148,8 +148,8 @@ fn use_parking_hints(map: &mut raw_data::Map, shapes: ExtraShapes, gps_bounds: & .collect(), ); - closest.add((idx, true), &pts.shift_right(LANE_THICKNESS)); - closest.add((idx, false), &pts.shift_left(LANE_THICKNESS)); + closest.add((*id, true), &pts.shift_right(LANE_THICKNESS)); + closest.add((*id, false), &pts.shift_left(LANE_THICKNESS)); } 'SHAPE: for s in shapes.shapes.into_iter() { @@ -173,9 +173,9 @@ fn use_parking_hints(map: &mut raw_data::Map, shapes: ExtraShapes, gps_bounds: & && category != Some(&"No Parking Allowed".to_string()); // Blindly override prior values. if fwds { - map.roads[r].parking_lane_fwd = has_parking; + map.roads.get_mut(&r).unwrap().parking_lane_fwd = has_parking; } else { - map.roads[r].parking_lane_back = has_parking; + map.roads.get_mut(&r).unwrap().parking_lane_back = has_parking; } } } diff --git a/convert_osm/src/osm.rs b/convert_osm/src/osm.rs index 5c7ef9c106..a4043dbec3 100644 --- a/convert_osm/src/osm.rs +++ b/convert_osm/src/osm.rs @@ -6,8 +6,14 @@ use map_model::{raw_data, AreaType}; use osm_xml; use std::collections::{BTreeMap, HashMap}; -// TODO Result, but is there an easy way to say io error or osm xml error? -pub fn osm_to_raw_roads(osm_path: &str, timer: &mut Timer) -> raw_data::Map { +pub fn osm_to_raw_roads( + osm_path: &str, + timer: &mut Timer, +) -> ( + Vec, + Vec, + Vec, +) { let (reader, done) = FileWithProgress::new(osm_path).unwrap(); let doc = osm_xml::OSM::parse(reader).expect("OSM parsing failed"); println!( @@ -19,7 +25,9 @@ pub fn osm_to_raw_roads(osm_path: &str, timer: &mut Timer) -> raw_data::Map { done(timer); let mut id_to_way: HashMap> = HashMap::new(); - let mut map = raw_data::Map::blank(); + let mut roads: Vec = Vec::new(); + let mut buildings: Vec = Vec::new(); + let mut areas: Vec = Vec::new(); timer.start_iter("processing OSM ways", doc.ways.len()); for way in doc.ways.values() { timer.next(); @@ -42,7 +50,7 @@ pub fn osm_to_raw_roads(osm_path: &str, timer: &mut Timer) -> raw_data::Map { } let tags = tags_to_map(&way.tags); if is_road(&tags) { - map.roads.push(raw_data::Road { + roads.push(raw_data::Road { osm_way_id: way.id, points: pts, osm_tags: tags, @@ -51,13 +59,13 @@ pub fn osm_to_raw_roads(osm_path: &str, timer: &mut Timer) -> raw_data::Map { parking_lane_back: false, }); } else if is_bldg(&tags) { - map.buildings.push(raw_data::Building { + buildings.push(raw_data::Building { osm_way_id: way.id, points: pts, osm_tags: tags, }); } else if let Some(at) = get_area_type(&tags) { - map.areas.push(raw_data::Area { + areas.push(raw_data::Area { area_type: at, osm_way_id: way.id, points: pts, @@ -81,7 +89,7 @@ pub fn osm_to_raw_roads(osm_path: &str, timer: &mut Timer) -> raw_data::Map { match id_to_way.get(&id) { Some(pts) => { if role == "outer" { - map.areas.push(raw_data::Area { + areas.push(raw_data::Area { area_type: at, osm_way_id: id, points: pts.to_vec(), @@ -108,7 +116,7 @@ pub fn osm_to_raw_roads(osm_path: &str, timer: &mut Timer) -> raw_data::Map { } } - map + (roads, buildings, areas) } fn tags_to_map(raw_tags: &[osm_xml::Tag]) -> BTreeMap { diff --git a/convert_osm/src/remove_disconnected.rs b/convert_osm/src/remove_disconnected.rs index 62c3703527..f22ca7e0c2 100644 --- a/convert_osm/src/remove_disconnected.rs +++ b/convert_osm/src/remove_disconnected.rs @@ -8,21 +8,19 @@ pub fn remove_disconnected_roads(map: &mut raw_data::Map, timer: &mut Timer) { // This is a simple floodfill, not Tarjan's. Assumes all roads bidirectional. // All the usizes are indices into the original list of roads - let mut next_roads: MultiMap = MultiMap::new(); - for (idx, r) in map.roads.iter().enumerate() { - next_roads.insert(r.first_pt().to_hashable(), idx); - next_roads.insert(r.last_pt().to_hashable(), idx); + let mut next_roads: MultiMap = MultiMap::new(); + for (id, r) in &map.roads { + next_roads.insert(r.first_pt().to_hashable(), *id); + next_roads.insert(r.last_pt().to_hashable(), *id); } - let mut partitions: Vec> = Vec::new(); - let mut unvisited_roads: HashSet = HashSet::new(); - for i in 0..map.roads.len() { - unvisited_roads.insert(i); - } + let mut partitions: Vec> = Vec::new(); + let mut unvisited_roads: HashSet = map.roads.keys().cloned().collect(); while !unvisited_roads.is_empty() { - let mut queue_roads: Vec = vec![*unvisited_roads.iter().next().unwrap()]; - let mut current_partition: Vec = Vec::new(); + let mut queue_roads: Vec = + vec![*unvisited_roads.iter().next().unwrap()]; + let mut current_partition: Vec = Vec::new(); while !queue_roads.is_empty() { let current = queue_roads.pop().unwrap(); if !unvisited_roads.contains(¤t) { @@ -31,7 +29,7 @@ pub fn remove_disconnected_roads(map: &mut raw_data::Map, timer: &mut Timer) { unvisited_roads.remove(¤t); current_partition.push(current); - let current_r = &map.roads[current]; + let current_r = &map.roads[¤t]; for other_r in next_roads.get(current_r.first_pt().to_hashable()).iter() { queue_roads.push(*other_r); } @@ -45,26 +43,30 @@ pub fn remove_disconnected_roads(map: &mut raw_data::Map, timer: &mut Timer) { partitions.sort_by_key(|roads| roads.len()); partitions.reverse(); println!("Main partition has {} roads", partitions[0].len()); - let mut remove_roads = HashSet::new(); for p in partitions.iter().skip(1) { println!("Removing disconnected partition with {} roads", p.len()); - for idx in p { - remove_roads.insert(idx); + for id in p { + let r = map.roads.remove(id).unwrap(); + next_roads.remove(r.first_pt().to_hashable(), *id); + next_roads.remove(r.last_pt().to_hashable(), *id); } } - let mut roads: Vec = Vec::new(); - for (idx, r) in map.roads.iter().enumerate() { - if remove_roads.contains(&idx) { - next_roads.remove(r.first_pt().to_hashable(), idx); - next_roads.remove(r.last_pt().to_hashable(), idx); - } else { - roads.push(r.clone()); - } - } - map.roads = roads; // Remove intersections without any roads - map.intersections - .retain(|i| !next_roads.get(i.point.to_hashable()).is_empty()); + // TODO retain for BTreeMap, please! + let remove_intersections: Vec = map + .intersections + .iter() + .filter_map(|(id, i)| { + if next_roads.get(i.point.to_hashable()).is_empty() { + Some(*id) + } else { + None + } + }) + .collect(); + for id in remove_intersections { + map.intersections.remove(&id); + } timer.stop("removing disconnected roads"); } diff --git a/convert_osm/src/split_ways.rs b/convert_osm/src/split_ways.rs index 84cd5a379f..4ded260eb8 100644 --- a/convert_osm/src/split_ways.rs +++ b/convert_osm/src/split_ways.rs @@ -6,14 +6,21 @@ use geom::{HashablePt2D, LonLat}; use map_model::{raw_data, IntersectionType}; use std::collections::{BTreeSet, HashMap}; -pub fn split_up_roads(mut input: raw_data::Map, elevation: &srtm::Elevation) -> raw_data::Map { - println!("splitting up {} roads", input.roads.len()); +pub fn split_up_roads( + (mut roads, buildings, areas): ( + Vec, + Vec, + Vec, + ), + elevation: &srtm::Elevation, +) -> raw_data::Map { + println!("splitting up {} roads", roads.len()); // Look for roundabout ways. Map all points on the roundabout to a new point in the center. // When we process ways that touch any point on the roundabout, make them instead point to the // roundabout's center, so that the roundabout winds up looking like a single intersection. let mut remap_roundabouts: HashMap = HashMap::new(); - input.roads.retain(|r| { + roads.retain(|r| { if r.osm_tags.get("junction") == Some(&"roundabout".to_string()) { let center = LonLat::center(&r.points); for pt in &r.points { @@ -27,7 +34,7 @@ pub fn split_up_roads(mut input: raw_data::Map, elevation: &srtm::Elevation) -> let mut counts_per_pt: HashMap = HashMap::new(); let mut intersections: BTreeSet = BTreeSet::new(); - for r in input.roads.iter_mut() { + for r in roads.iter_mut() { let added_to_start = if let Some(center) = remap_roundabouts.get(&r.points[0].to_hashable()) { r.points.insert(0, *center); @@ -74,20 +81,23 @@ pub fn split_up_roads(mut input: raw_data::Map, elevation: &srtm::Elevation) -> } let mut map = raw_data::Map::blank(); - map.buildings.extend(input.buildings.clone()); - map.areas.extend(input.areas.clone()); + map.buildings = buildings; + map.areas = areas; - for pt in &intersections { - map.intersections.push(raw_data::Intersection { - point: LonLat::new(pt.x(), pt.y()), - elevation: elevation.get(pt.x(), pt.y()) * si::M, - intersection_type: IntersectionType::StopSign, - label: None, - }); + for (idx, pt) in intersections.iter().enumerate() { + map.intersections.insert( + raw_data::StableIntersectionID(idx), + raw_data::Intersection { + point: LonLat::new(pt.x(), pt.y()), + elevation: elevation.get(pt.x(), pt.y()) * si::M, + intersection_type: IntersectionType::StopSign, + label: None, + }, + ); } // Now actually split up the roads based on the intersections - for orig_road in &input.roads { + for orig_road in &roads { let mut r = orig_road.clone(); r.points.clear(); @@ -95,7 +105,8 @@ pub fn split_up_roads(mut input: raw_data::Map, elevation: &srtm::Elevation) -> r.points.push(pt.clone()); if r.points.len() > 1 && intersections.contains(&pt.to_hashable()) { // Start a new road - map.roads.push(r.clone()); + map.roads + .insert(raw_data::StableRoadID(map.roads.len()), r.clone()); r.points.clear(); r.points.push(pt.clone()); } @@ -103,7 +114,5 @@ pub fn split_up_roads(mut input: raw_data::Map, elevation: &srtm::Elevation) -> assert!(r.points.len() == 1); } - // TODO we're somehow returning an intersection here with no roads. figure that out. - map } diff --git a/editor/src/plugins/debug/debug_objects.rs b/editor/src/plugins/debug/debug_objects.rs index 7a30bfc679..12c2d2b7c1 100644 --- a/editor/src/plugins/debug/debug_objects.rs +++ b/editor/src/plugins/debug/debug_objects.rs @@ -70,7 +70,10 @@ fn tooltip_lines(obj: ID, ctx: &Ctx) -> Text { None, ); txt.add_line(format!("From OSM way {}", r.osm_way_id)); - txt.add_line(format!("Parent {} points to {}", r.id, r.dst_i)); + txt.add_line(format!( + "Parent {} (stable ID {}) points to {}", + r.id, r.stable_id.0, r.dst_i + )); txt.add_line(format!( "Lane goes from {} to {}", i1.elevation, i2.elevation @@ -88,7 +91,9 @@ fn tooltip_lines(obj: ID, ctx: &Ctx) -> Text { } ID::Intersection(id) => { txt.add_line(id.to_string()); - txt.add_line(format!("Roads: {:?}", map.get_i(id).roads)); + let i = map.get_i(id); + txt.add_line(format!("Roads: {:?}", i.roads)); + txt.add_line(format!("Stable ID {}", i.stable_id.0)); } ID::Turn(id) => { let t = map.get_t(id); diff --git a/ezgui/src/runner.rs b/ezgui/src/runner.rs index b4cebaea91..c2d5153c5c 100644 --- a/ezgui/src/runner.rs +++ b/ezgui/src/runner.rs @@ -304,8 +304,8 @@ impl ScreenCaptureState { args.push("full.png".to_string()); let mut file = fs::File::create("screencap/combine.sh").unwrap(); - write!(file, "#!/bin/bash\n\n").unwrap(); - write!(file, "montage {}\n", args.join(" ")).unwrap(); - write!(file, "rm -f combine.sh\n").unwrap(); + writeln!(file, "#!/bin/bash\n").unwrap(); + writeln!(file, "montage {}", args.join(" ")).unwrap(); + writeln!(file, "rm -f combine.sh").unwrap(); } } diff --git a/map_model/src/intersection.rs b/map_model/src/intersection.rs index 4013838462..dad69c56c0 100644 --- a/map_model/src/intersection.rs +++ b/map_model/src/intersection.rs @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 -use crate::{LaneID, LaneType, Map, RoadID, TurnID}; +use crate::{raw_data, LaneID, LaneType, Map, RoadID, TurnID}; use abstutil; use dimensioned::si; use geom::Pt2D; @@ -38,6 +38,7 @@ pub struct Intersection { pub intersection_type: IntersectionType, pub label: Option, + pub stable_id: raw_data::StableIntersectionID, // Note that a lane may belong to both incoming_lanes and outgoing_lanes. // TODO narrow down when and why. is it just sidewalks in weird cases? diff --git a/map_model/src/make/half_map.rs b/map_model/src/make/half_map.rs index bd4b396aed..9df857ba62 100644 --- a/map_model/src/make/half_map.rs +++ b/map_model/src/make/half_map.rs @@ -29,7 +29,7 @@ pub fn make_half_map( let mut pt_to_intersection: HashMap = HashMap::new(); - for (idx, i) in data.intersections.iter().enumerate() { + for (idx, (stable_id, i)) in data.intersections.iter().enumerate() { let id = IntersectionID(idx); let pt = Pt2D::from_gps(i.point, &gps_bounds).unwrap(); m.intersections.push(Intersection { @@ -41,6 +41,7 @@ pub fn make_half_map( // Might change later intersection_type: i.intersection_type, label: i.label.clone(), + stable_id: *stable_id, incoming_lanes: Vec::new(), outgoing_lanes: Vec::new(), roads: BTreeSet::new(), @@ -50,7 +51,7 @@ pub fn make_half_map( let mut counter = 0; timer.start_iter("expand roads to lanes", data.roads.len()); - for (_, r) in data.roads.iter().enumerate() { + for (stable_id, r) in &data.roads { timer.next(); let road_id = RoadID(m.roads.len()); let road_center_pts = PolyLine::new( @@ -75,6 +76,7 @@ pub fn make_half_map( id: road_id, osm_tags: r.osm_tags.clone(), osm_way_id: r.osm_way_id, + stable_id: *stable_id, children_forwards: Vec::new(), children_backwards: Vec::new(), center_pts: road_center_pts.clone(), diff --git a/map_model/src/make/merge_intersections.rs b/map_model/src/make/merge_intersections.rs index aab6f1851c..153b712fb9 100644 --- a/map_model/src/make/merge_intersections.rs +++ b/map_model/src/make/merge_intersections.rs @@ -72,6 +72,7 @@ fn merge(delete_r: RoadID, mut m: HalfMap) -> HalfMap { elevation: m.intersections[old_i1.0].elevation, intersection_type: m.intersections[old_i1.0].intersection_type, label: m.intersections[old_i1.0].label.clone(), + stable_id: m.intersections[old_i1.0].stable_id, incoming_lanes: Vec::new(), outgoing_lanes: Vec::new(), roads: BTreeSet::new(), diff --git a/map_model/src/raw_data.rs b/map_model/src/raw_data.rs index 2411a05eaf..79ff418d16 100644 --- a/map_model/src/raw_data.rs +++ b/map_model/src/raw_data.rs @@ -6,10 +6,17 @@ use gtfs::Route; use serde_derive::{Deserialize, Serialize}; use std::collections::BTreeMap; +// Stable IDs don't get compacted as we merge and delete things. +//#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, PartialOrd, Ord, Clone, Copy, Hash)] +pub struct StableRoadID(pub usize); +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, PartialOrd, Ord, Clone, Copy, Hash)] +pub struct StableIntersectionID(pub usize); + #[derive(PartialEq, Debug, Serialize, Deserialize)] pub struct Map { - pub roads: Vec, - pub intersections: Vec, + pub roads: BTreeMap, + pub intersections: BTreeMap, pub buildings: Vec, pub parcels: Vec, pub bus_routes: Vec, @@ -21,8 +28,8 @@ pub struct Map { impl Map { pub fn blank() -> Map { Map { - roads: Vec::new(), - intersections: Vec::new(), + roads: BTreeMap::new(), + intersections: BTreeMap::new(), buildings: Vec::new(), parcels: Vec::new(), bus_routes: Vec::new(), @@ -34,12 +41,12 @@ impl Map { pub fn get_gps_bounds(&self) -> GPSBounds { let mut bounds = GPSBounds::new(); - for r in &self.roads { + for r in self.roads.values() { for pt in &r.points { bounds.update(*pt); } } - for i in &self.intersections { + for i in self.intersections.values() { bounds.update(i.point); } for b in &self.buildings { diff --git a/map_model/src/road.rs b/map_model/src/road.rs index ffe06d2ad4..61e53afa9d 100644 --- a/map_model/src/road.rs +++ b/map_model/src/road.rs @@ -1,4 +1,4 @@ -use crate::{IntersectionID, LaneID, LaneType}; +use crate::{raw_data, IntersectionID, LaneID, LaneType}; use abstutil::Error; use dimensioned::si; use geom::PolyLine; @@ -22,6 +22,7 @@ pub struct Road { pub id: RoadID, pub osm_tags: BTreeMap, pub osm_way_id: i64, + pub stable_id: raw_data::StableRoadID, // Invariant: A road must contain at least one child pub children_forwards: Vec<(LaneID, LaneType)>, diff --git a/synthetic/src/model.rs b/synthetic/src/model.rs index ccd4dc328b..f2643c4134 100644 --- a/synthetic/src/model.rs +++ b/synthetic/src/model.rs @@ -336,25 +336,31 @@ impl Model { if let Some(ref label) = r.back_label { osm_tags.insert("back_label".to_string(), label.to_string()); } - map.roads.push(raw_data::Road { - points: vec![ - pt(self.intersections[&r.i1].center), - pt(self.intersections[&r.i2].center), - ], - osm_tags, - osm_way_id: idx as i64, - parking_lane_fwd: r.lanes.fwd.contains(&LaneType::Parking), - parking_lane_back: r.lanes.back.contains(&LaneType::Parking), - }); + map.roads.insert( + raw_data::StableRoadID(idx), + raw_data::Road { + points: vec![ + pt(self.intersections[&r.i1].center), + pt(self.intersections[&r.i2].center), + ], + osm_tags, + osm_way_id: idx as i64, + parking_lane_fwd: r.lanes.fwd.contains(&LaneType::Parking), + parking_lane_back: r.lanes.back.contains(&LaneType::Parking), + }, + ); } - for i in self.intersections.values() { - map.intersections.push(raw_data::Intersection { - point: pt(i.center), - elevation: 0.0 * si::M, - intersection_type: i.intersection_type, - label: i.label.clone(), - }); + for (idx, i) in self.intersections.values().enumerate() { + map.intersections.insert( + raw_data::StableIntersectionID(idx), + raw_data::Intersection { + point: pt(i.center), + elevation: 0.0 * si::M, + intersection_type: i.intersection_type, + label: i.label.clone(), + }, + ); } for (idx, b) in self.buildings.values().enumerate() { @@ -387,7 +393,7 @@ impl Model { let mut pt_to_intersection: HashMap = HashMap::new(); let mut quadtree = QuadTree::default(gps_bounds.to_bounds().as_bbox()); - for (idx, i) in data.intersections.iter().enumerate() { + for (idx, i) in data.intersections.values().enumerate() { let center = Pt2D::from_gps(i.point, &gps_bounds).unwrap(); let i = Intersection { center, @@ -399,7 +405,7 @@ impl Model { pt_to_intersection.insert(center.into(), idx); } - for r in &data.roads { + for r in data.roads.values() { let i1 = pt_to_intersection[&Pt2D::from_gps(r.points[0], &gps_bounds).unwrap().into()]; let i2 = pt_to_intersection[&Pt2D::from_gps(*r.points.last().unwrap(), &gps_bounds) .unwrap() diff --git a/tests/src/map_conversion.rs b/tests/src/map_conversion.rs index 415baf9862..f758204f04 100644 --- a/tests/src/map_conversion.rs +++ b/tests/src/map_conversion.rs @@ -16,6 +16,7 @@ pub fn run(t: &mut TestRunner) { gtfs: "../data/input/google_transit_2018_18_08".to_string(), neighborhoods: "../data/input/neighborhoods.geojson".to_string(), output: "convert_osm_twice".to_string(), + fast_dev: false, }; let map1 = convert_osm::convert(&flags, &mut abstutil::Timer::new("convert map"));