use std::collections::HashMap;
use rand::seq::SliceRandom;
use rand::Rng;
use rand_xorshift::XorShiftRng;
use abstutil::Timer;
use geom::{Duration, Polygon, Time};
use map_model::{BuildingID, BuildingType, Map};
use sim::{IndividTrip, MapBorders, PersonSpec, TripEndpoint, TripMode, TripPurpose};
#[derive(Debug)]
pub struct DesireLine {
pub home_zone: String,
pub work_zone: String,
pub mode: TripMode,
pub number_commuters: usize,
}
pub struct Options {
pub departure_time: NormalDistribution,
pub work_duration: NormalDistribution,
}
impl Options {
pub fn default() -> Options {
Options {
departure_time: NormalDistribution::new(
Duration::hours(8) + Duration::minutes(30),
Duration::minutes(30),
),
work_duration: NormalDistribution::new(Duration::hours(9), Duration::hours(1)),
}
}
}
pub fn disaggregate(
map: &Map,
zones: HashMap<String, Polygon>,
desire_lines: Vec<DesireLine>,
opts: Options,
rng: &mut XorShiftRng,
timer: &mut Timer,
) -> Vec<PersonSpec> {
timer.start("match zones");
let zones = create_zones(map, zones);
timer.stop("match zones");
let mut people = Vec::new();
timer.start("create people");
for desire in desire_lines {
if !zones.contains_key(&desire.home_zone) || !zones.contains_key(&desire.work_zone) {
continue;
}
let home_zone = &zones[&desire.home_zone];
let work_zone = &zones[&desire.work_zone];
for _ in 0..desire.number_commuters {
if let (Some((leave_home, goto_home)), Some((leave_work, goto_work))) = (
home_zone.pick_home(desire.mode, map, rng),
work_zone.pick_workplace(desire.mode, map, rng),
) {
let goto_work_time = Time::START_OF_DAY + opts.departure_time.sample(rng);
let return_home_time = goto_work_time + opts.work_duration.sample(rng);
people.push(PersonSpec {
orig_id: None,
trips: vec![
IndividTrip::new(
goto_work_time,
TripPurpose::Work,
leave_home,
goto_work,
desire.mode,
),
IndividTrip::new(
return_home_time,
TripPurpose::Home,
leave_work,
goto_home,
desire.mode,
),
],
});
}
}
}
timer.stop("create people");
people
}
struct Zone {
polygon: Polygon,
pct_overlap: f64,
homes: Vec<(BuildingID, usize)>,
workplaces: Vec<(BuildingID, usize)>,
borders: MapBorders,
}
fn create_zones(map: &Map, input: HashMap<String, Polygon>) -> HashMap<String, Zone> {
let all_borders = MapBorders::new(map);
let mut zones = HashMap::new();
for (name, polygon) in input {
let mut overlapping_area = 0.0;
for p in polygon.intersection(map.get_boundary_polygon()) {
overlapping_area += p.area();
}
let pct_overlap = (overlapping_area / polygon.area()).min(1.0);
if pct_overlap == 0.0 {
continue;
}
zones.insert(
name,
Zone {
polygon,
pct_overlap,
homes: Vec::new(),
workplaces: Vec::new(),
borders: all_borders.clone(),
},
);
}
for b in map.all_buildings() {
let center = b.polygon.center();
if let Some((_, zone)) = zones
.iter_mut()
.find(|(_, z)| z.polygon.contains_pt(center))
{
match b.bldg_type {
BuildingType::Residential { num_residents, .. } => {
zone.homes.push((b.id, num_residents + 1));
}
BuildingType::ResidentialCommercial(num_residents, _) => {
zone.homes.push((b.id, num_residents + 1));
zone.workplaces.push((b.id, b.amenities.len()));
}
BuildingType::Commercial(_) => {
zone.workplaces.push((b.id, b.amenities.len()));
}
BuildingType::Empty => {}
}
}
}
for zone in zones.values_mut() {
let polygon = zone.polygon.clone();
for list in vec![
&mut zone.borders.incoming_walking,
&mut zone.borders.incoming_driving,
&mut zone.borders.incoming_biking,
&mut zone.borders.outgoing_walking,
&mut zone.borders.outgoing_driving,
&mut zone.borders.outgoing_biking,
] {
list.retain(|(i, _)| polygon.contains_pt(map.get_i(*i).polygon.center()));
}
}
zones
}
impl Zone {
fn pick_home(
&self,
mode: TripMode,
map: &Map,
rng: &mut XorShiftRng,
) -> Option<(TripEndpoint, TripEndpoint)> {
if rng.gen_bool(self.pct_overlap) && !self.homes.is_empty() {
let b = self.homes.choose_weighted(rng, |(_, n)| *n).unwrap().0;
return Some((TripEndpoint::Bldg(b), TripEndpoint::Bldg(b)));
}
self.pick_borders(mode, map, rng)
}
fn pick_workplace(
&self,
mode: TripMode,
map: &Map,
rng: &mut XorShiftRng,
) -> Option<(TripEndpoint, TripEndpoint)> {
if rng.gen_bool(self.pct_overlap) && !self.workplaces.is_empty() {
let b = self.workplaces.choose_weighted(rng, |(_, n)| *n).unwrap().0;
return Some((TripEndpoint::Bldg(b), TripEndpoint::Bldg(b)));
}
self.pick_borders(mode, map, rng)
}
fn pick_borders(
&self,
mode: TripMode,
map: &Map,
rng: &mut XorShiftRng,
) -> Option<(TripEndpoint, TripEndpoint)> {
let (incoming, outgoing) = self.borders.for_mode(mode);
let leave_i = incoming.choose(rng)?.0;
if outgoing.iter().any(|(i, _)| *i == leave_i) {
return Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(leave_i)));
}
let leave_pt = map.get_i(leave_i).polygon.center();
let goto_i = outgoing
.iter()
.min_by_key(|(i, _)| map.get_i(*i).polygon.center().dist_to(leave_pt))?
.0;
Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(goto_i)))
}
}
pub struct NormalDistribution {
pub mean: Duration,
pub std_deviation: Duration,
}
impl NormalDistribution {
pub fn new(mean: Duration, std_deviation: Duration) -> NormalDistribution {
NormalDistribution {
mean,
std_deviation,
}
}
pub fn sample(&self, rng: &mut XorShiftRng) -> Duration {
use rand_distr::{Distribution, Normal};
Duration::seconds(
Normal::new(
self.mean.inner_seconds(),
self.std_deviation.inner_seconds(),
)
.unwrap()
.sample(rng),
)
}
}