use std::collections::HashMap;
use anyhow::Result;
use fs_err::File;
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use serde::Deserialize;
use abstio::path_shared_input;
use abstutil::{prettyprint_usize, Timer};
use geom::{GPSBounds, Polygon};
use map_model::Map;
use popdat::od::DesireLine;
use raw_map::RawMap;
use synthpop::{Scenario, TrafficCounts, TripEndpoint, TripMode};
use crate::configuration::ImporterConfiguration;
use crate::utils::download;
pub async fn import_collision_data(
map: &RawMap,
config: &ImporterConfiguration,
timer: &mut Timer<'_>,
) {
download(
config,
path_shared_input("Road Safety Data - Accidents 2019.csv"),
"http://data.dft.gov.uk.s3.amazonaws.com/road-accidents-safety-data/DfTRoadSafety_Accidents_2019.zip").await;
let shapes = kml::ExtraShapes::load_csv(
path_shared_input("Road Safety Data - Accidents 2019.csv"),
&map.streets.gps_bounds,
timer,
)
.unwrap();
let collisions = collisions::import_stats19(
shapes,
"http://data.dft.gov.uk.s3.amazonaws.com/road-accidents-safety-data/DfTRoadSafety_Accidents_2019.zip");
abstio::write_binary(
map.get_city_name().input_path("collisions.bin"),
&collisions,
);
}
pub async fn generate_scenario(
map: &Map,
config: &ImporterConfiguration,
timer: &mut Timer<'_>,
) -> Result<()> {
timer.start("prepare input");
download(
config,
path_shared_input("wu03ew_v2.csv"),
"https://s3-eu-west-1.amazonaws.com/statistics.digitalresources.jisc.ac.uk/dkan/files/FLOW/wu03ew_v2/wu03ew_v2.csv").await;
download(
config,
path_shared_input("zones_core.geojson"),
"https://github.com/cyipt/actdev/releases/download/0.1.13/zones_core.geojson",
)
.await;
let desire_lines = parse_desire_lines(path_shared_input("wu03ew_v2.csv"))?;
let zones = parse_zones(
map.get_gps_bounds(),
path_shared_input("zones_core.geojson"),
)?;
timer.stop("prepare input");
timer.start("disaggregate");
let mut rng = XorShiftRng::seed_from_u64(42);
let mut scenario = Scenario::empty(map, "background");
scenario.only_seed_buses = None;
scenario.people = popdat::od::disaggregate(
map,
zones,
desire_lines,
popdat::od::Options::default(),
&mut rng,
timer,
);
scenario = scenario.remove_weird_schedules(false);
if false {
check_sensor_data(map, &scenario, "/home/dabreegster/sensors.json", timer);
}
info!(
"Generated background traffic scenario with {} people",
prettyprint_usize(scenario.people.len())
);
timer.stop("disaggregate");
match load_study_area(map) {
Ok(study_area) => {
let before = scenario.people.len();
scenario.people.retain(|p| match p.trips[0].origin {
TripEndpoint::Building(b) => !study_area.contains_pt(map.get_b(b).polygon.center()),
_ => true,
});
info!(
"Removed {} people from the background scenario that live in the study area",
prettyprint_usize(before - scenario.people.len())
);
let mut base: Scenario = abstio::maybe_read_binary::<Scenario>(
abstio::path_scenario(map.get_name(), "base"),
timer,
)?;
base.people.extend(scenario.people.clone());
base.scenario_name = "base_with_bg".to_string();
base.save();
let mut go_active: Scenario = abstio::maybe_read_binary(
abstio::path_scenario(map.get_name(), "go_active"),
timer,
)?;
go_active.people.extend(scenario.people);
go_active.scenario_name = "go_active_with_bg".to_string();
go_active.save();
}
Err(err) => {
info!("{} has no study area: {}", map.get_name().describe(), err);
scenario.save();
}
}
Ok(())
}
fn parse_desire_lines(path: String) -> Result<Vec<DesireLine>> {
let mut output = Vec::new();
for rec in csv::Reader::from_reader(File::open(path)?).deserialize() {
let rec: Record = rec?;
for (mode, number_commuters) in [
(TripMode::Drive, rec.num_drivers),
(TripMode::Bike, rec.num_bikers),
(TripMode::Walk, rec.num_pedestrians),
(
TripMode::Transit,
rec.num_transit1 + rec.num_transit2 + rec.num_transit3,
),
] {
if number_commuters > 0 {
output.push(DesireLine {
home_zone: rec.home_zone.clone(),
work_zone: rec.work_zone.clone(),
mode,
number_commuters,
});
}
}
}
Ok(output)
}
#[derive(Debug, Deserialize)]
struct Record {
#[serde(rename = "Area of residence")]
home_zone: String,
#[serde(rename = "Area of workplace")]
work_zone: String,
#[serde(rename = "Underground, metro, light rail, tram")]
num_transit1: usize,
#[serde(rename = "Train")]
num_transit2: usize,
#[serde(rename = "Bus, minibus or coach")]
num_transit3: usize,
#[serde(rename = "Driving a car or van")]
num_drivers: usize,
#[serde(rename = "Bicycle")]
num_bikers: usize,
#[serde(rename = "On foot")]
num_pedestrians: usize,
}
fn parse_zones(gps_bounds: &GPSBounds, path: String) -> Result<HashMap<String, Polygon>> {
let mut zones = HashMap::new();
let require_in_bounds = false;
for (polygon, tags) in
Polygon::from_geojson_bytes(&abstio::slurp_file(path)?, gps_bounds, require_in_bounds)?
{
if let Some(code) = tags.get("geo_code") {
zones.insert(code.to_string(), polygon);
} else {
bail!("Input is missing geo_code: {:?}", tags);
}
}
Ok(zones)
}
fn load_study_area(map: &Map) -> Result<Polygon> {
let require_in_bounds = true;
let mut list = Polygon::from_geojson_bytes(
&abstio::slurp_file(abstio::path(format!(
"system/study_areas/{}.geojson",
map.get_name().city.city.replace("_", "-")
)))?,
map.get_gps_bounds(),
require_in_bounds,
)?;
if list.len() != 1 {
bail!("study area geojson has {} polygons", list.len());
}
Ok(list.pop().unwrap().0)
}
fn check_sensor_data(map: &Map, scenario: &Scenario, sensor_path: &str, timer: &mut Timer) {
use map_model::PathRequest;
let requests = scenario
.all_trips()
.filter_map(|trip| {
if trip.mode == TripMode::Drive {
TripEndpoint::path_req(trip.origin, trip.destination, trip.mode, map)
} else {
None
}
})
.collect();
let deduped = PathRequest::deduplicate(map, requests);
let model = TrafficCounts::from_path_requests(
map,
"the generated scenario".to_string(),
&deduped,
map.get_pathfinder(),
timer,
);
let sensors = abstio::maybe_read_json::<TrafficCounts>(sensor_path.to_string(), timer).unwrap();
sensors.quickly_compare(&model);
}