1 use crate::app::App;
2 use crate::common::{ColorLegend, ColorNetwork, ColorScale, DivergingScale};
3 use crate::layer::{Layer, LayerOutcome};
4 use abstutil::Counter;
5 use geom::{Distance, Duration, Polygon, Time};
6 use map_model::{IntersectionID, Map, Traversable};
7 use maplit::btreeset;
8 use std::collections::BTreeSet;
9 use widgetry::{
10 hotkey, Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
11 Line, Outcome, Panel, Text, TextExt, VerticalAlignment, Widget,
12 };
13
14 pub struct Backpressure {
15 time: Time,
16 unzoomed: Drawable,
17 zoomed: Drawable,
18 panel: Panel,
19 }
20
21 impl Layer for Backpressure {
name(&self) -> Option<&'static str>22 fn name(&self) -> Option<&'static str> {
23 Some("backpressure")
24 }
event( &mut self, ctx: &mut EventCtx, app: &mut App, minimap: &Panel, ) -> Option<LayerOutcome>25 fn event(
26 &mut self,
27 ctx: &mut EventCtx,
28 app: &mut App,
29 minimap: &Panel,
30 ) -> Option<LayerOutcome> {
31 if app.primary.sim.time() != self.time {
32 *self = Backpressure::new(ctx, app);
33 }
34
35 Layer::simple_event(ctx, minimap, &mut self.panel)
36 }
draw(&self, g: &mut GfxCtx, app: &App)37 fn draw(&self, g: &mut GfxCtx, app: &App) {
38 self.panel.draw(g);
39 if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
40 g.redraw(&self.unzoomed);
41 } else {
42 g.redraw(&self.zoomed);
43 }
44 }
draw_minimap(&self, g: &mut GfxCtx)45 fn draw_minimap(&self, g: &mut GfxCtx) {
46 g.redraw(&self.unzoomed);
47 }
48 }
49
50 impl Backpressure {
new(ctx: &mut EventCtx, app: &App) -> Backpressure51 pub fn new(ctx: &mut EventCtx, app: &App) -> Backpressure {
52 let mut cnt_per_r = Counter::new();
53 let mut cnt_per_i = Counter::new();
54 for path in app.primary.sim.get_all_driving_paths() {
55 for step in path.get_steps() {
56 match step.as_traversable() {
57 Traversable::Lane(l) => {
58 cnt_per_r.inc(app.primary.map.get_l(l).parent);
59 }
60 Traversable::Turn(t) => {
61 cnt_per_i.inc(t.parent);
62 }
63 }
64 }
65 }
66
67 let panel = Panel::new(Widget::col(vec![
68 Widget::row(vec![
69 Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
70 "Backpressure".draw_text(ctx),
71 Btn::plaintext("X")
72 .build(ctx, "close", hotkey(Key::Escape))
73 .align_right(),
74 ]),
75 Text::from(
76 Line("This counts all active trips passing through a road in the future")
77 .secondary(),
78 )
79 .wrap_to_pct(ctx, 15)
80 .draw(ctx),
81 ColorLegend::gradient(
82 ctx,
83 &app.cs.good_to_bad_red,
84 vec!["lowest count", "highest"],
85 ),
86 ]))
87 .aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
88 .build(ctx);
89
90 let mut colorer = ColorNetwork::new(app);
91 colorer.ranked_roads(cnt_per_r, &app.cs.good_to_bad_red);
92 colorer.ranked_intersections(cnt_per_i, &app.cs.good_to_bad_red);
93 let (unzoomed, zoomed) = colorer.build(ctx);
94
95 Backpressure {
96 time: app.primary.sim.time(),
97 unzoomed,
98 zoomed,
99 panel,
100 }
101 }
102 }
103
104 // TODO Filter by mode
105 pub struct Throughput {
106 time: Time,
107 compare: bool,
108 unzoomed: Drawable,
109 zoomed: Drawable,
110 panel: Panel,
111 }
112
113 impl Layer for Throughput {
name(&self) -> Option<&'static str>114 fn name(&self) -> Option<&'static str> {
115 Some("throughput")
116 }
event( &mut self, ctx: &mut EventCtx, app: &mut App, minimap: &Panel, ) -> Option<LayerOutcome>117 fn event(
118 &mut self,
119 ctx: &mut EventCtx,
120 app: &mut App,
121 minimap: &Panel,
122 ) -> Option<LayerOutcome> {
123 if app.primary.sim.time() != self.time {
124 *self = Throughput::new(ctx, app, self.compare);
125 }
126
127 self.panel.align_above(ctx, minimap);
128 match self.panel.event(ctx) {
129 Outcome::Clicked(x) => match x.as_ref() {
130 "close" => {
131 return Some(LayerOutcome::Close);
132 }
133 _ => unreachable!(),
134 },
135 Outcome::Changed => {
136 *self = Throughput::new(
137 ctx,
138 app,
139 self.panel
140 .maybe_is_checked("Compare before edits")
141 .unwrap_or(false),
142 );
143 self.panel.align_above(ctx, minimap);
144 }
145 _ => {}
146 }
147 None
148 }
draw(&self, g: &mut GfxCtx, app: &App)149 fn draw(&self, g: &mut GfxCtx, app: &App) {
150 self.panel.draw(g);
151 if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
152 g.redraw(&self.unzoomed);
153 } else {
154 g.redraw(&self.zoomed);
155 }
156 }
draw_minimap(&self, g: &mut GfxCtx)157 fn draw_minimap(&self, g: &mut GfxCtx) {
158 g.redraw(&self.unzoomed);
159 }
160 }
161
162 impl Throughput {
new(ctx: &mut EventCtx, app: &App, compare: bool) -> Throughput163 pub fn new(ctx: &mut EventCtx, app: &App, compare: bool) -> Throughput {
164 if compare {
165 return Throughput::compare_throughput(ctx, app);
166 }
167 let panel = Panel::new(Widget::col(vec![
168 Widget::row(vec![
169 Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
170 "Throughput".draw_text(ctx),
171 Btn::plaintext("X")
172 .build(ctx, "close", hotkey(Key::Escape))
173 .align_right(),
174 ]),
175 Text::from(Line("This counts all people crossing since midnight").secondary())
176 .wrap_to_pct(ctx, 15)
177 .draw(ctx),
178 if app.has_prebaked().is_some() {
179 Checkbox::switch(ctx, "Compare before edits", None, false)
180 } else {
181 Widget::nothing()
182 },
183 ColorLegend::gradient(
184 ctx,
185 &app.cs.good_to_bad_red,
186 vec!["lowest count", "highest"],
187 ),
188 ]))
189 .aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
190 .build(ctx);
191
192 let mut colorer = ColorNetwork::new(app);
193 let stats = &app.primary.sim.get_analytics();
194 colorer.ranked_roads(
195 stats.road_thruput.all_total_counts(),
196 &app.cs.good_to_bad_red,
197 );
198 colorer.ranked_intersections(
199 stats.intersection_thruput.all_total_counts(),
200 &app.cs.good_to_bad_red,
201 );
202 let (unzoomed, zoomed) = colorer.build(ctx);
203
204 Throughput {
205 time: app.primary.sim.time(),
206 compare: false,
207 unzoomed,
208 zoomed,
209 panel,
210 }
211 }
212
compare_throughput(ctx: &mut EventCtx, app: &App) -> Throughput213 fn compare_throughput(ctx: &mut EventCtx, app: &App) -> Throughput {
214 let after = app.primary.sim.get_analytics();
215 let before = app.prebaked();
216 let hour = app.primary.sim.time().get_hours();
217
218 let mut after_road = Counter::new();
219 let mut before_road = Counter::new();
220 {
221 for ((r, _, _), count) in &after.road_thruput.counts {
222 after_road.add(*r, *count);
223 }
224 // TODO ew. lerp?
225 for ((r, _, hr), count) in &before.road_thruput.counts {
226 if *hr <= hour {
227 before_road.add(*r, *count);
228 }
229 }
230 }
231 let mut after_intersection = Counter::new();
232 let mut before_intersection = Counter::new();
233 {
234 for ((i, _, _), count) in &after.intersection_thruput.counts {
235 after_intersection.add(*i, *count);
236 }
237 // TODO ew. lerp?
238 for ((i, _, hr), count) in &before.intersection_thruput.counts {
239 if *hr <= hour {
240 before_intersection.add(*i, *count);
241 }
242 }
243 }
244
245 let mut colorer = ColorNetwork::new(app);
246
247 let scale = DivergingScale::new(Color::hex("#5D9630"), Color::WHITE, Color::hex("#A32015"))
248 .range(0.0, 2.0)
249 .ignore(0.7, 1.3);
250
251 for (r, before, after) in before_road.compare(after_road) {
252 if let Some(c) = scale.eval((after as f64) / (before as f64)) {
253 colorer.add_r(r, c);
254 }
255 }
256 for (i, before, after) in before_intersection.compare(after_intersection) {
257 if let Some(c) = scale.eval((after as f64) / (before as f64)) {
258 colorer.add_i(i, c);
259 }
260 }
261
262 let panel = Panel::new(Widget::col(vec![
263 Widget::row(vec![
264 Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
265 "Relative Throughput".draw_text(ctx),
266 Btn::plaintext("X")
267 .build(ctx, "close", hotkey(Key::Escape))
268 .align_right(),
269 ]),
270 Checkbox::switch(ctx, "Compare before edits", None, true),
271 scale.make_legend(ctx, vec!["less traffic", "same", "more"]),
272 ]))
273 .aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
274 .build(ctx);
275 let (unzoomed, zoomed) = colorer.build(ctx);
276
277 Throughput {
278 time: app.primary.sim.time(),
279 compare: true,
280 unzoomed,
281 zoomed,
282 panel,
283 }
284 }
285 }
286
287 pub struct Delay {
288 time: Time,
289 compare: bool,
290 unzoomed: Drawable,
291 zoomed: Drawable,
292 panel: Panel,
293 }
294
295 impl Layer for Delay {
name(&self) -> Option<&'static str>296 fn name(&self) -> Option<&'static str> {
297 Some("delay")
298 }
event( &mut self, ctx: &mut EventCtx, app: &mut App, minimap: &Panel, ) -> Option<LayerOutcome>299 fn event(
300 &mut self,
301 ctx: &mut EventCtx,
302 app: &mut App,
303 minimap: &Panel,
304 ) -> Option<LayerOutcome> {
305 if app.primary.sim.time() != self.time {
306 *self = Delay::new(ctx, app, self.compare);
307 }
308
309 self.panel.align_above(ctx, minimap);
310 match self.panel.event(ctx) {
311 Outcome::Clicked(x) => match x.as_ref() {
312 "close" => {
313 return Some(LayerOutcome::Close);
314 }
315 _ => unreachable!(),
316 },
317 Outcome::Changed => {
318 *self = Delay::new(
319 ctx,
320 app,
321 self.panel
322 .maybe_is_checked("Compare before edits")
323 .unwrap_or(false),
324 );
325 self.panel.align_above(ctx, minimap);
326 }
327 _ => {}
328 }
329 None
330 }
draw(&self, g: &mut GfxCtx, app: &App)331 fn draw(&self, g: &mut GfxCtx, app: &App) {
332 self.panel.draw(g);
333 if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
334 g.redraw(&self.unzoomed);
335 } else {
336 g.redraw(&self.zoomed);
337 }
338 }
draw_minimap(&self, g: &mut GfxCtx)339 fn draw_minimap(&self, g: &mut GfxCtx) {
340 g.redraw(&self.unzoomed);
341 }
342 }
343
344 impl Delay {
new(ctx: &mut EventCtx, app: &App, compare: bool) -> Delay345 pub fn new(ctx: &mut EventCtx, app: &App, compare: bool) -> Delay {
346 if compare {
347 return Delay::compare_delay(ctx, app);
348 }
349
350 let mut colorer = ColorNetwork::new(app);
351
352 let (per_road, per_intersection) = app.primary.sim.worst_delay(&app.primary.map);
353 for (r, d) in per_road {
354 if d < Duration::minutes(1) {
355 continue;
356 }
357 let color = app
358 .cs
359 .good_to_bad_red
360 .eval(((d - Duration::minutes(1)) / Duration::minutes(15)).min(1.0));
361 colorer.add_r(r, color);
362 }
363 for (i, d) in per_intersection {
364 if d < Duration::minutes(1) {
365 continue;
366 }
367 let color = app
368 .cs
369 .good_to_bad_red
370 .eval(((d - Duration::minutes(1)) / Duration::minutes(15)).min(1.0));
371 colorer.add_i(i, color);
372 }
373
374 let panel = Panel::new(Widget::col(vec![
375 Widget::row(vec![
376 Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
377 "Delay (minutes)".draw_text(ctx),
378 Btn::plaintext("X")
379 .build(ctx, "close", hotkey(Key::Escape))
380 .align_right(),
381 ]),
382 if app.has_prebaked().is_some() {
383 Checkbox::switch(ctx, "Compare before edits", None, false)
384 } else {
385 Widget::nothing()
386 },
387 ColorLegend::gradient(ctx, &app.cs.good_to_bad_red, vec!["1", "5", "10", "15+"]),
388 ]))
389 .aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
390 .build(ctx);
391 let (unzoomed, zoomed) = colorer.build(ctx);
392
393 Delay {
394 time: app.primary.sim.time(),
395 compare: false,
396 unzoomed,
397 zoomed,
398 panel,
399 }
400 }
401
402 // TODO Needs work.
compare_delay(ctx: &mut EventCtx, app: &App) -> Delay403 fn compare_delay(ctx: &mut EventCtx, app: &App) -> Delay {
404 let mut colorer = ColorNetwork::new(app);
405 let red = Color::hex("#A32015");
406 let green = Color::hex("#5D9630");
407
408 let results = app
409 .primary
410 .sim
411 .get_analytics()
412 .compare_delay(app.primary.sim.time(), app.prebaked());
413 if !results.is_empty() {
414 let fastest = results.iter().min_by_key(|(_, dt)| *dt).unwrap().1;
415 let slowest = results.iter().max_by_key(|(_, dt)| *dt).unwrap().1;
416
417 for (i, dt) in results {
418 let color = if dt < Duration::ZERO {
419 green.lerp(Color::WHITE, 1.0 - (dt / fastest))
420 } else {
421 Color::WHITE.lerp(red, dt / slowest)
422 };
423 colorer.add_i(i, color);
424 }
425 }
426
427 let panel = Panel::new(Widget::col(vec![
428 Widget::row(vec![
429 Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
430 "Delay".draw_text(ctx),
431 Btn::plaintext("X")
432 .build(ctx, "close", hotkey(Key::Escape))
433 .align_right(),
434 ]),
435 Checkbox::switch(ctx, "Compare before edits", None, true),
436 ColorLegend::gradient(
437 ctx,
438 &ColorScale(vec![green, Color::WHITE, red]),
439 vec!["faster", "same", "slower"],
440 ),
441 ]))
442 .aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
443 .build(ctx);
444 let (unzoomed, zoomed) = colorer.build(ctx);
445
446 Delay {
447 time: app.primary.sim.time(),
448 compare: true,
449 unzoomed,
450 zoomed,
451 panel,
452 }
453 }
454 }
455
456 pub struct TrafficJams {
457 time: Time,
458 unzoomed: Drawable,
459 zoomed: Drawable,
460 panel: Panel,
461 }
462
463 impl Layer for TrafficJams {
name(&self) -> Option<&'static str>464 fn name(&self) -> Option<&'static str> {
465 Some("traffic jams")
466 }
event( &mut self, ctx: &mut EventCtx, app: &mut App, minimap: &Panel, ) -> Option<LayerOutcome>467 fn event(
468 &mut self,
469 ctx: &mut EventCtx,
470 app: &mut App,
471 minimap: &Panel,
472 ) -> Option<LayerOutcome> {
473 if app.primary.sim.time() != self.time {
474 *self = TrafficJams::new(ctx, app);
475 }
476
477 Layer::simple_event(ctx, minimap, &mut self.panel)
478 }
draw(&self, g: &mut GfxCtx, app: &App)479 fn draw(&self, g: &mut GfxCtx, app: &App) {
480 self.panel.draw(g);
481 if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
482 g.redraw(&self.unzoomed);
483 } else {
484 g.redraw(&self.zoomed);
485 }
486 }
draw_minimap(&self, g: &mut GfxCtx)487 fn draw_minimap(&self, g: &mut GfxCtx) {
488 g.redraw(&self.unzoomed);
489 }
490 }
491
492 impl TrafficJams {
new(ctx: &mut EventCtx, app: &App) -> TrafficJams493 pub fn new(ctx: &mut EventCtx, app: &App) -> TrafficJams {
494 // TODO Use cached delayed_intersections?
495 let mut unzoomed = GeomBatch::new();
496 unzoomed.push(
497 app.cs.fade_map_dark,
498 app.primary.map.get_boundary_polygon().clone(),
499 );
500 let mut zoomed = GeomBatch::new();
501 let mut cnt = 0;
502 for (epicenter, boundary) in cluster_jams(
503 &app.primary.map,
504 app.primary.sim.delayed_intersections(Duration::minutes(5)),
505 ) {
506 cnt += 1;
507 unzoomed.push(
508 Color::RED,
509 boundary.to_outline(Distance::meters(5.0)).unwrap(),
510 );
511 unzoomed.push(Color::RED.alpha(0.5), boundary.clone());
512 unzoomed.push(Color::WHITE, epicenter.clone());
513
514 zoomed.push(
515 Color::RED.alpha(0.4),
516 boundary.to_outline(Distance::meters(5.0)).unwrap(),
517 );
518 zoomed.push(Color::RED.alpha(0.3), boundary);
519 zoomed.push(Color::WHITE.alpha(0.4), epicenter);
520 }
521
522 let panel = Panel::new(Widget::col(vec![
523 Widget::row(vec![
524 Widget::draw_svg(ctx, "system/assets/tools/layers.svg"),
525 "Traffic jams".draw_text(ctx),
526 Btn::plaintext("X")
527 .build(ctx, "close", hotkey(Key::Escape))
528 .align_right(),
529 ]),
530 Text::from(
531 Line("A jam starts when delay exceeds 5 mins, then spreads out").secondary(),
532 )
533 .wrap_to_pct(ctx, 15)
534 .draw(ctx),
535 format!("{} jams detected", cnt).draw_text(ctx),
536 ]))
537 .aligned(HorizontalAlignment::Right, VerticalAlignment::Center)
538 .build(ctx);
539
540 TrafficJams {
541 time: app.primary.sim.time(),
542 unzoomed: ctx.upload(unzoomed),
543 zoomed: ctx.upload(zoomed),
544 panel,
545 }
546 }
547 }
548
549 struct Jam {
550 epicenter: IntersectionID,
551 members: BTreeSet<IntersectionID>,
552 }
553
554 // (Epicenter, entire shape)
cluster_jams(map: &Map, problems: Vec<(IntersectionID, Time)>) -> Vec<(Polygon, Polygon)>555 fn cluster_jams(map: &Map, problems: Vec<(IntersectionID, Time)>) -> Vec<(Polygon, Polygon)> {
556 let mut jams: Vec<Jam> = Vec::new();
557 // The delay itself doesn't matter, as long as they're sorted.
558 for (i, _) in problems {
559 // Is this connected to an existing problem?
560 if let Some(ref mut jam) = jams.iter_mut().find(|j| j.adjacent_to(map, i)) {
561 jam.members.insert(i);
562 } else {
563 jams.push(Jam {
564 epicenter: i,
565 members: btreeset! { i },
566 });
567 }
568 }
569
570 jams.into_iter()
571 .map(|jam| {
572 (
573 map.get_i(jam.epicenter).polygon.clone(),
574 Polygon::convex_hull(jam.all_polygons(map)),
575 )
576 })
577 .collect()
578 }
579
580 impl Jam {
adjacent_to(&self, map: &Map, i: IntersectionID) -> bool581 fn adjacent_to(&self, map: &Map, i: IntersectionID) -> bool {
582 for r in &map.get_i(i).roads {
583 let r = map.get_r(*r);
584 if self.members.contains(&r.src_i) || self.members.contains(&r.dst_i) {
585 return true;
586 }
587 }
588 false
589 }
590
all_polygons(self, map: &Map) -> Vec<Polygon>591 fn all_polygons(self, map: &Map) -> Vec<Polygon> {
592 let mut polygons = Vec::new();
593 for i in self.members {
594 polygons.push(map.get_i(i).polygon.clone());
595 }
596 polygons
597 }
598 }
599