1 use crate::app::App;
2 use crate::common::ColorNetwork;
3 use crate::helpers::ID;
4 use crate::info::{header_btns, make_tabs, Details, Tab};
5 use abstutil::{prettyprint_usize, Counter};
6 use geom::{Circle, Distance, Time};
7 use map_model::{BusRoute, BusRouteID, BusStopID, PathStep};
8 use sim::{AgentID, CarID};
9 use widgetry::{hotkey, Btn, Color, EventCtx, Key, Line, RewriteColor, Text, TextExt, Widget};
10 
stop(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusStopID) -> Vec<Widget>11 pub fn stop(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusStopID) -> Vec<Widget> {
12     let bs = app.primary.map.get_bs(id);
13     let mut rows = vec![];
14 
15     let sim = &app.primary.sim;
16 
17     rows.push(Widget::row(vec![
18         Line("Bus stop").small_heading().draw(ctx),
19         header_btns(ctx),
20     ]));
21     rows.push(Line(&bs.name).draw(ctx));
22 
23     let all_arrivals = &sim.get_analytics().bus_arrivals;
24     for r in app.primary.map.get_routes_serving_stop(id) {
25         // Full names can overlap, so include the ID
26         let label = format!("{} ({})", r.full_name, r.id);
27         rows.push(Btn::text_fg(format!("Route {}", r.short_name)).build(ctx, &label, None));
28         details.hyperlinks.insert(label, Tab::BusRoute(r.id));
29 
30         let arrivals: Vec<(Time, CarID)> = all_arrivals
31             .iter()
32             .filter(|(_, _, route, stop)| r.id == *route && id == *stop)
33             .map(|(t, car, _, _)| (*t, *car))
34             .collect();
35         let mut txt = Text::new();
36         if let Some((t, _)) = arrivals.last() {
37             // TODO Button to jump to the bus
38             txt.add(Line(format!("  Last bus arrived {} ago", sim.time() - *t)).secondary());
39         } else {
40             txt.add(Line("  No arrivals yet").secondary());
41         }
42         rows.push(txt.draw(ctx));
43     }
44 
45     let mut boardings: Counter<BusRouteID> = Counter::new();
46     let mut alightings: Counter<BusRouteID> = Counter::new();
47     if let Some(list) = app.primary.sim.get_analytics().passengers_boarding.get(&id) {
48         for (_, r, _) in list {
49             boardings.inc(*r);
50         }
51     }
52     if let Some(list) = app
53         .primary
54         .sim
55         .get_analytics()
56         .passengers_alighting
57         .get(&id)
58     {
59         for (_, r) in list {
60             alightings.inc(*r);
61         }
62     }
63     let mut txt = Text::new();
64     txt.add(Line("Total"));
65     txt.append(
66         Line(format!(
67             ": {} boardings, {} alightings",
68             prettyprint_usize(boardings.sum()),
69             prettyprint_usize(alightings.sum())
70         ))
71         .secondary(),
72     );
73     for r in app.primary.map.get_routes_serving_stop(id) {
74         txt.add(Line(format!("Route {}", r.short_name)));
75         txt.append(
76             Line(format!(
77                 ": {} boardings, {} alightings",
78                 prettyprint_usize(boardings.get(r.id)),
79                 prettyprint_usize(alightings.get(r.id))
80             ))
81             .secondary(),
82         );
83     }
84     rows.push(txt.draw(ctx));
85 
86     // Draw where the bus/train stops
87     details.zoomed.push(
88         app.cs.bus_body.alpha(0.5),
89         Circle::new(bs.driving_pos.pt(&app.primary.map), Distance::meters(2.5)).to_polygon(),
90     );
91 
92     rows
93 }
94 
bus_status(ctx: &mut EventCtx, app: &App, details: &mut Details, id: CarID) -> Vec<Widget>95 pub fn bus_status(ctx: &mut EventCtx, app: &App, details: &mut Details, id: CarID) -> Vec<Widget> {
96     let mut rows = bus_header(ctx, app, details, id, Tab::BusStatus(id));
97 
98     let route = app
99         .primary
100         .map
101         .get_br(app.primary.sim.bus_route_id(id).unwrap());
102 
103     rows.push(Btn::text_fg(format!("Serves route {}", route.short_name)).build_def(ctx, None));
104     details.hyperlinks.insert(
105         format!("Serves route {}", route.short_name),
106         Tab::BusRoute(route.id),
107     );
108 
109     rows.push(
110         Line(format!(
111             "Currently has {} passengers",
112             app.primary.sim.num_transit_passengers(id),
113         ))
114         .draw(ctx),
115     );
116 
117     rows
118 }
119 
bus_header( ctx: &mut EventCtx, app: &App, details: &mut Details, id: CarID, tab: Tab, ) -> Vec<Widget>120 fn bus_header(
121     ctx: &mut EventCtx,
122     app: &App,
123     details: &mut Details,
124     id: CarID,
125     tab: Tab,
126 ) -> Vec<Widget> {
127     let route = app.primary.sim.bus_route_id(id).unwrap();
128 
129     if let Some(pt) = app
130         .primary
131         .sim
132         .canonical_pt_for_agent(AgentID::Car(id), &app.primary.map)
133     {
134         ctx.canvas.center_on_map_pt(pt);
135     }
136 
137     let mut rows = vec![];
138     rows.push(Widget::row(vec![
139         Line(format!(
140             "{} (route {})",
141             id,
142             app.primary.map.get_br(route).short_name
143         ))
144         .small_heading()
145         .draw(ctx),
146         header_btns(ctx),
147     ]));
148     rows.push(make_tabs(
149         ctx,
150         &mut details.hyperlinks,
151         tab,
152         vec![("Status", Tab::BusStatus(id))],
153     ));
154 
155     rows
156 }
157 
route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteID) -> Vec<Widget>158 pub fn route(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BusRouteID) -> Vec<Widget> {
159     let map = &app.primary.map;
160     let route = map.get_br(id);
161     let mut rows = vec![];
162 
163     rows.push(Widget::row(vec![
164         Line(format!("Route {}", route.short_name))
165             .small_heading()
166             .draw(ctx),
167         header_btns(ctx),
168     ]));
169     rows.push(
170         Text::from(Line(&route.full_name))
171             .wrap_to_pct(ctx, 20)
172             .draw(ctx),
173     );
174 
175     if app.opts.dev {
176         rows.push(Btn::text_bg1("Open OSM relation").build(
177             ctx,
178             format!("open {}", route.osm_rel_id),
179             None,
180         ));
181     }
182 
183     let buses = app.primary.sim.status_of_buses(id, map);
184     let mut bus_locations = Vec::new();
185     if buses.is_empty() {
186         rows.push(format!("No {} running", route.plural_noun()).draw_text(ctx));
187     } else {
188         for (bus, _, _, pt) in buses {
189             rows.push(Btn::text_fg(bus.to_string()).build_def(ctx, None));
190             details
191                 .hyperlinks
192                 .insert(bus.to_string(), Tab::BusStatus(bus));
193             bus_locations.push(pt);
194         }
195     }
196 
197     let mut boardings: Counter<BusStopID> = Counter::new();
198     let mut alightings: Counter<BusStopID> = Counter::new();
199     let mut waiting: Counter<BusStopID> = Counter::new();
200     for bs in &route.stops {
201         if let Some(list) = app.primary.sim.get_analytics().passengers_boarding.get(bs) {
202             for (_, r, _) in list {
203                 if *r == id {
204                     boardings.inc(*bs);
205                 }
206             }
207         }
208         if let Some(list) = app.primary.sim.get_analytics().passengers_alighting.get(bs) {
209             for (_, r) in list {
210                 if *r == id {
211                     alightings.inc(*bs);
212                 }
213             }
214         }
215 
216         for (_, r, _, _) in app.primary.sim.get_people_waiting_at_stop(*bs) {
217             if *r == id {
218                 waiting.inc(*bs);
219             }
220         }
221     }
222 
223     rows.push(
224         Text::from_all(vec![
225             Line("Total"),
226             Line(format!(
227                 ": {} boardings, {} alightings, {} currently waiting",
228                 prettyprint_usize(boardings.sum()),
229                 prettyprint_usize(alightings.sum()),
230                 prettyprint_usize(waiting.sum())
231             ))
232             .secondary(),
233         ])
234         .draw(ctx),
235     );
236 
237     rows.push(format!("{} stops", route.stops.len()).draw_text(ctx));
238     {
239         let i = map.get_i(map.get_l(route.start).src_i);
240         let name = format!("Starts at {}", i.name(app.opts.language.as_ref(), map));
241         rows.push(Widget::row(vec![
242             Btn::svg(
243                 "system/assets/timeline/goal_pos.svg",
244                 RewriteColor::Change(Color::WHITE, app.cs.hovering),
245             )
246             .build(ctx, &name, None),
247             name.clone().draw_text(ctx),
248         ]));
249         details.warpers.insert(name, ID::Intersection(i.id));
250     }
251     for (idx, bs) in route.stops.iter().enumerate() {
252         let bs = map.get_bs(*bs);
253         let name = format!("Stop {}: {}", idx + 1, bs.name);
254         rows.push(Widget::row(vec![
255             Btn::svg(
256                 "system/assets/tools/pin.svg",
257                 RewriteColor::Change(Color::hex("#CC4121"), app.cs.hovering),
258             )
259             .build(ctx, &name, None),
260             Text::from_all(vec![
261                 Line(&bs.name),
262                 Line(format!(
263                     ": {} boardings, {} alightings, {} currently waiting",
264                     prettyprint_usize(boardings.get(bs.id)),
265                     prettyprint_usize(alightings.get(bs.id)),
266                     prettyprint_usize(waiting.get(bs.id))
267                 ))
268                 .secondary(),
269             ])
270             .draw(ctx),
271         ]));
272         details.warpers.insert(name, ID::BusStop(bs.id));
273     }
274     if let Some(l) = route.end_border {
275         let i = map.get_i(map.get_l(l).dst_i);
276         let name = format!("Ends at {}", i.name(app.opts.language.as_ref(), map));
277         rows.push(Widget::row(vec![
278             Btn::svg(
279                 "system/assets/timeline/goal_pos.svg",
280                 RewriteColor::Change(Color::WHITE, app.cs.hovering),
281             )
282             .build(ctx, &name, None),
283             name.clone().draw_text(ctx),
284         ]));
285         details.warpers.insert(name, ID::Intersection(i.id));
286     }
287 
288     // TODO Soon it'll be time to split into tabs
289     {
290         rows.push(Btn::text_fg("Edit schedule").build(
291             ctx,
292             format!("edit {}", route.id),
293             hotkey(Key::E),
294         ));
295         rows.push(describe_schedule(route).draw(ctx));
296     }
297 
298     // Draw the route, label stops, and show location of buses
299     {
300         let mut colorer = ColorNetwork::new(app);
301         for req in route.all_steps(map) {
302             for step in map.pathfind(req).unwrap().get_steps() {
303                 if let PathStep::Lane(l) = step {
304                     colorer.add_l(*l, app.cs.unzoomed_bus);
305                 }
306             }
307         }
308         details.unzoomed.append(colorer.unzoomed);
309         details.zoomed.append(colorer.zoomed);
310 
311         for pt in bus_locations {
312             details.unzoomed.push(
313                 Color::BLUE,
314                 Circle::new(pt, Distance::meters(20.0)).to_polygon(),
315             );
316             details.zoomed.push(
317                 Color::BLUE.alpha(0.5),
318                 Circle::new(pt, Distance::meters(5.0)).to_polygon(),
319             );
320         }
321 
322         for (idx, bs) in route.stops.iter().enumerate() {
323             let bs = map.get_bs(*bs);
324             details.unzoomed.append(
325                 Text::from(Line(format!("{}) {}", idx + 1, bs.name)))
326                     .with_bg()
327                     .render_to_batch(ctx.prerender)
328                     .centered_on(bs.sidewalk_pos.pt(map)),
329             );
330             details.zoomed.append(
331                 Text::from(Line(format!("{}) {}", idx + 1, bs.name)))
332                     .with_bg()
333                     .render_to_batch(ctx.prerender)
334                     .scale(0.1)
335                     .centered_on(bs.sidewalk_pos.pt(map)),
336             );
337         }
338     }
339 
340     rows
341 }
342 
343 // TODO Unit test
describe_schedule(route: &BusRoute) -> Text344 fn describe_schedule(route: &BusRoute) -> Text {
345     let mut txt = Text::new();
346     txt.add(Line(format!(
347         "{} {}s run this route daily",
348         route.spawn_times.len(),
349         route.plural_noun()
350     )));
351 
352     if false {
353         // Compress the times
354         let mut start = route.spawn_times[0];
355         let mut last = None;
356         let mut dt = None;
357         for t in route.spawn_times.iter().skip(1) {
358             if let Some(l) = last {
359                 let new_dt = *t - l;
360                 if Some(new_dt) == dt {
361                     last = Some(*t);
362                 } else {
363                     txt.add(Line(format!(
364                         "Every {} from {} to {}",
365                         dt.unwrap(),
366                         start.ampm_tostring(),
367                         l.ampm_tostring()
368                     )));
369                     start = l;
370                     last = Some(*t);
371                     dt = Some(new_dt);
372                 }
373             } else {
374                 last = Some(*t);
375                 dt = Some(*t - start);
376             }
377         }
378         // Handle end
379         txt.add(Line(format!(
380             "Every {} from {} to {}",
381             dt.unwrap(),
382             start.ampm_tostring(),
383             last.unwrap().ampm_tostring()
384         )));
385     } else {
386         // Just list the times
387         for t in &route.spawn_times {
388             txt.add(Line(t.ampm_tostring()));
389         }
390     }
391     txt
392 }
393