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