1#[macro_use]
5extern crate log;
6
7mod augment_scenario;
8mod clip_osm;
9mod generate_houses;
10mod import_grid2demand;
11mod import_scenario;
12mod one_step_import;
13
14use std::io::Write;
15
16use abstio::CityName;
17use anyhow::Result;
18use fs_err::File;
19use importer::Job;
20use structopt::StructOpt;
21
22use abstutil::Timer;
23
24#[derive(StructOpt)]
25#[structopt(name = "abcli", about = "The A/B Street multi-tool")]
26enum Command {
27 DumpJSON {
29 #[structopt()]
30 path: String,
31 },
32 RandomScenario {
34 #[structopt(long)]
36 rng_seed: u64,
37 #[structopt(long)]
39 map: String,
40 #[structopt(long)]
42 scenario_name: String,
43 },
44 AugmentScenario {
46 #[structopt(long)]
51 input_scenario: String,
52 #[structopt(long)]
55 add_return_trips: bool,
56 #[structopt(long)]
58 add_lunch_trips: bool,
59 #[structopt(long, parse(try_from_str = parse_modifiers), default_value = "[]")]
61 scenario_modifiers: ModifierList,
62 #[structopt(long)]
64 delete_cancelled_trips: bool,
65 #[structopt(long, default_value = "42")]
67 rng_seed: u64,
68 },
69 ClipOSM {
72 #[structopt(long)]
74 pbf_path: String,
75 #[structopt(long)]
77 clip_path: String,
78 #[structopt(long)]
80 out_path: String,
81 },
82 ImportGrid2Demand {
84 #[structopt(long)]
86 input: String,
87 #[structopt(long)]
89 map: String,
90 },
91 ImportScenario {
94 #[structopt(long)]
96 input: String,
97 #[structopt(long)]
99 map: String,
100 #[structopt(long)]
103 skip_problems: bool,
104 },
105 ImportJSONMap {
108 #[structopt(long)]
110 input: String,
111 #[structopt(long)]
113 output: String,
114 },
115 MinifyMap {
117 #[structopt()]
119 map: String,
120 },
121 GenerateHouses {
123 #[structopt(long)]
125 map: String,
126 #[structopt(long)]
129 num_required: usize,
130 #[structopt(long, default_value = "42")]
132 rng_seed: u64,
133 #[structopt(long)]
135 output: String,
136 },
137 PickGeofabrik {
142 #[structopt()]
144 input: String,
145 },
146 OneStepImport {
148 #[structopt(long)]
150 geojson_path: String,
151 #[structopt(long)]
154 map_name: String,
155 #[structopt(long)]
157 use_geofabrik: bool,
158 #[structopt(long)]
161 use_osmium: bool,
162 #[structopt(long)]
165 inferred_sidewalks: bool,
166 #[structopt(long)]
168 filter_crosswalks: bool,
169 #[structopt(long)]
172 create_uk_travel_demand_model: bool,
173 #[structopt(flatten)]
174 opts: map_model::RawToMapOptions,
175 },
176 OneshotImport {
178 #[structopt()]
179 osm_input: String,
180 #[structopt(long)]
183 clip_path: Option<String>,
184 #[structopt(long)]
187 inferred_sidewalks: bool,
188 #[structopt(long)]
191 filter_crosswalks: bool,
192 #[structopt(long)]
195 create_uk_travel_demand_model: bool,
196 #[structopt(flatten)]
197 opts: map_model::RawToMapOptions,
198 },
199 RegenerateEverything {
201 #[structopt(long, default_value = "0")]
204 shard_num: usize,
205 #[structopt(long, default_value = "1")]
208 num_shards: usize,
209 },
210 RegenerateEverythingExternally,
212 Import {
214 #[structopt(flatten)]
215 job: Job,
216 },
217 #[structopt(name = "prebake-scenario")]
220 PrebakeScenario {
221 #[structopt()]
223 scenario_path: String,
224 },
225}
226
227type ModifierList = Vec<synthpop::ScenarioModifier>;
229
230fn parse_modifiers(x: &str) -> Result<ModifierList> {
231 abstutil::from_json(&x.to_string().into_bytes())
232}
233
234#[tokio::main]
235async fn main() -> Result<()> {
236 let cmd = Command::from_args();
237
238 if !matches!(
240 cmd,
241 Command::DumpJSON { .. } | Command::PickGeofabrik { .. },
242 ) {
243 abstutil::logger::setup();
244 }
245
246 match Command::from_args() {
249 Command::DumpJSON { path } => dump_json(path),
250 Command::RandomScenario {
251 rng_seed,
252 map,
253 scenario_name,
254 } => random_scenario(rng_seed, map, scenario_name),
255 Command::AugmentScenario {
256 input_scenario,
257 add_return_trips,
258 add_lunch_trips,
259 scenario_modifiers,
260 delete_cancelled_trips,
261 rng_seed,
262 } => augment_scenario::run(
263 input_scenario,
264 add_return_trips,
265 add_lunch_trips,
266 scenario_modifiers,
267 delete_cancelled_trips,
268 rng_seed,
269 ),
270 Command::ClipOSM {
271 pbf_path,
272 clip_path,
273 out_path,
274 } => clip_osm::run(pbf_path, clip_path, out_path)?,
275 Command::ImportGrid2Demand { input, map } => import_grid2demand::run(input, map)?,
276 Command::ImportScenario {
277 input,
278 map,
279 skip_problems,
280 } => import_scenario::run(input, map, skip_problems),
281 Command::ImportJSONMap { input, output } => import_json_map(input, output),
282 Command::MinifyMap { map } => minify_map(map),
283 Command::GenerateHouses {
284 map,
285 num_required,
286 rng_seed,
287 output,
288 } => generate_houses::run(map, num_required, rng_seed, output),
289 Command::PickGeofabrik { input } => {
290 println!("{}", importer::pick_geofabrik(input).await?.0)
291 }
292 Command::OneStepImport {
293 geojson_path,
294 map_name,
295 use_geofabrik,
296 use_osmium,
297 inferred_sidewalks,
298 filter_crosswalks,
299 create_uk_travel_demand_model,
300 opts,
301 } => {
302 let mut options = convert_osm::Options::default();
303 options.map_config.inferred_sidewalks = inferred_sidewalks;
304 options.filter_crosswalks = filter_crosswalks;
305 one_step_import::run(
306 geojson_path,
307 map_name,
308 use_geofabrik,
309 use_osmium,
310 options,
311 create_uk_travel_demand_model,
312 opts,
313 )
314 .await?
315 }
316 Command::OneshotImport {
317 osm_input,
318 clip_path,
319 inferred_sidewalks,
320 filter_crosswalks,
321 create_uk_travel_demand_model,
322 opts,
323 } => {
324 let mut options = convert_osm::Options::default();
325 options.map_config.inferred_sidewalks = inferred_sidewalks;
326 options.filter_crosswalks = filter_crosswalks;
327 importer::oneshot(
328 osm_input,
329 clip_path,
330 options,
331 create_uk_travel_demand_model,
332 opts,
333 )
334 .await
335 }
336 Command::RegenerateEverything {
337 shard_num,
338 num_shards,
339 } => importer::regenerate_everything(shard_num, num_shards).await,
340 Command::RegenerateEverythingExternally => regenerate_everything_externally()?,
341 Command::Import { job } => job.run(&mut Timer::new("import one city")).await,
342 Command::PrebakeScenario { scenario_path } => prebake_scenario(scenario_path),
343 }
344 Ok(())
345}
346
347fn dump_json(path: String) {
348 if path.contains("/maps/") {
350 if let Ok(map) =
351 abstio::maybe_read_binary::<map_model::Map>(path.clone(), &mut Timer::throwaway())
352 {
353 println!("{}", abstutil::to_json(&map));
354 return;
355 }
356 }
357 if path.contains("/raw_maps/") {
358 if let Ok(map) =
359 abstio::maybe_read_binary::<raw_map::RawMap>(path.clone(), &mut Timer::throwaway())
360 {
361 println!("{}", abstutil::to_json(&map));
362 return;
363 }
364 }
365 if path.contains("/scenarios/") {
366 if let Ok(scenario) =
367 abstio::maybe_read_binary::<synthpop::Scenario>(path.clone(), &mut Timer::throwaway())
368 {
369 println!("{}", abstutil::to_json(&scenario));
370 return;
371 }
372 }
373 panic!(
374 "Don't know how to dump JSON for {}. Only maps, raw maps, and scenarios are supported.",
375 path
376 );
377}
378
379fn random_scenario(rng_seed: u64, map: String, scenario_name: String) {
380 use rand::SeedableRng;
381 use rand_xorshift::XorShiftRng;
382
383 let mut rng = XorShiftRng::seed_from_u64(rng_seed);
384 let map = map_model::Map::load_synchronously(map, &mut Timer::throwaway());
385 let mut scenario =
386 sim::ScenarioGenerator::proletariat_robot(&map, &mut rng, &mut Timer::throwaway());
387 scenario.scenario_name = scenario_name;
388 scenario.save();
389 println!(
390 "Wrote {}",
391 abstio::path_scenario(&scenario.map_name, &scenario.scenario_name)
392 );
393}
394
395fn import_json_map(input: String, output: String) {
396 let mut map: map_model::Map = abstio::read_json(input, &mut Timer::throwaway());
398 map.map_loaded_directly(&mut Timer::throwaway());
399 abstio::write_binary(output, &map);
400}
401
402fn minify_map(path: String) {
403 let mut timer = Timer::new("minify map");
404 let mut map = map_model::Map::load_synchronously(path, &mut timer);
405 map.minify(&mut timer);
406 map.save();
408}
409
410fn regenerate_everything_externally() -> Result<()> {
411 let path = "regenerate.sh";
412 let mut f = File::create(path)?;
413 writeln!(f, "#!/bin/sh")?;
414 writeln!(f, "pueue parallel 16")?;
415 for city in CityName::list_all_cities_from_importer_config() {
416 if city == CityName::new("gb", "london") {
417 for map in city.list_all_maps_in_city_from_importer_config() {
419 writeln!(
420 f,
421 "pueue add -- ./import.sh --raw --map --scenario {} --city=gb/london",
422 map.map
423 )?;
424 }
425 continue;
426 }
427
428 let job = Job::full_for_city(city);
429 writeln!(f, "pueue add -- ./import.sh {}", job.flags().join(" "))?;
430 }
431 println!("");
432 println!(
433 "You can run {}. You'll need https://github.com/Nukesor/pueue set up first",
434 path
435 );
436 println!("Handy reminders: pueue status / pause / reset");
437 println!("pueue status | grep Success | wc -l");
438 println!("For the long-tail: pueue status | grep Running");
439 Ok(())
440}
441
442fn prebake_scenario(path: String) {
443 let mut timer = Timer::new("prebake scenario");
444 let scenario: synthpop::Scenario = abstio::must_read_object(path, &mut timer);
445 let map = map_model::Map::load_synchronously(scenario.map_name.path(), &mut timer);
446 sim::prebake::prebake(&map, scenario, &mut timer);
447}