importer/
pick_geofabrik.rs1use std::convert::TryInto;
2
3use anyhow::{bail, Result};
4use geo::{Area, Contains};
5use geojson::GeoJson;
6
7use abstutil::Timer;
8
9pub async fn pick_geofabrik(input: String) -> Result<(String, String)> {
12 let boundary = load_boundary(input)?;
13
14 let geofabrik_idx = load_remote_geojson(
15 abstio::path_shared_input("geofabrik-index.json"),
16 "https://download.geofabrik.de/index-v1.json",
17 )
18 .await?;
19 let matches = find_matching_regions(geofabrik_idx, boundary);
20 info!("{} regions contain boundary", matches.len(),);
21 let (_, url) = matches
23 .into_iter()
24 .min_by_key(|(mp, _)| mp.unsigned_area() as usize)
25 .unwrap();
26
27 let basename = url
30 .strip_prefix("https://download.geofabrik.de/")
31 .expect("Geofabrik URLs changed");
32 let local = abstio::path_shared_input(format!("geofabrik/{basename}"));
33
34 Ok((url, local))
35}
36
37fn load_boundary(path: String) -> Result<geo::Polygon> {
38 let gj: GeoJson = abstio::maybe_read_json(path, &mut Timer::throwaway())?;
39 let mut features = match gj {
40 GeoJson::Feature(feature) => vec![feature],
41 GeoJson::FeatureCollection(feature_collection) => feature_collection.features,
42 _ => bail!("Unexpected geojson: {:?}", gj),
43 };
44 if features.len() != 1 {
45 bail!("Expected exactly 1 feature");
46 }
47 let poly: geo::Polygon = features
48 .pop()
49 .unwrap()
50 .geometry
51 .take()
52 .unwrap()
53 .value
54 .try_into()
55 .unwrap();
56 Ok(poly)
57}
58
59async fn load_remote_geojson(path: String, url: &str) -> Result<GeoJson> {
60 if !abstio::file_exists(&path) {
61 info!("Downloading {}", url);
62 abstio::download_to_file(url, None, &path).await?;
63 }
64 abstio::maybe_read_json(path, &mut Timer::throwaway())
65}
66
67fn find_matching_regions(
68 geojson: GeoJson,
69 boundary: geo::Polygon,
70) -> Vec<(geo::MultiPolygon, String)> {
71 let mut matches = Vec::new();
72
73 if let GeoJson::FeatureCollection(fc) = geojson {
77 info!("Searching {} regions", fc.features.len());
78 for mut feature in fc.features {
79 let mp: geo::MultiPolygon = feature.geometry.take().unwrap().value.try_into().unwrap();
80 if mp.contains(&boundary) {
81 matches.push((
82 mp,
83 feature
84 .property("urls")
85 .unwrap()
86 .get("pbf")
87 .unwrap()
88 .as_str()
89 .unwrap()
90 .to_string(),
91 ));
92 }
93 }
94 }
95
96 matches
97}