game/sandbox/dashboards/
traffic_signals.rs

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    // Unsorted
166    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            // TODO For every step, increase 'now' by the best-case time to cross that step.
198            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}