1use std::collections::HashMap;
6
7use rand::seq::SliceRandom;
8use rand::Rng;
9use rand_xorshift::XorShiftRng;
10
11use abstutil::{prettyprint_usize, Timer};
12use geom::{Duration, Percent, PolyLine, Polygon, Pt2D, Time};
13use map_model::{BuildingID, BuildingType, Map};
14use synthpop::{IndividTrip, MapBorders, PersonSpec, TripEndpoint, TripMode, TripPurpose};
15
16#[derive(Debug)]
19pub struct DesireLine {
20 pub home_zone: String,
21 pub work_zone: String,
22 pub mode: TripMode,
23 pub number_commuters: usize,
24}
25
26pub struct Options {
29 pub departure_time: NormalDistribution,
31 pub work_duration: NormalDistribution,
33 pub include_zones: IncludeZonePolicy,
34}
35
36impl Options {
37 pub fn default() -> Options {
38 Options {
39 departure_time: NormalDistribution::new(
40 Duration::hours(8) + Duration::minutes(30),
41 Duration::minutes(30),
42 ),
43 work_duration: NormalDistribution::new(Duration::hours(9), Duration::hours(1)),
44 include_zones: IncludeZonePolicy::AllowRemote,
45 }
46 }
47}
48
49#[derive(PartialEq)]
51pub enum IncludeZonePolicy {
52 MustOverlap,
56 AllowRemote,
59}
60
61pub fn disaggregate(
72 map: &Map,
73 zones: HashMap<String, Polygon>,
74 desire_lines: Vec<DesireLine>,
75 opts: Options,
76 rng: &mut XorShiftRng,
77 timer: &mut Timer,
78) -> Vec<PersonSpec> {
79 let zones = create_zones(map, zones, opts.include_zones, timer);
82
83 let mut people = Vec::new();
84 let mut on_map_only = 0;
85 let mut lives_on_map = 0;
86 let mut works_on_map = 0;
87 let mut pass_through = 0;
88
89 timer.start_iter("create people per desire line", desire_lines.len());
90 for desire in desire_lines {
91 timer.next();
92 if !zones.contains_key(&desire.home_zone) || !zones.contains_key(&desire.work_zone) {
94 continue;
95 }
96
97 let home_zone = &zones[&desire.home_zone];
98 let work_zone = &zones[&desire.work_zone];
99
100 if home_zone.is_remote() && work_zone.is_remote() {
102 if desire.home_zone == desire.work_zone {
103 continue;
104 }
105
106 if !map
107 .get_boundary_polygon()
108 .intersects_polyline(&PolyLine::must_new(vec![
109 home_zone.center,
110 work_zone.center,
111 ]))
112 {
113 continue;
114 }
115 }
116
117 for _ in 0..desire.number_commuters {
118 if let (Some((leave_home, goto_home)), Some((leave_work, goto_work))) = (
121 home_zone.pick_home(desire.mode, map, rng),
122 work_zone.pick_workplace(desire.mode, map, rng),
123 ) {
124 if leave_home == goto_work || leave_work == goto_home {
126 continue;
127 }
128
129 match (goto_home, goto_work) {
130 (TripEndpoint::Building(_), TripEndpoint::Building(_)) => {
131 on_map_only += 1;
132 }
133 (TripEndpoint::Building(_), TripEndpoint::Border(_)) => {
134 lives_on_map += 1;
135 }
136 (TripEndpoint::Border(_), TripEndpoint::Building(_)) => {
137 works_on_map += 1;
138 }
139 (TripEndpoint::Border(_), TripEndpoint::Border(_)) => {
140 pass_through += 1;
141 }
142 _ => unreachable!(),
143 }
144
145 let goto_work_time = Time::START_OF_DAY + opts.departure_time.sample(rng);
147 let return_home_time = goto_work_time + opts.work_duration.sample(rng);
148 people.push(PersonSpec {
149 orig_id: None,
150 trips: vec![
151 IndividTrip::new(
152 goto_work_time,
153 TripPurpose::Work,
154 leave_home,
155 goto_work,
156 desire.mode,
157 ),
158 IndividTrip::new(
159 return_home_time,
160 TripPurpose::Home,
161 leave_work,
162 goto_home,
163 desire.mode,
164 ),
165 ],
166 });
167 }
168 }
169 }
170 let total = on_map_only + lives_on_map + works_on_map + pass_through;
171 for (x, label) in [
172 (on_map_only, "live and work on-map"),
173 (lives_on_map, "live on-map, work remote"),
174 (works_on_map, "live remote, work on-map"),
175 (pass_through, "just pass through"),
176 ] {
177 info!(
178 "{} people ({}) {}",
179 prettyprint_usize(x),
180 Percent::of(x, total),
181 label
182 );
183 }
184
185 people
186}
187
188struct Zone {
189 polygon: Polygon,
190 center: Pt2D,
191 pct_overlap: f64,
192 homes: Vec<(BuildingID, usize)>,
196 workplaces: Vec<(BuildingID, usize)>,
197 borders: MapBorders,
198}
199
200impl Zone {
201 fn is_remote(&self) -> bool {
202 self.pct_overlap == 0.0
203 }
204}
205
206fn create_zones(
207 map: &Map,
208 input: HashMap<String, Polygon>,
209 include_zones: IncludeZonePolicy,
210 timer: &mut Timer,
211) -> HashMap<String, Zone> {
212 let all_borders = MapBorders::new(map);
213
214 let mut normal_zones = HashMap::new();
215 let mut remote_zones = HashMap::new();
216 for (name, zone) in timer
217 .parallelize(
218 "create zones",
219 input.into_iter().collect(),
220 |(name, polygon)| {
221 let mut overlapping_area = 0.0;
222 if let Ok(list) = polygon.intersection(map.get_boundary_polygon()) {
223 for p in list {
224 overlapping_area += p.area();
225 }
226 }
227 let pct_overlap = (overlapping_area / polygon.area()).min(1.0);
230 let is_remote = pct_overlap == 0.0;
231
232 if is_remote && include_zones == IncludeZonePolicy::MustOverlap {
233 None
234 } else {
235 let center = polygon.center();
237 let mut borders = all_borders.clone();
238 if !is_remote {
241 for list in vec![
242 &mut borders.incoming_walking,
243 &mut borders.incoming_driving,
244 &mut borders.incoming_biking,
245 &mut borders.outgoing_walking,
246 &mut borders.outgoing_driving,
247 &mut borders.outgoing_biking,
248 ] {
249 list.retain(|border| polygon.contains_pt(border.pos));
254 }
255 }
256 Some((
257 name,
258 Zone {
259 polygon,
260 center,
261 pct_overlap,
262 homes: Vec::new(),
263 workplaces: Vec::new(),
264 borders,
265 },
266 ))
267 }
268 },
269 )
270 .into_iter()
271 .flatten()
272 {
273 if zone.is_remote() {
274 remote_zones.insert(name, zone);
275 } else {
276 normal_zones.insert(name, zone);
277 }
278 }
279
280 info!(
281 "{} zones partly in the map boundary, {} remote zones",
282 prettyprint_usize(normal_zones.len()),
283 prettyprint_usize(remote_zones.len())
284 );
285
286 timer.start_iter("assign buildings to zones", map.all_buildings().len());
288 for b in map.all_buildings() {
289 timer.next();
290 let center = b.polygon.center();
291 if let Some((_, zone)) = normal_zones
293 .iter_mut()
294 .find(|(_, z)| z.polygon.contains_pt(center))
295 {
296 match b.bldg_type {
297 BuildingType::Residential { num_residents, .. } => {
300 zone.homes.push((b.id, num_residents + 1));
301 }
302 BuildingType::ResidentialCommercial(num_residents, _) => {
303 zone.homes.push((b.id, num_residents + 1));
304 zone.workplaces.push((b.id, b.amenities.len()));
308 }
309 BuildingType::Commercial(_) => {
310 zone.workplaces.push((b.id, b.amenities.len()));
311 }
312 BuildingType::Empty => {}
313 }
314 }
315 }
316
317 normal_zones.extend(remote_zones);
318 normal_zones
319}
320
321impl Zone {
322 fn pick_home(
325 &self,
326 mode: TripMode,
327 map: &Map,
328 rng: &mut XorShiftRng,
329 ) -> Option<(TripEndpoint, TripEndpoint)> {
330 if rng.gen_bool(self.pct_overlap) && !self.homes.is_empty() {
331 let b = self.homes.choose_weighted(rng, |(_, n)| *n).unwrap().0;
332 return Some((TripEndpoint::Building(b), TripEndpoint::Building(b)));
333 }
334 self.pick_borders(mode, map, rng)
335 }
336
337 fn pick_workplace(
340 &self,
341 mode: TripMode,
342 map: &Map,
343 rng: &mut XorShiftRng,
344 ) -> Option<(TripEndpoint, TripEndpoint)> {
345 if rng.gen_bool(self.pct_overlap) && !self.workplaces.is_empty() {
346 let b = self.workplaces.choose_weighted(rng, |(_, n)| *n).unwrap().0;
347 return Some((TripEndpoint::Building(b), TripEndpoint::Building(b)));
348 }
349 self.pick_borders(mode, map, rng)
350 }
351
352 fn pick_borders(
353 &self,
354 mode: TripMode,
355 map: &Map,
356 rng: &mut XorShiftRng,
357 ) -> Option<(TripEndpoint, TripEndpoint)> {
358 let (incoming, outgoing) = self.borders.for_mode(mode);
359
360 let leave_i = incoming
361 .choose_weighted(rng, |border| {
362 (border.weight as f64) * self.center.fast_dist(border.pos).into_inner()
363 })
364 .ok()?
365 .i;
366
367 if outgoing.iter().any(|border| border.i == leave_i) {
369 return Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(leave_i)));
370 }
371 let leave_pt = map.get_i(leave_i).polygon.center();
374 let goto_i = outgoing
375 .iter()
376 .min_by_key(|border| map.get_i(border.i).polygon.center().dist_to(leave_pt))?
377 .i;
378 Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(goto_i)))
379 }
380}
381
382pub struct NormalDistribution {
384 pub mean: Duration,
385 pub std_deviation: Duration,
386}
387
388impl NormalDistribution {
389 pub fn new(mean: Duration, std_deviation: Duration) -> NormalDistribution {
390 NormalDistribution {
391 mean,
392 std_deviation,
393 }
394 }
395
396 pub fn sample(&self, rng: &mut XorShiftRng) -> Duration {
397 use rand_distr::{Distribution, Normal};
398
399 Duration::seconds(
400 Normal::new(
401 self.mean.inner_seconds(),
402 self.std_deviation.inner_seconds(),
403 )
404 .unwrap()
405 .sample(rng),
406 )
407 }
408}