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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::collections::HashMap;
use abstutil::MultiMap;
use geom::{Duration, Polygon};
use map_gui::tools::{amenity_type, Grid};
use map_model::{connectivity, BuildingID, Map, Path, PathConstraints, PathRequest};
use widgetry::{Color, Drawable, EventCtx, GeomBatch};
use crate::App;
pub struct Isochrone {
pub start: BuildingID,
pub constraints: PathConstraints,
pub draw: Drawable,
pub time_to_reach_building: HashMap<BuildingID, Duration>,
pub amenities_reachable: MultiMap<&'static str, BuildingID>,
}
impl Isochrone {
pub fn new(
ctx: &mut EventCtx,
app: &App,
start: BuildingID,
constraints: PathConstraints,
) -> Isochrone {
let time_to_reach_building =
connectivity::all_costs_from(&app.map, start, Duration::minutes(15), constraints);
let draw = draw_isochrone(app, &time_to_reach_building).upload(ctx);
let mut amenities_reachable = MultiMap::new();
for b in time_to_reach_building.keys() {
let bldg = app.map.get_b(*b);
for amenity in &bldg.amenities {
if let Some(category) = amenity_type(&amenity.amenity_type) {
amenities_reachable.insert(category, bldg.id);
}
}
}
Isochrone {
start,
constraints,
draw,
time_to_reach_building,
amenities_reachable,
}
}
pub fn path_to(&self, map: &Map, to: BuildingID) -> Option<Path> {
let req = PathRequest::between_buildings(map, self.start, to, self.constraints)?;
map.pathfind(req)
}
}
fn draw_isochrone(app: &App, time_to_reach_building: &HashMap<BuildingID, Duration>) -> GeomBatch {
let bounds = app.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 time_to_reach_building {
let pt = app.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_seconds();
}
let thresholds = vec![
0.1,
Duration::minutes(5).inner_seconds(),
Duration::minutes(10).inner_seconds(),
Duration::minutes(15).inner_seconds(),
];
let colors = vec![
Color::GREEN.alpha(0.5),
Color::ORANGE.alpha(0.5),
Color::RED.alpha(0.5),
];
let smooth = false;
let c = contour::ContourBuilder::new(grid.width as u32, grid.height as u32, smooth);
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
}