1use std::collections::HashMap;
2
3use rand::seq::SliceRandom;
4use rand::Rng;
5use rand_xorshift::XorShiftRng;
6
7use abstutil::Timer;
8use map_model::{BuildingID, IntersectionID, Map, PathConstraints, PathRequest};
9use synthpop::{IndividTrip, PersonSpec, TripEndpoint, TripMode, TripPurpose};
10
11use crate::{Activity, CensusPerson, Config};
12
13pub fn make_people(
14 people: Vec<CensusPerson>,
15 map: &Map,
16 timer: &mut Timer,
17 rng: &mut XorShiftRng,
18 config: &Config,
19) -> Vec<PersonSpec> {
20 let commuter_borders: Vec<IntersectionID> = map
27 .all_outgoing_borders()
28 .into_iter()
29 .filter(|b| b.is_incoming_border())
30 .map(|b| b.id)
31 .collect();
32
33 let person_factory = PersonFactory::new(map);
37 let make_person_inputs = people
38 .into_iter()
39 .map(|person| (person, sim::fork_rng(rng)))
40 .collect();
41 timer.parallelize(
42 "making people in parallel",
43 make_person_inputs,
44 |(person, mut rng)| {
45 person_factory.make_person(person, map, &commuter_borders, &mut rng, config)
46 },
47 )
48}
49
50struct PersonFactory {
51 activity_to_buildings: HashMap<Activity, Vec<BuildingID>>,
52}
53
54impl PersonFactory {
55 fn new(map: &Map) -> Self {
56 let activity_to_buildings = Self::activity_to_buildings(map);
57 Self {
58 activity_to_buildings,
59 }
60 }
61
62 fn activity_to_buildings(map: &Map) -> HashMap<Activity, Vec<BuildingID>> {
63 let categories = vec![
65 (Activity::Breakfast, vec!["cafe"]),
66 (Activity::Lunch, vec!["pub", "food_court", "fast_food"]),
67 (
68 Activity::Dinner,
69 vec!["restaurant", "theatre", "biergarten"],
70 ),
71 (
72 Activity::School,
73 vec![
74 "college",
75 "kindergarten",
76 "language_school",
77 "library",
78 "music_school",
79 "university",
80 ],
81 ),
82 (
83 Activity::Entertainment,
84 vec![
85 "arts_centre",
86 "casino",
87 "cinema",
88 "community_centre",
89 "fountain",
90 "gambling",
91 "nightclub",
92 "planetarium",
93 "public_bookcase",
94 "pool",
95 "dojo",
96 "social_centre",
97 "social_centre",
98 "studio",
99 "theatre",
100 "bar",
101 "bbq",
102 "bicycle_rental",
103 "boat_rental",
104 "boat_sharing",
105 "dive_centre",
106 "internet_cafe",
107 ],
108 ),
109 (
110 Activity::Errands,
111 vec![
112 "marketplace",
113 "post_box",
114 "photo_booth",
115 "recycling",
116 "townhall",
117 ],
118 ),
119 (Activity::Financial, vec!["bank", "atm", "bureau_de_change"]),
120 (
121 Activity::Healthcare,
122 vec![
123 "baby_hatch",
124 "clinic",
125 "dentist",
126 "doctors",
127 "hospital",
128 "nursing_home",
129 "pharmacy",
130 "social_facility",
131 "veterinary",
132 "childcare",
133 ],
134 ),
135 (Activity::Work, vec!["bank", "clinic"]),
136 ];
137
138 let mut candidates: HashMap<Activity, Vec<BuildingID>> = HashMap::new();
140 for b in map.all_buildings() {
141 for (activity, categories) in &categories {
142 for amenity in &b.amenities {
143 if categories.contains(&amenity.amenity_type.as_str()) {
144 candidates
145 .entry(*activity)
146 .and_modify(|v| v.push(b.id))
147 .or_insert_with(|| vec![b.id]);
148 }
149 }
150 }
151 }
152 candidates
153 }
154
155 fn find_building_for_activity(
156 &self,
157 activity: Activity,
158 _start: TripEndpoint,
159 _map: &Map,
160 rng: &mut XorShiftRng,
161 ) -> Option<BuildingID> {
162 self.activity_to_buildings
170 .get(&activity)
171 .and_then(|buildings| buildings.choose(rng).cloned())
172 }
173
174 pub fn make_person(
175 &self,
176 person: CensusPerson,
177 map: &Map,
178 commuter_borders: &[IntersectionID],
179 rng: &mut XorShiftRng,
180 config: &Config,
181 ) -> PersonSpec {
182 let schedule = person.generate_schedule(config, rng);
183
184 let mut output = PersonSpec {
185 orig_id: None,
186 trips: Vec::new(),
187 };
188
189 let mut current_location = TripEndpoint::Building(person.home);
190 for (departure_time, activity) in schedule.activities {
191 let purpose = TripPurpose::Shopping;
194
195 let goto = if let Some(destination) =
196 self.find_building_for_activity(activity, current_location, map, rng)
197 {
198 TripEndpoint::Building(destination)
199 } else if let Some(i) = commuter_borders.choose(rng) {
200 TripEndpoint::Border(*i)
202 } else {
203 continue;
205 };
206
207 let mode = pick_mode(current_location, goto, map, rng, config);
208 output.trips.push(IndividTrip::new(
209 departure_time,
210 purpose,
211 current_location,
212 goto,
213 mode,
214 ));
215
216 current_location = goto;
217 }
218
219 output
220 }
221}
222
223fn pick_mode(
224 from: TripEndpoint,
225 to: TripEndpoint,
226 map: &Map,
227 rng: &mut XorShiftRng,
228 config: &Config,
229) -> TripMode {
230 let (b1, b2) = match (from, to) {
231 (TripEndpoint::Building(b1), TripEndpoint::Building(b2)) => (b1, b2),
232 _ => {
234 return TripMode::Drive;
235 }
236 };
237
238 let distance = if let Some(path) =
240 PathRequest::between_buildings(map, b1, b2, PathConstraints::Pedestrian)
241 .and_then(|req| map.pathfind(req).ok())
242 {
243 path.total_length()
244 } else {
245 return TripMode::Drive;
249 };
250
251 if distance < config.walk_for_distances_shorter_than {
267 return TripMode::Walk;
268 }
269
270 if distance < config.walk_or_bike_for_distances_shorter_than {
272 if rng.gen_bool(0.15) {
275 return TripMode::Bike;
276 }
277 if rng.gen_bool(0.05) {
278 return TripMode::Walk;
279 }
280 }
281
282 if rng.gen_bool(0.005) {
284 return TripMode::Bike;
285 }
286 if rng.gen_bool(0.3) {
288 return TripMode::Transit;
289 }
290
291 TripMode::Drive
293}