1use std::collections::HashMap;
2
3use anyhow::Result;
4use fs_err::File;
5use rand::SeedableRng;
6use rand_xorshift::XorShiftRng;
7use serde::Deserialize;
8
9use abstio::path_shared_input;
10use abstutil::{prettyprint_usize, Timer};
11use geom::{GPSBounds, Polygon};
12use map_model::Map;
13use popdat::od::DesireLine;
14use raw_map::RawMap;
15use synthpop::{Scenario, TrafficCounts, TripEndpoint, TripMode};
16
17use crate::configuration::ImporterConfiguration;
18use crate::utils::download;
19
20pub async fn import_collision_data(
21 map: &RawMap,
22 config: &ImporterConfiguration,
23 timer: &mut Timer<'_>,
24) {
25 download(
26 config,
27 path_shared_input("Road Safety Data - Accidents 2019.csv"),
28 "http://data.dft.gov.uk.s3.amazonaws.com/road-accidents-safety-data/DfTRoadSafety_Accidents_2019.zip").await;
29
30 let shapes = kml::ExtraShapes::load_csv(
32 path_shared_input("Road Safety Data - Accidents 2019.csv"),
33 &map.streets.gps_bounds,
34 timer,
35 )
36 .unwrap();
37 let collisions = collisions::import_stats19(
38 shapes,
39 "http://data.dft.gov.uk.s3.amazonaws.com/road-accidents-safety-data/DfTRoadSafety_Accidents_2019.zip");
40 abstio::write_binary(
41 map.get_city_name().input_path("collisions.bin"),
42 &collisions,
43 );
44}
45
46pub async fn generate_scenario(
47 map: &Map,
48 config: &ImporterConfiguration,
49 timer: &mut Timer<'_>,
50) -> Result<()> {
51 timer.start("prepare input");
52 download(
53 config,
54 path_shared_input("wu03ew_v2.csv"),
55 "https://s3-eu-west-1.amazonaws.com/statistics.digitalresources.jisc.ac.uk/dkan/files/FLOW/wu03ew_v2/wu03ew_v2.csv").await;
56 download(
59 config,
60 path_shared_input("zones_core.geojson"),
61 "https://github.com/cyipt/actdev/releases/download/0.1.13/zones_core.geojson",
62 )
63 .await;
64
65 let desire_lines = parse_desire_lines(path_shared_input("wu03ew_v2.csv"))?;
66 let zones = parse_zones(
67 map.get_gps_bounds(),
68 path_shared_input("zones_core.geojson"),
69 )?;
70 timer.stop("prepare input");
71
72 timer.start("disaggregate");
73 let mut rng = XorShiftRng::seed_from_u64(42);
75 let mut scenario = Scenario::empty(map, "background");
76 scenario.only_seed_buses = None;
78 scenario.people = popdat::od::disaggregate(
79 map,
80 zones,
81 desire_lines,
82 popdat::od::Options::default(),
83 &mut rng,
84 timer,
85 );
86 scenario = scenario.remove_weird_schedules(false);
89 if false {
91 check_sensor_data(map, &scenario, "/home/dabreegster/sensors.json", timer);
92 }
93 info!(
94 "Generated background traffic scenario with {} people",
95 prettyprint_usize(scenario.people.len())
96 );
97 timer.stop("disaggregate");
98
99 match load_study_area(map) {
101 Ok(study_area) => {
102 let before = scenario.people.len();
105 scenario.people.retain(|p| match p.trips[0].origin {
106 TripEndpoint::Building(b) => !study_area.contains_pt(map.get_b(b).polygon.center()),
107 _ => true,
108 });
109 info!(
110 "Removed {} people from the background scenario that live in the study area",
111 prettyprint_usize(before - scenario.people.len())
112 );
113
114 let mut base: Scenario = abstio::maybe_read_binary::<Scenario>(
116 abstio::path_scenario(map.get_name(), "base"),
117 timer,
118 )?;
119 base.people.extend(scenario.people.clone());
120 base.scenario_name = "base_with_bg".to_string();
121 base.save();
122
123 let mut go_active: Scenario = abstio::maybe_read_binary(
124 abstio::path_scenario(map.get_name(), "go_active"),
125 timer,
126 )?;
127 go_active.people.extend(scenario.people);
128 go_active.scenario_name = "go_active_with_bg".to_string();
129 go_active.save();
130
131 }
133 Err(err) => {
134 info!("{} has no study area: {}", map.get_name().describe(), err);
136 scenario.save();
137 }
138 }
139
140 Ok(())
141}
142
143fn parse_desire_lines(path: String) -> Result<Vec<DesireLine>> {
144 let mut output = Vec::new();
145 for rec in csv::Reader::from_reader(File::open(path)?).deserialize() {
146 let rec: Record = rec?;
147 for (mode, number_commuters) in [
148 (TripMode::Drive, rec.num_drivers),
149 (TripMode::Bike, rec.num_bikers),
150 (TripMode::Walk, rec.num_pedestrians),
151 (
152 TripMode::Transit,
153 rec.num_transit1 + rec.num_transit2 + rec.num_transit3,
154 ),
155 ] {
156 if number_commuters > 0 {
157 output.push(DesireLine {
158 home_zone: rec.home_zone.clone(),
159 work_zone: rec.work_zone.clone(),
160 mode,
161 number_commuters,
162 });
163 }
164 }
165 }
166 Ok(output)
167}
168
169#[derive(Debug, Deserialize)]
172struct Record {
173 #[serde(rename = "Area of residence")]
174 home_zone: String,
175 #[serde(rename = "Area of workplace")]
176 work_zone: String,
177 #[serde(rename = "Underground, metro, light rail, tram")]
178 num_transit1: usize,
179 #[serde(rename = "Train")]
180 num_transit2: usize,
181 #[serde(rename = "Bus, minibus or coach")]
182 num_transit3: usize,
183 #[serde(rename = "Driving a car or van")]
184 num_drivers: usize,
185 #[serde(rename = "Bicycle")]
186 num_bikers: usize,
187 #[serde(rename = "On foot")]
188 num_pedestrians: usize,
189}
190
191fn parse_zones(gps_bounds: &GPSBounds, path: String) -> Result<HashMap<String, Polygon>> {
193 let mut zones = HashMap::new();
194 let require_in_bounds = false;
195 for (polygon, tags) in
196 Polygon::from_geojson_bytes(&abstio::slurp_file(path)?, gps_bounds, require_in_bounds)?
197 {
198 if let Some(code) = tags.get("geo_code") {
199 zones.insert(code.to_string(), polygon);
200 } else {
201 bail!("Input is missing geo_code: {:?}", tags);
202 }
203 }
204 Ok(zones)
205}
206
207fn load_study_area(map: &Map) -> Result<Polygon> {
208 let require_in_bounds = true;
209 let mut list = Polygon::from_geojson_bytes(
210 &abstio::slurp_file(abstio::path(format!(
211 "system/study_areas/{}.geojson",
212 map.get_name().city.city.replace("_", "-")
213 )))?,
214 map.get_gps_bounds(),
215 require_in_bounds,
216 )?;
217 if list.len() != 1 {
218 bail!("study area geojson has {} polygons", list.len());
219 }
220 Ok(list.pop().unwrap().0)
221}
222
223fn check_sensor_data(map: &Map, scenario: &Scenario, sensor_path: &str, timer: &mut Timer) {
224 use map_model::PathRequest;
225
226 let requests = scenario
227 .all_trips()
228 .filter_map(|trip| {
229 if trip.mode == TripMode::Drive {
230 TripEndpoint::path_req(trip.origin, trip.destination, trip.mode, map)
231 } else {
232 None
233 }
234 })
235 .collect();
236 let deduped = PathRequest::deduplicate(map, requests);
237 let model = TrafficCounts::from_path_requests(
238 map,
239 "the generated scenario".to_string(),
240 &deduped,
241 map.get_pathfinder(),
242 timer,
243 );
244
245 let sensors = abstio::maybe_read_json::<TrafficCounts>(sensor_path.to_string(), timer).unwrap();
246 sensors.quickly_compare(&model);
247}