1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use crate::app::App;
use crate::common::heatmap::Grid;
use crate::game::{State, Transition};
use ezgui::{
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
Line, Outcome, VerticalAlignment, Widget,
};
use geom::{Distance, Polygon};
use map_model::{connectivity, BuildingID};
pub struct IsochroneViewer {
composite: Composite,
draw: Drawable,
}
impl IsochroneViewer {
pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Box<dyn State> {
let draw = make_isochrone(ctx, app, start);
Box::new(IsochroneViewer {
composite: Composite::new(Widget::col(vec![
Widget::row(vec![
Line("Isochrone").small_heading().draw(ctx),
Btn::text_fg("X")
.build(ctx, "close", hotkey(Key::Escape))
.align_right(),
]),
]))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx),
draw,
})
}
}
impl State for IsochroneViewer {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
ctx.canvas_movement();
match self.composite.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"close" => {
return Transition::Pop;
}
_ => unreachable!(),
},
_ => {}
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, _: &App) {
g.redraw(&self.draw);
self.composite.draw(g);
}
}
fn make_isochrone(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Drawable {
let bounds = app.primary.map.get_bounds();
let resolution_m = 100.0;
let mut grid: Grid<f64> = Grid::new(
(bounds.width() / resolution_m).ceil() as usize,
(bounds.height() / resolution_m).ceil() as usize,
0.0,
);
for (b, cost) in connectivity::all_costs_from(&app.primary.map, start) {
let pt = app.primary.map.get_b(b).polygon.center();
let idx = grid.idx(
((pt.x() - bounds.min_x) / resolution_m) as usize,
((pt.y() - bounds.min_y) / resolution_m) as usize,
);
grid.data[idx] = cost.inner_meters();
}
let thresholds = vec![
0.1,
Distance::miles(0.5).inner_meters(),
Distance::miles(3.0).inner_meters(),
Distance::miles(6.0).inner_meters(),
];
let colors = vec![
Color::BLACK.alpha(0.5),
Color::GREEN.alpha(0.5),
Color::BLUE.alpha(0.5),
Color::RED.alpha(0.5),
];
let c = contour::ContourBuilder::new(grid.width as u32, grid.height as u32, false);
let mut batch = GeomBatch::new();
for (feature, color) in c
.contours(&grid.data, &thresholds)
.unwrap()
.into_iter()
.zip(colors)
{
match feature.geometry.unwrap().value {
geojson::Value::MultiPolygon(polygons) => {
for p in polygons {
batch.push(color, Polygon::from_geojson(&p).scale(resolution_m));
}
}
_ => unreachable!(),
}
}
batch.upload(ctx)
}