use std::collections::HashMap;
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use abstutil::{Parallelism, Timer};
use map_model::{BuildingID, IntersectionID, Map, PathConstraints, PathRequest};
use sim::{IndividTrip, PersonSpec, TripEndpoint, TripMode, TripPurpose};
use crate::{Activity, CensusPerson, Config};
pub fn make_people(
people: Vec<CensusPerson>,
map: &Map,
timer: &mut Timer,
rng: &mut XorShiftRng,
config: &Config,
) -> Vec<PersonSpec> {
let commuter_borders: Vec<IntersectionID> = map
.all_outgoing_borders()
.into_iter()
.filter(|b| b.is_incoming_border())
.map(|b| b.id)
.collect();
let person_factory = PersonFactory::new(map);
let make_person_inputs = people
.into_iter()
.map(|person| (person, sim::fork_rng(rng)))
.collect();
timer.parallelize(
"making people in parallel",
Parallelism::Fastest,
make_person_inputs,
|(person, mut rng)| {
person_factory.make_person(person, map, &commuter_borders, &mut rng, &config)
},
)
}
struct PersonFactory {
activity_to_buildings: HashMap<Activity, Vec<BuildingID>>,
}
impl PersonFactory {
fn new(map: &Map) -> Self {
let activity_to_buildings = Self::activity_to_buildings(map);
Self {
activity_to_buildings,
}
}
fn activity_to_buildings(map: &Map) -> HashMap<Activity, Vec<BuildingID>> {
let categories = vec![
(Activity::Breakfast, vec!["cafe"]),
(Activity::Lunch, vec!["pub", "food_court", "fast_food"]),
(
Activity::Dinner,
vec!["restaurant", "theatre", "biergarten"],
),
(
Activity::School,
vec![
"college",
"kindergarten",
"language_school",
"library",
"music_school",
"university",
],
),
(
Activity::Entertainment,
vec![
"arts_centre",
"casino",
"cinema",
"community_centre",
"fountain",
"gambling",
"nightclub",
"planetarium",
"public_bookcase",
"pool",
"dojo",
"social_centre",
"social_centre",
"studio",
"theatre",
"bar",
"bbq",
"bicycle_rental",
"boat_rental",
"boat_sharing",
"dive_centre",
"internet_cafe",
],
),
(
Activity::Errands,
vec![
"marketplace",
"post_box",
"photo_booth",
"recycling",
"townhall",
],
),
(Activity::Financial, vec!["bank", "atm", "bureau_de_change"]),
(
Activity::Healthcare,
vec![
"baby_hatch",
"clinic",
"dentist",
"doctors",
"hospital",
"nursing_home",
"pharmacy",
"social_facility",
"veterinary",
"childcare",
],
),
(Activity::Work, vec!["bank", "clinic"]),
];
let mut candidates: HashMap<Activity, Vec<BuildingID>> = HashMap::new();
for b in map.all_buildings() {
for (activity, categories) in &categories {
for amenity in &b.amenities {
if categories.contains(&amenity.amenity_type.as_str()) {
candidates
.entry(*activity)
.and_modify(|v| v.push(b.id))
.or_insert(vec![b.id]);
}
}
}
}
candidates
}
fn find_building_for_activity(
&self,
activity: Activity,
_start: TripEndpoint,
_map: &Map,
rng: &mut XorShiftRng,
) -> Option<BuildingID> {
self.activity_to_buildings
.get(&activity)
.and_then(|buildings| buildings.choose(rng).cloned())
}
pub fn make_person(
&self,
person: CensusPerson,
map: &Map,
commuter_borders: &Vec<IntersectionID>,
rng: &mut XorShiftRng,
config: &Config,
) -> PersonSpec {
let schedule = person.generate_schedule(config, rng);
let mut output = PersonSpec {
orig_id: None,
origin: TripEndpoint::Bldg(person.home),
trips: Vec::new(),
};
let mut current_location = TripEndpoint::Bldg(person.home);
for (departure_time, activity) in schedule.activities {
let purpose = TripPurpose::Shopping;
let goto = if let Some(destination) =
self.find_building_for_activity(activity, current_location, map, rng)
{
TripEndpoint::Bldg(destination)
} else {
TripEndpoint::Border(*commuter_borders.choose(rng).unwrap())
};
let mode = pick_mode(current_location, goto, map, rng, config);
output
.trips
.push(IndividTrip::new(departure_time, purpose, goto, mode));
current_location = goto;
}
output
}
}
fn pick_mode(
from: TripEndpoint,
to: TripEndpoint,
map: &Map,
rng: &mut XorShiftRng,
config: &Config,
) -> TripMode {
let (b1, b2) = match (from, to) {
(TripEndpoint::Bldg(b1), TripEndpoint::Bldg(b2)) => (b1, b2),
_ => {
return TripMode::Drive;
}
};
let distance = if let Some(path) =
PathRequest::between_buildings(map, b1, b2, PathConstraints::Pedestrian)
.and_then(|req| map.pathfind(req).ok())
{
path.total_length()
} else {
return TripMode::Drive;
};
if distance < config.walk_for_distances_shorter_than {
return TripMode::Walk;
}
if distance < config.walk_or_bike_for_distances_shorter_than {
if rng.gen_bool(0.15) {
return TripMode::Bike;
}
if rng.gen_bool(0.05) {
return TripMode::Walk;
}
}
if rng.gen_bool(0.005) {
return TripMode::Bike;
}
if rng.gen_bool(0.3) {
return TripMode::Transit;
}
TripMode::Drive
}