1use std::collections::{HashMap, HashSet};
2
3use abstutil::MultiMap;
4use connectivity::Spot;
5use geom::Duration;
6use map_gui::tools::draw_isochrone;
7use map_model::{
8 connectivity, AmenityType, BuildingID, BuildingType, IntersectionID, LaneType, Map, Path,
9 PathConstraints, PathRequest,
10};
11use widgetry::mapspace::{ToggleZoomed, ToggleZoomedBuilder};
12use widgetry::{Color, EventCtx};
13
14use crate::App;
15
16pub struct Isochrone {
18 pub start: Vec<BuildingID>,
20 pub options: Options,
22 pub draw: ToggleZoomed,
24 pub thresholds: Vec<f64>,
26 pub colors: Vec<Color>,
28 pub time_to_reach_building: HashMap<BuildingID, Duration>,
30 pub amenities_reachable: MultiMap<AmenityType, BuildingID>,
32 pub population: usize,
35 pub onstreet_parking_spots: usize,
37}
38
39#[derive(Clone)]
40pub struct Options {
41 pub movement: MovementOptions,
42 pub thresholds: Vec<(Duration, Color)>,
43}
44
45impl Options {
46 pub fn default_thresholds() -> Vec<(Duration, Color)> {
47 vec![
48 (Duration::minutes(5), Color::GREEN.alpha(0.5)),
49 (Duration::minutes(10), Color::ORANGE.alpha(0.5)),
50 (Duration::minutes(15), Color::RED.alpha(0.5)),
51 ]
52 }
53}
54
55#[derive(Clone)]
57pub enum MovementOptions {
58 Walking(connectivity::WalkingOptions),
59 Biking,
60}
61
62impl MovementOptions {
63 pub fn times_from(self, map: &Map, starts: Vec<Spot>) -> HashMap<BuildingID, Duration> {
66 match self {
67 MovementOptions::Walking(opts) => {
68 connectivity::all_walking_costs_from(map, starts, Duration::minutes(15), opts)
69 }
70 MovementOptions::Biking => connectivity::all_vehicle_costs_from(
71 map,
72 starts,
73 Duration::minutes(15),
74 PathConstraints::Bike,
75 ),
76 }
77 }
78}
79
80impl Isochrone {
81 pub fn new(
82 ctx: &mut EventCtx,
83 app: &App,
84 start: Vec<BuildingID>,
85 options: Options,
86 ) -> Isochrone {
87 let spot_starts = start.iter().map(|b_id| Spot::Building(*b_id)).collect();
88 let time_to_reach_building = options.movement.clone().times_from(&app.map, spot_starts);
89
90 let mut amenities_reachable = MultiMap::new();
91 let mut population = 0;
92 let mut all_roads = HashSet::new();
93 for b in time_to_reach_building.keys() {
94 let bldg = app.map.get_b(*b);
95 for amenity in &bldg.amenities {
96 if let Some(category) = AmenityType::categorize(&amenity.amenity_type) {
97 amenities_reachable.insert(category, bldg.id);
98 }
99 }
100 match bldg.bldg_type {
101 BuildingType::Residential { num_residents, .. }
102 | BuildingType::ResidentialCommercial(num_residents, _) => {
103 population += num_residents;
104 }
105 _ => {}
106 }
107 all_roads.insert(bldg.sidewalk_pos.lane().road);
108 }
109
110 let mut onstreet_parking_spots = 0;
111 for r in all_roads {
112 let r = app.map.get_r(r);
113 for l in &r.lanes {
114 if l.lane_type == LaneType::Parking {
115 onstreet_parking_spots += l.number_parking_spots(app.map.get_config());
116 }
117 }
118 }
119
120 let mut thresholds = vec![0.1];
123 let mut colors = Vec::new();
124 for (threshold, color) in &options.thresholds {
125 thresholds.push(threshold.inner_seconds());
126 colors.push(*color);
127 }
128
129 let mut i = Isochrone {
130 start,
131 options,
132 draw: ToggleZoomed::empty(ctx),
133 thresholds,
134 colors,
135 time_to_reach_building,
136 amenities_reachable,
137 population,
138 onstreet_parking_spots,
139 };
140
141 i.draw = ToggleZoomedBuilder::from(draw_isochrone(
142 &app.map,
143 &i.time_to_reach_building,
144 &i.thresholds,
145 &i.colors,
146 ))
147 .build(ctx);
148 i
149 }
150
151 pub fn path_to(&self, map: &Map, to: BuildingID) -> Option<Path> {
152 if !self.time_to_reach_building.contains_key(&to) {
154 return None;
155 }
156
157 let constraints = match self.options.movement {
158 MovementOptions::Walking(_) => PathConstraints::Pedestrian,
159 MovementOptions::Biking => PathConstraints::Bike,
160 };
161
162 let all_paths = self.start.iter().filter_map(|b_id| {
163 PathRequest::between_buildings(map, *b_id, to, constraints)
164 .and_then(|req| map.pathfind(req).ok())
165 });
166
167 all_paths.min_by_key(|path| path.total_length())
168 }
169}
170
171pub struct BorderIsochrone {
173 pub start: Vec<IntersectionID>,
175 pub options: Options,
177 pub draw: ToggleZoomed,
179 pub thresholds: Vec<f64>,
181 pub colors: Vec<Color>,
183 pub time_to_reach_building: HashMap<BuildingID, Duration>,
185}
186
187impl BorderIsochrone {
188 pub fn new(
189 ctx: &mut EventCtx,
190 app: &App,
191 start: Vec<IntersectionID>,
192 options: Options,
193 ) -> BorderIsochrone {
194 let spot_starts = start.iter().map(|i_id| Spot::Border(*i_id)).collect();
195 let time_to_reach_building = options.movement.clone().times_from(&app.map, spot_starts);
196
197 let thresholds = vec![0.1, Duration::minutes(15).inner_seconds()];
199
200 let colors = vec![Color::rgb(0, 0, 0).alpha(0.3)];
202
203 let mut i = BorderIsochrone {
204 start,
205 options,
206 draw: ToggleZoomed::empty(ctx),
207 thresholds,
208 colors,
209 time_to_reach_building,
210 };
211
212 i.draw = ToggleZoomedBuilder::from(draw_isochrone(
213 &app.map,
214 &i.time_to_reach_building,
215 &i.thresholds,
216 &i.colors,
217 ))
218 .build(ctx);
219 i
220 }
221}