Prompt the user to use a bus gate when necessary. #965

This commit is contained in:
Dustin Carlino 2022-08-31 14:06:11 +01:00
parent daf3ed379d
commit bb389bf003
5 changed files with 125 additions and 9 deletions

View File

@ -84,6 +84,12 @@ impl Layers {
}
}
pub fn show_bus_routes(&mut self, ctx: &mut EventCtx, cs: &ColorScheme) {
self.minimized = false;
self.show_bus_routes = true;
self.update_panel(ctx, cs);
}
fn update_panel(&mut self, ctx: &mut EventCtx, cs: &ColorScheme) {
self.panel = Panel::new_builder(
Widget::col(vec![

View File

@ -3,7 +3,7 @@ use widgetry::tools::open_browser;
use widgetry::{lctrl, EventCtx, Key, Line, Text, Transition, Widget};
use super::{road_name, EditOutcome, Obj};
use crate::{after_edit, colors, App, DiagonalFilter, Neighbourhood, RoadFilter};
use crate::{after_edit, colors, App, DiagonalFilter, FilterType, Neighbourhood, RoadFilter};
pub fn widget(ctx: &mut EventCtx) -> Widget {
Text::from(Line(
@ -84,6 +84,17 @@ pub fn handle_world_outcome(
let pt_on_line = road.center_pts.project_pt(cursor_pt);
let (distance, _) = road.center_pts.dist_along_of_point(pt_on_line).unwrap();
// If we have a one-way bus route, the one-way resolver will win and we won't warn
// about bus gates. Oh well.
if app.session.filter_type != FilterType::BusGate
&& !app.map.get_bus_routes_on_road(r).is_empty()
{
app.session.edits.cancel_empty_edit();
return EditOutcome::Transition(Transition::Push(
super::ResolveBusGate::new_state(ctx, app, vec![(r, distance)]),
));
}
app.session.edits.roads.insert(
r,
RoadFilter::new_by_user(distance, app.session.filter_type),

View File

@ -2,7 +2,7 @@ use geom::PolyLine;
use widgetry::{EventCtx, Line, Text, Widget};
use crate::edit::{EditMode, EditOutcome};
use crate::{after_edit, App, DiagonalFilter, Neighbourhood, RoadFilter, Transition};
use crate::{after_edit, App, DiagonalFilter, FilterType, Neighbourhood, RoadFilter, Transition};
pub fn widget(ctx: &mut EventCtx) -> Widget {
Text::from_all(vec![
@ -35,6 +35,7 @@ fn make_filters_along_path(
path: PolyLine,
) -> Transition {
let mut oneways = Vec::new();
let mut bus_roads = Vec::new();
app.session.edits.before_edit();
for r in &neighbourhood.orig_perimeter.interior {
@ -57,6 +58,14 @@ fn make_filters_along_path(
.dist_along_of_point(pt)
.map(|pair| pair.0)
.unwrap_or(road.center_pts.length() / 2.0);
if app.session.filter_type != FilterType::BusGate
&& !app.map.get_bus_routes_on_road(*r).is_empty()
{
bus_roads.push((*r, dist));
continue;
}
app.session
.edits
.roads
@ -71,9 +80,11 @@ fn make_filters_along_path(
}
after_edit(ctx, app);
if oneways.is_empty() {
Transition::Recreate
} else {
if !oneways.is_empty() {
Transition::Push(super::ResolveOneWayAndFilter::new_state(ctx, oneways))
} else if !bus_roads.is_empty() {
Transition::Push(super::ResolveBusGate::new_state(ctx, app, bus_roads))
} else {
Transition::Recreate
}
}

View File

@ -3,6 +3,9 @@ mod freehand_filters;
mod one_ways;
mod shortcuts;
use std::collections::BTreeSet;
use geom::Distance;
use map_gui::tools::grey_out_map;
use map_model::{EditRoad, IntersectionID, Road, RoadID};
use street_network::{Direction, LaneSpec};
@ -405,3 +408,88 @@ impl State<App> for ResolveOneWayAndFilter {
self.panel.draw(g);
}
}
struct ResolveBusGate {
panel: Panel,
roads: Vec<(RoadID, Distance)>,
}
impl ResolveBusGate {
fn new_state(
ctx: &mut EventCtx,
app: &mut App,
roads: Vec<(RoadID, Distance)>,
) -> Box<dyn State<App>> {
app.session.layers.show_bus_routes(ctx, &app.cs);
let mut txt = Text::new();
txt.add_line(Line("Warning").small_heading());
txt.add_line("A regular modal filter would impact bus routes here.");
txt.add_line("A bus gate uses signage and camera enforcement to only allow buses");
// TODO Enable after regenerating maps with correct route names
if false {
txt.add_line("");
txt.add_line("The following bus routes cross this road:");
let mut routes = BTreeSet::new();
for (r, _) in &roads {
routes.extend(app.map.get_bus_routes_on_road(*r));
}
for route in routes {
txt.add_line(format!("- {route}"));
}
}
let panel = Panel::new_builder(Widget::col(vec![
txt.into_widget(ctx),
Widget::row(vec![
// TODO Just have pictures?
ctx.style()
.btn_solid
.text("Place a regular modal filter here")
.build_def(ctx),
ctx.style()
.btn_solid_primary
.text("Place bus gates")
.build_def(ctx),
]),
]))
.build(ctx);
Box::new(Self { panel, roads })
}
}
impl State<App> for ResolveBusGate {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if let Outcome::Clicked(x) = self.panel.event(ctx) {
app.session.edits.before_edit();
let filter_type = if x == "Place bus gates" {
FilterType::BusGate
} else {
app.session.filter_type
};
for (r, dist) in self.roads.drain(..) {
app.session
.edits
.roads
.insert(r, RoadFilter::new_by_user(dist, filter_type));
}
after_edit(ctx, app);
return Transition::Multi(vec![Transition::Pop, Transition::Recreate]);
}
Transition::Keep
}
fn draw_baselayer(&self) -> DrawBaselayer {
DrawBaselayer::PreviousState
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
grey_out_map(g, app);
self.panel.draw(g);
}
}

View File

@ -222,10 +222,10 @@ pub fn extract_osm(
}
}
} else if rel.tags.is("type", "route") && rel.tags.is("route", "bus") {
for (role, member) in &rel.members {
if let OsmID::Way(w) = member {
if role.is_empty() {
if let Some(name) = doc.ways[w].tags.get("name") {
if let Some(name) = rel.tags.get("name") {
for (role, member) in &rel.members {
if let OsmID::Way(w) = member {
if role.is_empty() {
bus_routes_on_roads.insert(*w, name.to_string());
}
}