importer/
lib.rs

1//! It's assumed that the importer is run with the current directory as the project repository; aka
2//! `./data/` and `./importer/config` must exist.
3
4// Disable some noisy clippy lints
5#![allow(clippy::type_complexity)]
6
7#[macro_use]
8extern crate anyhow;
9#[macro_use]
10extern crate log;
11
12use structopt::StructOpt;
13
14use abstio::{CityName, MapName};
15use abstutil::Timer;
16use map_model::RawToMapOptions;
17
18pub use self::configuration::ImporterConfiguration;
19pub use self::pick_geofabrik::pick_geofabrik;
20pub use utils::osmium;
21
22mod berlin;
23mod configuration;
24mod map_config;
25mod pick_geofabrik;
26mod seattle;
27mod soundcast;
28mod uk;
29mod utils;
30
31/// Regenerate all maps and scenarios from scratch.
32pub async fn regenerate_everything(shard_num: usize, num_shards: usize) {
33    // Discover all cities by looking at config. But always operate on Seattle first. Special
34    // treatment ;)
35    let mut all_cities = CityName::list_all_cities_from_importer_config();
36    all_cities.retain(|x| x != &CityName::seattle());
37    all_cities.insert(0, CityName::seattle());
38
39    let mut timer = Timer::new("regenerate all maps");
40    for (cnt, city) in all_cities.into_iter().enumerate() {
41        if cnt % num_shards == shard_num {
42            let job = Job::full_for_city(city);
43            job.run(&mut timer).await;
44        }
45    }
46}
47
48/// Transforms a .osm.xml or .pbf file to a map in one step.
49pub async fn oneshot(
50    osm_path: String,
51    clip: Option<String>,
52    options: convert_osm::Options,
53    create_uk_travel_demand_model: bool,
54    opts: RawToMapOptions,
55) {
56    let mut timer = abstutil::Timer::new("oneshot");
57    println!("- Running convert_osm on {}", osm_path);
58    let name = abstutil::basename(&osm_path);
59    let raw = convert_osm::convert(
60        osm_path,
61        MapName::new("zz", "oneshot", &name),
62        clip,
63        options,
64        &mut timer,
65    );
66    // Often helpful to save intermediate representation in case user wants to load into map_editor
67    raw.save();
68    let map = map_model::Map::create_from_raw(raw, opts, &mut timer);
69    timer.start("save map");
70    map.save();
71    timer.stop("save map");
72
73    if create_uk_travel_demand_model {
74        timer.start("generating UK travel demand model");
75        uk::generate_scenario(&map, &ImporterConfiguration::load(), &mut timer)
76            .await
77            .unwrap();
78        timer.stop("generating UK travel demand model");
79    }
80
81    println!("{} has been created", map.get_name().path());
82}
83
84/// A specification for importing all maps in a single city.
85#[derive(StructOpt)]
86pub struct Job {
87    #[structopt(long, parse(try_from_str = CityName::parse), default_value = "us/seattle")]
88    pub city: CityName,
89    /// Download all raw input files, then convert OSM to the intermediate RawMap.
90    #[structopt(long = "--raw")]
91    pub osm_to_raw: bool,
92    /// Convert the RawMap to the final Map format.
93    #[structopt(long = "--map")]
94    pub raw_to_map: bool,
95    /// Download trip demand data, then produce the typical weekday scenario.
96    #[structopt(long)]
97    pub scenario: bool,
98    /// Produce a city overview from all of the individual maps in a city.
99    #[structopt(long)]
100    pub city_overview: bool,
101
102    /// Only process one map. If not specified, process all maps defined by clipping polygons in
103    /// importer/config/$city/.
104    #[structopt()]
105    pub only_map: Option<String>,
106
107    #[structopt(flatten)]
108    pub opts: RawToMapOptions,
109}
110
111impl Job {
112    pub fn full_for_city(city: CityName) -> Job {
113        let mut job = Job {
114            city: city,
115            osm_to_raw: true,
116            raw_to_map: true,
117            scenario: false,
118            city_overview: false,
119            only_map: None,
120            opts: RawToMapOptions::default(),
121        };
122        // Only some maps run extra tasks
123        if job.city == CityName::seattle() || job.city.country == "gb" {
124            job.scenario = true;
125        }
126        // TODO Autodetect this based on number of maps per city?
127        if job.city == CityName::new("ch", "zurich")
128            || job.city == CityName::new("gb", "leeds")
129            || job.city == CityName::new("gb", "london")
130            || job.city == CityName::new("us", "nyc")
131            || job.city == CityName::new("fr", "charleville_mezieres")
132            || job.city == CityName::new("fr", "paris")
133            || job.city == CityName::new("at", "salzburg")
134            || job.city == CityName::new("ir", "tehran")
135            || job.city == CityName::new("pt", "portugal")
136        {
137            job.city_overview = true;
138        }
139        job
140    }
141
142    /// Return the command-line flags that should produce this job. Incomplete -- doesn't invert
143    /// RawToMapOptions
144    pub fn flags(&self) -> Vec<String> {
145        // TODO Can structopt do the inversion?
146        let mut flags = vec![];
147        flags.push(format!("--city={}", self.city.to_path()));
148        if self.osm_to_raw {
149            flags.push("--raw".to_string());
150        }
151        if self.raw_to_map {
152            flags.push("--map".to_string());
153        }
154        if self.scenario {
155            flags.push("--scenario".to_string());
156        }
157        if self.city_overview {
158            flags.push("--city-overview".to_string());
159        }
160        if let Some(ref name) = self.only_map {
161            flags.push(name.clone());
162        }
163        flags
164    }
165
166    pub async fn run(self, timer: &mut Timer<'_>) {
167        if !self.osm_to_raw && !self.raw_to_map && !self.scenario && !self.city_overview {
168            println!(
169                "Nothing to do! Pass some combination of --raw, --map, --scenario, or --city_overview"
170            );
171            std::process::exit(1);
172        }
173
174        let config = ImporterConfiguration::load();
175
176        timer.start(format!("import {}", self.city.describe()));
177        let names = if let Some(n) = self.only_map {
178            println!("- Just working on {}", n);
179            vec![MapName::from_city(&self.city, &n)]
180        } else {
181            println!("- Working on all {} maps", self.city.describe());
182            self.city.list_all_maps_in_city_from_importer_config()
183        };
184
185        // When regenerating everything, huge_seattle gets created twice! This is expensive enough
186        // to hack in a way to avoid the work.
187        let mut built_raw_huge_seattle = false;
188        let mut built_map_huge_seattle = false;
189        let (maybe_popdat, maybe_huge_map, maybe_zoning_parcels) = if self.scenario
190            && self.city == CityName::seattle()
191        {
192            timer.start("ensure_popdat_exists");
193            let (popdat, huge_map) = seattle::ensure_popdat_exists(
194                timer,
195                &config,
196                &mut built_raw_huge_seattle,
197                &mut built_map_huge_seattle,
198            )
199            .await;
200            // Just assume --raw has been called...
201            let shapes: kml::ExtraShapes =
202                abstio::read_binary(CityName::seattle().input_path("zoning_parcels.bin"), timer);
203            timer.stop("ensure_popdat_exists");
204            (Some(popdat), Some(huge_map), Some(shapes))
205        } else {
206            (None, None, None)
207        };
208
209        for name in names {
210            timer.start(name.describe());
211            if self.osm_to_raw
212                && (!built_raw_huge_seattle || name != MapName::seattle("huge_seattle"))
213            {
214                let raw = utils::osm_to_raw(name.clone(), timer, &config).await;
215
216                // The collision data will only cover one part of London, since we don't have a
217                // region-wide map there yet
218                if name.city == CityName::new("de", "berlin") {
219                    berlin::import_extra_data(&raw, &config, timer).await;
220                } else if name == MapName::new("gb", "leeds", "huge") {
221                    uk::import_collision_data(&raw, &config, timer).await;
222                } else if name == MapName::new("gb", "london", "camden") {
223                    uk::import_collision_data(&raw, &config, timer).await;
224                }
225            }
226
227            let mut maybe_map = if self.raw_to_map {
228                let mut map = if built_map_huge_seattle && name == MapName::seattle("huge_seattle")
229                {
230                    map_model::Map::load_synchronously(name.path(), timer)
231                } else {
232                    utils::raw_to_map(&name, self.opts.clone(), timer)
233                };
234
235                // Another strange step in the pipeline.
236                if name == MapName::new("de", "berlin", "center") {
237                    timer.start(format!(
238                        "distribute residents from planning areas for {}",
239                        name.describe()
240                    ));
241                    berlin::distribute_residents(&mut map, timer);
242                    timer.stop(format!(
243                        "distribute residents from planning areas for {}",
244                        name.describe()
245                    ));
246
247                    map.save();
248                }
249
250                Some(map)
251            } else if self.scenario {
252                Some(map_model::Map::load_synchronously(name.path(), timer))
253            } else {
254                None
255            };
256
257            if self.scenario {
258                if self.city == CityName::seattle() {
259                    timer.start(format!("scenario for {}", name.describe()));
260                    let scenario = soundcast::make_scenario(
261                        "weekday",
262                        maybe_map.as_ref().unwrap(),
263                        maybe_popdat.as_ref().unwrap(),
264                        maybe_huge_map.as_ref().unwrap(),
265                        timer,
266                    );
267                    scenario.save();
268                    timer.stop(format!("scenario for {}", name.describe()));
269
270                    // This is a strange ordering.
271                    if name.map == "downtown"
272                        || name.map == "qa"
273                        || name.map == "south_seattle"
274                        || name.map == "wallingford"
275                    {
276                        timer.start(format!("adjust parking for {}", name.describe()));
277                        seattle::adjust_private_parking(maybe_map.as_mut().unwrap(), &scenario);
278                        timer.stop(format!("adjust parking for {}", name.describe()));
279                    }
280
281                    timer.start("match parcels to buildings");
282                    seattle::match_parcels_to_buildings(
283                        maybe_map.as_mut().unwrap(),
284                        maybe_zoning_parcels.as_ref().unwrap(),
285                        timer,
286                    );
287                    timer.stop("match parcels to buildings");
288
289                    // Even stranger hacks! AFTER generating the scenarios, which requires full
290                    // pathfinding, for a few maps, "minify" them to cut down file size for the
291                    // bike network tool.
292                    if name.map == "central_seattle"
293                        || name.map == "north_seattle"
294                        || name.map == "south_seattle"
295                    {
296                        let map = maybe_map.as_mut().unwrap();
297                        map.minify(timer);
298                        map.save();
299                    }
300                }
301
302                if self.city.country == "gb" {
303                    if name == MapName::new("gb", "london", "central") {
304                        // No scenario for Central London, which has buildings stripped out
305                        let map = maybe_map.as_mut().unwrap();
306                        map.minify_buildings(timer);
307                        map.save();
308                    } else {
309                        uk::generate_scenario(maybe_map.as_ref().unwrap(), &config, timer)
310                            .await
311                            .unwrap();
312                    }
313                }
314            }
315            timer.stop(name.describe());
316        }
317
318        if self.city_overview {
319            timer.start(format!(
320                "generate city overview for {}",
321                self.city.describe()
322            ));
323            abstio::write_binary(
324                abstio::path(format!(
325                    "system/{}/{}/city.bin",
326                    self.city.country, self.city.city
327                )),
328                &map_model::City::from_individual_maps(&self.city, timer),
329            );
330            timer.stop(format!(
331                "generate city overview for {}",
332                self.city.describe()
333            ));
334        }
335
336        timer.stop(format!("import {}", self.city.describe()));
337    }
338}