fifteen_min/
bus.rs
1use abstutil::prettyprint_usize;
2use geom::Duration;
3use map_gui::tools::{InputWaypoints, WaypointID};
4use map_model::connectivity::WalkingOptions;
5use map_model::PathConstraints;
6use synthpop::{TripEndpoint, TripMode};
7use widgetry::mapspace::{ObjectID, World, WorldOutcome};
8use widgetry::{
9 Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
10 Transition, VerticalAlignment, Widget,
11};
12
13use crate::isochrone::{Isochrone, MovementOptions, Options};
14use crate::App;
15
16pub struct BusExperiment {
17 panel: Panel,
18 waypoints: InputWaypoints,
19 world: World<ID>,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23enum ID {
24 Waypoint(WaypointID),
25 BusRoute(usize),
27}
28impl ObjectID for ID {}
29
30impl BusExperiment {
31 pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
32 let mut state = BusExperiment {
33 panel: Panel::empty(ctx),
34 waypoints: InputWaypoints::new(app, vec![PathConstraints::Car, PathConstraints::Bus]),
35 world: World::new(),
36 };
37 state.recalculate_everything(ctx, app);
38 Box::new(state)
39 }
40
41 fn recalculate_everything(&mut self, ctx: &mut EventCtx, app: &App) {
42 let map = &app.map;
43 let mut world = World::new();
44 self.waypoints
45 .rebuild_world(ctx, &mut world, ID::Waypoint, 1);
46
47 for (idx, pair) in self.waypoints.get_waypoints().windows(2).enumerate() {
48 if let Some(path) = TripEndpoint::path_req(pair[0], pair[1], TripMode::Drive, map)
50 .and_then(|req| map.pathfind(req).ok())
51 {
52 let duration = path.estimate_duration(map, None);
53 if let Ok(hitbox) = path.trace_v2(map) {
54 world
55 .add(ID::BusRoute(idx))
56 .hitbox(hitbox)
57 .zorder(0)
58 .draw_color(self.waypoints.get_waypoint_color(idx))
59 .hover_alpha(0.8)
60 .tooltip(Text::from(Line(format!("Freeflow time is {duration}"))))
61 .build(ctx);
62 }
63 }
64 }
65
66 let stops = self
67 .waypoints
68 .get_waypoints()
69 .into_iter()
70 .filter_map(|endpt| match endpt {
71 TripEndpoint::Building(b) => Some(b),
72 _ => None,
73 })
74 .collect::<Vec<_>>();
75 let isochrone = Isochrone::new(
76 ctx,
77 app,
78 stops,
79 Options {
80 movement: MovementOptions::Walking(WalkingOptions::default()),
81 thresholds: vec![(Duration::minutes(15), Color::grey(0.3).alpha(0.5))],
82 },
89 );
90 world.draw_master_batch_built(isochrone.draw);
91
92 world.initialize_hover(ctx);
93 world.rebuilt_during_drag(ctx, &self.world);
94 self.world = world;
95
96 self.panel = Panel::new_builder(Widget::col(vec![
97 map_gui::tools::app_header(ctx, app, "Bus planner"),
98 ctx.style()
99 .btn_back("15-minute neighborhoods")
100 .hotkey(Key::Escape)
101 .build_def(ctx),
102 Text::from_multiline(vec![
103 Line("Within a 15 min walk of all stops:"),
104 Line(format!(
105 "Population: {}",
106 prettyprint_usize(isochrone.population)
107 )),
108 Line(format!(
109 "Shops: {}",
110 prettyprint_usize(
111 isochrone
112 .amenities_reachable
113 .borrow()
114 .values()
115 .map(|x| x.len())
116 .sum()
117 )
118 )),
119 ])
120 .into_widget(ctx),
121 self.waypoints.get_panel_widget(ctx),
122 ]))
123 .aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
124 .ignore_initial_events()
125 .build(ctx);
126 }
127}
128
129impl State<App> for BusExperiment {
130 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
131 let panel_outcome = self.panel.event(ctx);
132 if let Outcome::Clicked(ref x) = panel_outcome {
133 if x == "15-minute neighborhoods" {
134 return Transition::Pop;
135 }
136 }
137
138 let world_outcome = self.world.event(ctx);
139 let world_outcome_for_waypoints = world_outcome
140 .maybe_map_id(|id| match id {
141 ID::Waypoint(id) => Some(id),
142 _ => None,
143 })
144 .unwrap_or(WorldOutcome::Nothing);
145
146 if self
147 .waypoints
148 .event(app, panel_outcome, world_outcome_for_waypoints)
149 {
150 self.recalculate_everything(ctx, app);
151 }
152
153 Transition::Keep
154 }
155
156 fn draw(&self, g: &mut GfxCtx, _: &App) {
157 self.panel.draw(g);
158 self.world.draw(g);
159 }
160}