1use std::collections::HashSet;
2
3use abstutil::prettyprint_usize;
4use geom::{Circle, Distance, Pt2D, Time};
5use map_gui::tools::{make_heatmap, HeatmapOptions};
6use sim::PersonState;
7use widgetry::mapspace::ToggleZoomed;
8use widgetry::{Color, EventCtx, GfxCtx, Image, Line, Outcome, Panel, Toggle, Widget};
9
10use crate::app::App;
11use crate::layer::{header, Layer, LayerOutcome, PANEL_PLACEMENT};
12
13pub struct PopulationMap {
16 time: Time,
17 opts: Options,
18 draw: ToggleZoomed,
19 panel: Panel,
20}
21
22impl Layer for PopulationMap {
23 fn name(&self) -> Option<&'static str> {
24 Some("population map")
25 }
26 fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<LayerOutcome> {
27 if app.primary.sim.time() != self.time {
28 let mut new = PopulationMap::new(ctx, app, self.opts.clone());
29 new.panel.restore(ctx, &self.panel);
30 *self = new;
31 }
32
33 match self.panel.event(ctx) {
34 Outcome::Clicked(x) => match x.as_ref() {
35 "close" => {
36 return Some(LayerOutcome::Close);
37 }
38 _ => unreachable!(),
39 },
40 _ => {
41 let new_opts = self.options();
42 if self.opts != new_opts {
43 *self = PopulationMap::new(ctx, app, new_opts);
44 }
45 }
46 }
47 None
48 }
49 fn draw(&self, g: &mut GfxCtx, _: &App) {
50 self.panel.draw(g);
51 self.draw.draw(g);
52 }
53 fn draw_minimap(&self, g: &mut GfxCtx) {
54 g.redraw(&self.draw.unzoomed);
55 }
56}
57
58impl PopulationMap {
59 pub fn new(ctx: &mut EventCtx, app: &App, opts: Options) -> PopulationMap {
60 let mut pts = Vec::new();
61 for a in app
63 .primary
64 .sim
65 .get_unzoomed_agents(&app.primary.map)
66 .into_iter()
67 .chain(
68 app.primary
69 .sim
70 .get_unzoomed_transit_riders(&app.primary.map),
71 )
72 {
73 if a.person.is_some() {
74 pts.push(a.pos);
75 }
76 }
77
78 let mut seen_bldgs = HashSet::new();
82 let mut repeat_pts = Vec::new();
83 for person in app.primary.sim.get_all_people() {
84 match person.state {
85 PersonState::Trip(_) => {}
87 PersonState::Inside(b) => {
88 let pt = app.primary.map.get_b(b).polygon.center();
89 if seen_bldgs.contains(&b) {
90 repeat_pts.push(pt);
91 } else {
92 seen_bldgs.insert(b);
93 pts.push(pt);
94 }
95 }
96 PersonState::OffMap => {}
97 }
98 }
99
100 let mut draw = ToggleZoomed::builder();
101 let legend = if let Some(ref o) = opts.heatmap {
102 pts.extend(repeat_pts);
103 Some(make_heatmap(
104 ctx,
105 &mut draw.unzoomed,
106 app.primary.map.get_bounds(),
107 pts,
108 o,
109 ))
110 } else {
111 let circle = Circle::new(Pt2D::new(0.0, 0.0), Distance::meters(10.0)).to_polygon();
113 for pt in pts {
114 draw.unzoomed
115 .push(Color::RED.alpha(0.8), circle.translate(pt.x(), pt.y()));
116 }
117 None
118 };
119 let controls = make_controls(ctx, app, &opts, legend);
120 PopulationMap {
121 time: app.primary.sim.time(),
122 opts,
123 draw: draw.build(ctx),
124 panel: controls,
125 }
126 }
127
128 fn options(&self) -> Options {
129 let heatmap = if self.panel.is_checked("Show heatmap") {
130 Some(HeatmapOptions::from_controls(&self.panel))
131 } else {
132 None
133 };
134 Options { heatmap }
135 }
136}
137
138#[derive(Clone, PartialEq)]
139pub struct Options {
140 pub heatmap: Option<HeatmapOptions>,
142}
143
144fn make_controls(ctx: &mut EventCtx, app: &App, opts: &Options, legend: Option<Widget>) -> Panel {
145 let (total_ppl, ppl_in_bldg, ppl_off_map) = app.primary.sim.num_ppl();
146
147 let mut col = vec![
148 header(
149 ctx,
150 &format!("Population: {}", prettyprint_usize(total_ppl)),
151 ),
152 Widget::row(vec![
153 Widget::row(vec![
154 Image::from_path("system/assets/tools/home.svg").into_widget(ctx),
155 Line(prettyprint_usize(ppl_in_bldg))
156 .small()
157 .into_widget(ctx),
158 ]),
159 Line(format!("Off-map: {}", prettyprint_usize(ppl_off_map)))
160 .small()
161 .into_widget(ctx),
162 ])
163 .centered(),
164 ];
165
166 col.push(Toggle::switch(
167 ctx,
168 "Show heatmap",
169 None,
170 opts.heatmap.is_some(),
171 ));
172 if let Some(ref o) = opts.heatmap {
173 col.extend(o.to_controls(ctx, legend.unwrap()));
174 }
175
176 Panel::new_builder(Widget::col(col))
177 .aligned_pair(PANEL_PLACEMENT)
178 .build(ctx)
179}