diff --git a/data/hints.json b/data/hints.json index 5e12fb2821..82b9ca72c0 100644 --- a/data/hints.json +++ b/data/hints.json @@ -331,6 +331,30 @@ "latitude": 47.6447682 } } + }, + { + "BanTurnsBetween": [ + { + "pt1": { + "longitude": -122.2961948, + "latitude": 47.6234769 + }, + "pt2": { + "longitude": -122.296451, + "latitude": 47.6236649 + } + }, + { + "pt1": { + "longitude": -122.2965799, + "latitude": 47.6236045 + }, + "pt2": { + "longitude": -122.2964149, + "latitude": 47.6233284 + } + } + ] } ], "parking_overrides": [ @@ -341,7 +365,7 @@ "latitude": 47.647500000184834 }, "pt2": { - "longitude": -122.30462539972673, + "longitude": -122.30462539972672, "latitude": 47.6469044999953 } }, diff --git a/docs/articles/map/article.md b/docs/articles/map/article.md index 36921a53e9..b99b486ad8 100644 --- a/docs/articles/map/article.md +++ b/docs/articles/map/article.md @@ -6,7 +6,7 @@ become the complex maps in A/B Street. As always, process generalizes to most cities in OpenStreetMap. Some extra data specific to Seattle is used, but could be ommitted. -Everything here should be up-to-date as of July 2019. +Everything here should be up-to-date as of August 2019. @@ -176,6 +176,9 @@ before the final Map: InitialMap and HalfMap. lanes, speed limit, or some other reason to preserve the intersection. - The hint to override parking on a road: the blockface dataset is inaccurate sometimes, so manually toggle parking on a road. + - The ban turns hint: don't allow turns from one road to another. In areas + with many close intersections, there are some U-turns that may be valid in + reality, but quickly cause gridlock in the simulation. ## InitialMap to HalfMap diff --git a/fix_map_geom/src/main.rs b/fix_map_geom/src/main.rs index 859e47e70b..c093d3ae37 100644 --- a/fix_map_geom/src/main.rs +++ b/fix_map_geom/src/main.rs @@ -28,6 +28,11 @@ enum State { osd: Text, }, BrowsingHints(WarpingItemSlider), + BanTurnsBetween { + from: StableRoadID, + selected: Option, + osd: Text, + }, } impl State { @@ -95,13 +100,6 @@ impl GUI for UI { len, self.hints.parking_overrides.len() )); - for i in (1..=5).rev() { - if len >= i { - txt.add_line(describe(&self.hints.hints[len - i])); - } else { - txt.add_line("...".to_string()); - } - } if let Some(ID::Road(r)) = selected { txt.push(format!( "[red:{}] is {} long", @@ -169,7 +167,9 @@ impl GUI for UI { .iter() .filter_map(|h| { let gps_pt = match h { - Hint::MergeRoad(r) | Hint::DeleteRoad(r) => { + Hint::MergeRoad(r) + | Hint::DeleteRoad(r) + | Hint::BanTurnsBetween(r, _) => { self.raw.roads[&self.raw.find_r(*r)?].points[0] } Hint::MergeDegenerateIntersection(i) => { @@ -240,6 +240,16 @@ impl GUI for UI { &mut Timer::new("override parking"), ); self.world = initial_map_to_world(&self.data, ctx); + } else if ctx + .input + .key_pressed(Key::T, "ban turns between this road and another") + { + self.state = State::BanTurnsBetween { + from: *r, + selected: *selected, + osd: Text::new(), + }; + return EventLoopMode::InputOnly; } } if let Some(ID::Intersection(i)) = selected { @@ -271,6 +281,43 @@ impl GUI for UI { EventLoopMode::InputOnly } } + State::BanTurnsBetween { + from, + ref mut selected, + ref mut osd, + } => { + ctx.canvas.handle_event(ctx.input); + + if ctx.redo_mouseover() { + *selected = self.world.mouseover_something(ctx, &HashSet::new()); + } + + if ctx.input.key_pressed(Key::Escape, "cancel") { + self.state = State::main(ctx); + return EventLoopMode::InputOnly; + } else if let Some(ID::Road(r)) = selected { + // TODO Why do we use data and not raw here? + let (i1, i2) = (self.data.roads[&from].src_i, self.data.roads[&from].dst_i); + let (i3, i4) = (self.data.roads[r].src_i, self.data.roads[r].dst_i); + + if from != *r + && (i1 == i3 || i1 == i4 || i2 == i3 || i2 == i4) + && ctx.input.key_pressed(Key::T, "ban turns to this road") + { + self.hints.hints.push(Hint::BanTurnsBetween( + self.raw.roads[&from].orig_id(), + self.raw.roads[r].orig_id(), + )); + // There's nothing to change about our model here. + self.state = State::main(ctx); + return EventLoopMode::InputOnly; + } + } + + *osd = Text::new(); + ctx.input.populate_osd(osd); + EventLoopMode::InputOnly + } } } @@ -295,7 +342,7 @@ impl GUI for UI { State::BrowsingHints(ref slider) => { let poly = match slider.get().1 { - Hint::MergeRoad(r) | Hint::DeleteRoad(r) => { + Hint::MergeRoad(r) | Hint::DeleteRoad(r) | Hint::BanTurnsBetween(r, _) => { PolyLine::new(self.raw.gps_bounds.must_convert( &self.raw.roads[&self.raw.find_r(*r).unwrap()].points, )) @@ -316,6 +363,17 @@ impl GUI for UI { slider.draw(g); } + State::BanTurnsBetween { + ref selected, + ref osd, + .. + } => { + if let Some(id) = selected { + self.world.draw_selected(g, *id); + } + + g.draw_blocking_text(osd, ezgui::BOTTOM_LEFT); + } } } } @@ -392,5 +450,6 @@ fn describe(hint: &Hint) -> String { Hint::MergeRoad(_) => "MergeRoad(...)".to_string(), Hint::DeleteRoad(_) => "DeleteRoad(...)".to_string(), Hint::MergeDegenerateIntersection(_) => "MergeDegenerateIntersection(...)".to_string(), + Hint::BanTurnsBetween(_, _) => "BanTurnsBetween(...)".to_string(), } } diff --git a/format_md.sh b/format_md.sh index 24f1e76f97..4f3dd0925d 100755 --- a/format_md.sh +++ b/format_md.sh @@ -2,6 +2,11 @@ set -e +if [ "$1" != "" ]; then + ~/npm/node_modules/prettier/bin-prettier.js --write --prose-wrap=always $1; + exit +fi + # Ignore notes; they're not important for x in `find | grep '.md$' | grep -v design/notes | grep -v TODO_ | grep -v CHANGELOG.md`; do ~/npm/node_modules/prettier/bin-prettier.js --write --prose-wrap=always $x; diff --git a/map_model/src/make/half_map.rs b/map_model/src/make/half_map.rs index 61adc6434e..a1bfae1606 100644 --- a/map_model/src/make/half_map.rs +++ b/map_model/src/make/half_map.rs @@ -87,6 +87,10 @@ pub fn make_half_map( parking_lane_fwd: r.parking_lane_fwd, parking_lane_back: r.parking_lane_back, }; + for stable_id in &r.override_turn_restrictions_to { + road.turn_restrictions + .push(("no_anything".to_string(), road_id_mapping[stable_id])); + } for lane in &r.lane_specs { let id = LaneID(half_map.lanes.len()); diff --git a/map_model/src/make/initial/mod.rs b/map_model/src/make/initial/mod.rs index 954605131e..c3ec444e06 100644 --- a/map_model/src/make/initial/mod.rs +++ b/map_model/src/make/initial/mod.rs @@ -32,6 +32,7 @@ pub struct Road { pub osm_tags: BTreeMap, pub parking_lane_fwd: bool, pub parking_lane_back: bool, + pub override_turn_restrictions_to: Vec, } impl Road { @@ -165,6 +166,7 @@ impl InitialMap { osm_tags: r.osm_tags.clone(), parking_lane_fwd: r.parking_lane_fwd, parking_lane_back: r.parking_lane_back, + override_turn_restrictions_to: Vec::new(), }, ); } @@ -345,6 +347,18 @@ impl InitialMap { self.merge_degenerate_intersection(i, timer); } } + Hint::BanTurnsBetween(orig1, orig2) => { + if let Some(r1) = raw.find_r(*orig1) { + if let Some(r2) = raw.find_r(*orig2) { + self.roads + .get_mut(&r1) + .unwrap() + .override_turn_restrictions_to + .push(r2); + cnt += 1; + } + } + } } } timer.note(format!("Applied {} of {} hints", cnt, hints.hints.len())); @@ -395,6 +409,7 @@ pub enum Hint { MergeRoad(raw_data::OriginalRoad), DeleteRoad(raw_data::OriginalRoad), MergeDegenerateIntersection(raw_data::OriginalIntersection), + BanTurnsBetween(raw_data::OriginalRoad, raw_data::OriginalRoad), } pub struct LaneSpec { diff --git a/map_model/src/make/turns.rs b/map_model/src/make/turns.rs index a0a9aa5a24..87433fb486 100644 --- a/map_model/src/make/turns.rs +++ b/map_model/src/make/turns.rs @@ -526,7 +526,7 @@ fn does_turn_pass_restrictions( // // Strip off time restrictions (like " @ (Mo-Fr 06:00-09:00, 15:00-18:30)") match restriction.split(" @ ").next().unwrap() { - "no_left_turn" | "no_right_turn" | "no_straight_on" | "no_u_turn" => { + "no_left_turn" | "no_right_turn" | "no_straight_on" | "no_u_turn" | "no_anything" => { if dst == *to { return false; }