1use std::collections::HashMap;
2
3use abstutil::{prettyprint_usize, Counter, Timer};
4use geom::{ArrowCap, Distance, Duration, Time};
5use map_gui::render::DrawOptions;
6use map_model::{IntersectionID, MovementID, PathStep, TurnType};
7use synthpop::TripEndpoint;
8use widgetry::mapspace::{DummyID, World};
9use widgetry::{
10 Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
11 Panel, Spinner, State, Text, TextExt, VerticalAlignment, Widget,
12};
13
14use crate::app::{App, ShowEverything, Transition};
15use crate::sandbox::dashboards::DashTab;
16
17pub struct TrafficSignalDemand {
18 panel: Panel,
19 all_demand: HashMap<IntersectionID, Demand>,
20 hour: Time,
21 world: World<DummyID>,
22}
23
24impl TrafficSignalDemand {
25 pub fn new_state(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
26 let all_demand = ctx.loading_screen("predict all demand", |_, timer| {
27 Demand::all_demand(app, timer)
28 });
29
30 app.primary.current_selection = None;
31 assert!(app.primary.suspended_sim.is_none());
32 app.primary.suspended_sim = Some(app.primary.clear_sim());
33
34 let hour = Time::START_OF_DAY;
35 let mut state = TrafficSignalDemand {
36 all_demand,
37 hour,
38 world: World::new(),
39 panel: Panel::new_builder(Widget::col(vec![
40 DashTab::TrafficSignals.picker(ctx, app),
41 Text::from_all(vec![
42 Line("Press "),
43 Key::LeftArrow.txt(ctx),
44 Line(" and "),
45 Key::RightArrow.txt(ctx),
46 Line(" to adjust the hour"),
47 ])
48 .into_widget(ctx),
49 Widget::row(vec![
50 "Hour:".text_widget(ctx).centered_vert(),
51 Spinner::widget(
52 ctx,
53 "hour",
54 (Duration::ZERO, Duration::hours(24)),
55 Duration::hours(7),
56 Duration::hours(1),
57 ),
58 ]),
59 ]))
60 .aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
61 .build(ctx),
62 };
63 state.rebuild_world(ctx, app);
64 Box::new(state)
65 }
66
67 fn rebuild_world(&mut self, ctx: &mut EventCtx, app: &App) {
68 let mut world = World::new();
69
70 let mut draw_all = GeomBatch::new();
71 for (i, demand) in &self.all_demand {
72 let cnt_per_movement = demand.count(self.hour);
73 let total_demand = cnt_per_movement.sum();
74
75 let mut outlines = Vec::new();
76 for (movement, cnt) in cnt_per_movement.consume() {
77 let percent = (cnt as f64) / (total_demand as f64);
78 let arrow = app.primary.map.get_i(*i).movements[&movement]
79 .geom
80 .make_arrow(percent * Distance::meters(3.0), ArrowCap::Triangle);
81
82 let mut draw_hovered = GeomBatch::new();
83 let outline = arrow.to_outline(Distance::meters(0.1));
84 outlines.push(outline.clone());
85 draw_hovered.push(Color::WHITE, outline);
86 draw_all.push(Color::hex("#A3A3A3"), arrow.clone());
87 draw_hovered.push(Color::hex("#EE702E"), arrow.clone());
88
89 world
90 .add_unnamed()
91 .hitbox(arrow)
92 .drawn_in_master_batch()
93 .draw_hovered(draw_hovered)
94 .tooltip(Text::from(format!(
95 "{} / {}",
96 prettyprint_usize(cnt),
97 prettyprint_usize(total_demand)
98 )))
99 .build(ctx);
100 }
101 draw_all.extend(Color::WHITE, outlines);
102 }
103 world.draw_master_batch(ctx, draw_all);
104
105 world.initialize_hover(ctx);
106 self.world = world;
107 }
108}
109
110impl State<App> for TrafficSignalDemand {
111 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
112 self.world.event(ctx);
113
114 let mut changed = false;
115 match self.panel.event(ctx) {
116 Outcome::Clicked(x) => match x.as_ref() {
117 "close" => {
118 app.primary.sim = app.primary.suspended_sim.take().unwrap();
119 return Transition::Pop;
120 }
121 _ => unreachable!(),
122 },
123 Outcome::Changed(_) => {
124 if let Some(tab) = DashTab::TrafficSignals.tab_changed(app, &self.panel) {
125 app.primary.sim = app.primary.suspended_sim.take().unwrap();
126 return Transition::Replace(tab.launch(ctx, app));
127 }
128 changed = true;
129 }
130 _ => {}
131 }
132 if ctx.input.pressed(Key::LeftArrow) {
133 self.panel
134 .modify_spinner(ctx, "hour", -1.0 * Duration::hours(1));
135 changed = true;
136 }
137 if ctx.input.pressed(Key::RightArrow) {
138 self.panel.modify_spinner(ctx, "hour", Duration::hours(1));
139 changed = true;
140 }
141 if changed {
142 self.hour = Time::START_OF_DAY + self.panel.spinner("hour");
143 self.rebuild_world(ctx, app);
144 }
145
146 Transition::Keep
147 }
148
149 fn draw_baselayer(&self) -> DrawBaselayer {
150 DrawBaselayer::Custom
151 }
152
153 fn draw(&self, g: &mut GfxCtx, app: &App) {
154 let mut opts = DrawOptions::new();
155 opts.suppress_traffic_signal_details
156 .extend(self.all_demand.keys().cloned());
157 app.draw(g, opts, &ShowEverything::new());
158
159 self.panel.draw(g);
160 self.world.draw(g);
161 }
162}
163
164struct Demand {
165 raw: Vec<(Time, MovementID)>,
167}
168
169impl Demand {
170 fn all_demand(app: &App, timer: &mut Timer) -> HashMap<IntersectionID, Demand> {
171 let map = &app.primary.map;
172
173 let mut all_demand = HashMap::new();
174 for i in map.all_intersections() {
175 if i.is_traffic_signal() {
176 all_demand.insert(i.id, Demand { raw: Vec::new() });
177 }
178 }
179
180 let paths = timer
181 .parallelize(
182 "predict routes",
183 app.primary.sim.all_trip_info(),
184 |(_, trip)| {
185 let departure = trip.departure;
186 TripEndpoint::path_req(trip.start, trip.end, trip.mode, map)
187 .and_then(|req| map.pathfind(req).ok())
188 .map(|path| (departure, path))
189 },
190 )
191 .into_iter()
192 .flatten()
193 .collect::<Vec<_>>();
194 timer.start_iter("compute demand", paths.len());
195 for (now, path) in paths {
196 timer.next();
197 for step in path.get_steps() {
199 match step {
200 PathStep::Lane(_) | PathStep::ContraflowLane(_) => {}
201 PathStep::Turn(t) | PathStep::ContraflowTurn(t) => {
202 if map.get_t(*t).turn_type == TurnType::SharedSidewalkCorner {
203 continue;
204 }
205 if let Some(demand) = all_demand.get_mut(&t.parent) {
206 demand
207 .raw
208 .push((now, map.get_i(t.parent).turn_to_movement(*t).0));
209 }
210 }
211 }
212 }
213 }
214
215 all_demand
216 }
217
218 fn count(&self, start: Time) -> Counter<MovementID> {
219 let end = start + Duration::hours(1);
220 let mut cnt = Counter::new();
221 for (t, m) in &self.raw {
222 if *t >= start && *t <= end {
223 cnt.inc(*m);
224 }
225 }
226 cnt
227 }
228}