cli/
augment_scenario.rs
use rand::prelude::SliceRandom;
use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng;
use abstutil::{prettyprint_usize, Timer};
use geom::{Distance, Duration, FindClosest};
use map_model::{AmenityType, BuildingID, Map};
use synthpop::{IndividTrip, Scenario, ScenarioModifier, TripEndpoint, TripMode, TripPurpose};
pub fn run(
input_scenario: String,
should_add_return_trips: bool,
should_add_lunch_trips: bool,
modifiers: Vec<ScenarioModifier>,
should_delete_cancelled_trips: bool,
rng_seed: u64,
) {
let mut rng = XorShiftRng::seed_from_u64(rng_seed);
let mut timer = Timer::new("augment scenario");
let mut scenario: Scenario = abstio::must_read_object(input_scenario, &mut timer);
let map = Map::load_synchronously(scenario.map_name.path(), &mut timer);
if should_add_return_trips {
add_return_trips(&mut scenario, &mut rng);
}
if should_add_lunch_trips {
add_lunch_trips(&mut scenario, &map, &mut rng, &mut timer);
}
for m in modifiers {
scenario = m.apply(&map, scenario, &mut rng);
}
if should_delete_cancelled_trips {
scenario = delete_cancelled_trips(scenario);
}
scenario.save();
}
fn add_return_trips(scenario: &mut Scenario, rng: &mut XorShiftRng) {
let mut cnt = 0;
for person in &mut scenario.people {
if person.trips.len() != 1 {
continue;
}
let depart =
person.trips[0].depart + rand_duration(rng, Duration::hours(4), Duration::hours(12));
person.trips.push(IndividTrip::new(
depart,
TripPurpose::Home,
person.trips[0].destination,
person.trips[0].origin,
person.trips[0].mode,
));
cnt += 1;
}
info!("Added return trips to {} people", prettyprint_usize(cnt));
}
fn rand_duration(rng: &mut XorShiftRng, low: Duration, high: Duration) -> Duration {
Duration::seconds(rng.gen_range(low.inner_seconds()..high.inner_seconds()))
}
fn add_lunch_trips(scenario: &mut Scenario, map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) {
timer.start("index lunch spots");
let mut closest_spots: FindClosest<BuildingID> = FindClosest::new();
for b in map.all_buildings() {
if b.amenities
.iter()
.any(|a| AmenityType::categorize(&a.amenity_type) == Some(AmenityType::Food))
{
closest_spots.add_polygon(b.id, &b.polygon);
}
}
timer.stop("index lunch spots");
timer.start_iter("add lunch trips", scenario.people.len());
let mut cnt = 0;
for person in &mut scenario.people {
timer.next();
let num_trips = person.trips.len();
if num_trips <= 1 || person.trips[num_trips - 1].destination != person.trips[0].origin {
continue;
}
let work = match person.trips[num_trips - 2].destination {
TripEndpoint::Building(b) => b,
_ => continue,
};
let has_bike = person.trips[num_trips - 2].mode == TripMode::Bike;
let (restaurant, mode) =
if let Some(pair) = pick_lunch_spot(work, has_bike, &closest_spots, map, rng) {
pair
} else {
continue;
};
let t1 = person.trips[num_trips - 2].depart;
let t2 = person.trips[num_trips - 1].depart;
let depart = t1 + (t2 - t1) / 2.0;
let return_home = person.trips.pop().unwrap();
person.trips.push(IndividTrip::new(
depart,
TripPurpose::Meal,
TripEndpoint::Building(work),
TripEndpoint::Building(restaurant),
mode,
));
person.trips.push(IndividTrip::new(
depart + Duration::minutes(30),
TripPurpose::Work,
TripEndpoint::Building(restaurant),
TripEndpoint::Building(work),
mode,
));
person.trips.push(return_home);
cnt += 1;
}
info!("Added lunch trips to {} people", prettyprint_usize(cnt));
}
fn pick_lunch_spot(
work: BuildingID,
has_bike: bool,
closest_spots: &FindClosest<BuildingID>,
map: &Map,
rng: &mut XorShiftRng,
) -> Option<(BuildingID, TripMode)> {
let choices =
closest_spots.all_close_pts(map.get_b(work).polygon.center(), Distance::miles(10.0));
let (b, _, dist) = choices
.choose_weighted(rng, |(_, _, dist)| dist.inner_meters())
.ok()?;
let mode = if *dist <= Distance::miles(1.0) {
TripMode::Walk
} else if has_bike {
TripMode::Bike
} else {
TripMode::Drive
};
Some((*b, mode))
}
fn delete_cancelled_trips(mut scenario: Scenario) -> Scenario {
for person in &mut scenario.people {
person.trips.retain(|trip| !trip.cancelled);
}
scenario.remove_weird_schedules(false)
}