importer/
seattle.rs

1use std::collections::HashSet;
2
3use abstio::{CityName, MapName};
4use abstutil::Timer;
5use geom::{Polygon, QuadTree, Ring};
6use kml::ExtraShapes;
7use map_model::{BuildingID, BuildingType, Map};
8use sim::count_parked_cars_per_bldg;
9use synthpop::Scenario;
10
11use crate::configuration::ImporterConfiguration;
12use crate::utils::{download, download_kml};
13
14pub async fn input(config: &ImporterConfiguration, timer: &mut Timer<'_>) {
15    let city = CityName::seattle();
16
17    // Soundcast data was originally retrieved from staff at PSRC via a download link that didn't
18    // last long. From that original 2014 .zip (possibly still available from
19    // https://github.com/psrc/soundcast/releases), two files were extracted --
20    // parcels_urbansim.txt and trips_2014.csv. Those are now stored in S3. It's a bit weird for
21    // the importer pipeline to depend on something in data/input in S3, but this should let
22    // anybody run the full pipeline.
23    download(
24        config,
25        city.input_path("parcels_urbansim.txt"),
26        "http://abstreet.s3-website.us-east-2.amazonaws.com/dev/data/input/us/seattle/parcels_urbansim.txt.gz",
27    )
28    .await;
29    download(
30        config,
31        city.input_path("trips_2014.csv"),
32        "http://abstreet.s3-website.us-east-2.amazonaws.com/dev/data/input/us/seattle/trips_2014.csv.gz",
33    )
34    .await;
35
36    let bounds = geom::GPSBounds::from(
37        geom::LonLat::read_geojson_polygon("importer/config/us/seattle/huge_seattle.geojson")
38            .unwrap(),
39    );
40    // From http://data-seattlecitygis.opendata.arcgis.com/datasets/blockface
41    download_kml(
42        city.input_path("blockface.bin"),
43        "https://opendata.arcgis.com/datasets/a1458ad1abca41869b81f7c0db0cd777_0.kml",
44        &bounds,
45        true,
46        timer,
47    )
48    .await;
49    // From https://data-seattlecitygis.opendata.arcgis.com/datasets/public-garages-or-parking-lots
50    download_kml(
51        city.input_path("offstreet_parking.bin"),
52        "http://data-seattlecitygis.opendata.arcgis.com/datasets/8e52dfde6d5d45948f7a90654c8d50cd_0.kml",
53        &bounds,
54        true,
55        timer
56    ).await;
57
58    // From
59    // https://data-seattlecitygis.opendata.arcgis.com/datasets/5b5c745e0f1f48e7a53acec63a0022ab_0
60    download(
61        config,
62        city.input_path("collisions.kml"),
63        "https://opendata.arcgis.com/datasets/5b5c745e0f1f48e7a53acec63a0022ab_0.kml",
64    )
65    .await;
66
67    // This is a little expensive, so delete data/input/us/seattle/collisions.bin to regenerate
68    // this.
69    if !abstio::file_exists(city.input_path("collisions.bin")) {
70        let shapes = kml::load(city.input_path("collisions.kml"), &bounds, true, timer).unwrap();
71        let collisions = collisions::import_seattle(
72            shapes,
73            "https://data-seattlecitygis.opendata.arcgis.com/datasets/5b5c745e0f1f48e7a53acec63a0022ab_0");
74        abstio::write_binary(city.input_path("collisions.bin"), &collisions);
75    }
76
77    // From https://data-seattlecitygis.opendata.arcgis.com/datasets/parcels-1
78    download_kml(
79        city.input_path("zoning_parcels.bin"),
80        "https://opendata.arcgis.com/datasets/42863f1debdc47488a1c2b9edd38053e_2.kml",
81        &bounds,
82        true,
83        timer,
84    )
85    .await;
86
87    // From
88    // https://data-seattlecitygis.opendata.arcgis.com/datasets/current-land-use-zoning-detail
89    download_kml(
90        city.input_path("land_use.bin"),
91        "https://opendata.arcgis.com/datasets/dd29065b5d01420e9686570c2b77502b_0.kml",
92        &bounds,
93        false,
94        timer,
95    )
96    .await;
97}
98
99/// Download and pre-process data needed to generate Seattle scenarios.
100pub async fn ensure_popdat_exists(
101    timer: &mut Timer<'_>,
102    config: &ImporterConfiguration,
103    built_raw_huge_seattle: &mut bool,
104    built_map_huge_seattle: &mut bool,
105) -> (crate::soundcast::PopDat, map_model::Map) {
106    let huge_name = MapName::seattle("huge_seattle");
107
108    if abstio::file_exists(abstio::path_popdat()) {
109        println!("- {} exists, not regenerating it", abstio::path_popdat());
110        return (
111            abstio::read_binary(abstio::path_popdat(), timer),
112            map_model::Map::load_synchronously(huge_name.path(), timer),
113        );
114    }
115
116    if !abstio::file_exists(abstio::path_raw_map(&huge_name)) {
117        crate::utils::osm_to_raw(MapName::seattle("huge_seattle"), timer, config).await;
118        *built_raw_huge_seattle = true;
119    }
120    let huge_map = if abstio::file_exists(huge_name.path()) {
121        map_model::Map::load_synchronously(huge_name.path(), timer)
122    } else {
123        *built_map_huge_seattle = true;
124        crate::utils::raw_to_map(&huge_name, map_model::RawToMapOptions::default(), timer)
125    };
126
127    (crate::soundcast::import_data(&huge_map, timer), huge_map)
128}
129
130pub fn adjust_private_parking(map: &mut Map, scenario: &Scenario) {
131    for (b, count) in count_parked_cars_per_bldg(scenario).consume() {
132        map.hack_override_offstreet_spots_individ(b, count);
133    }
134    map.save();
135}
136
137/// Match OSM buildings to parcels, scraping the number of housing units.
138// TODO It's expensive to load the huge zoning_parcels.bin file for every map.
139pub fn match_parcels_to_buildings(map: &mut Map, shapes: &ExtraShapes, timer: &mut Timer) {
140    let mut parcels_with_housing: Vec<(Polygon, usize)> = Vec::new();
141    // TODO We should refactor something like FindClosest, but for polygon containment
142    // The quadtree's ID is just an index into parcels_with_housing.
143    let mut quadtree = QuadTree::builder();
144    timer.start_iter("index all parcels", shapes.shapes.len());
145    for shape in &shapes.shapes {
146        timer.next();
147        if let Some(units) = shape
148            .attributes
149            .get("EXIST_UNITS")
150            .and_then(|x| x.parse::<usize>().ok())
151        {
152            if let Some(ring) = map
153                .get_gps_bounds()
154                .try_convert(&shape.points)
155                .and_then(|pts| Ring::new(pts).ok())
156            {
157                let polygon = ring.into_polygon();
158                quadtree.add_with_box(parcels_with_housing.len(), polygon.get_bounds());
159                parcels_with_housing.push((polygon, units));
160            }
161        }
162    }
163    let quadtree = quadtree.build();
164
165    let mut used_parcels: HashSet<usize> = HashSet::new();
166    let mut units_per_bldg: Vec<(BuildingID, usize)> = Vec::new();
167    timer.start_iter("match buildings to parcels", map.all_buildings().len());
168    for b in map.all_buildings() {
169        timer.next();
170        // If multiple parcels contain a building's center, just pick one arbitrarily
171        for idx in quadtree.query_bbox(b.polygon.get_bounds()) {
172            if used_parcels.contains(&idx)
173                || !parcels_with_housing[idx].0.contains_pt(b.label_center)
174            {
175                continue;
176            }
177            used_parcels.insert(idx);
178            units_per_bldg.push((b.id, parcels_with_housing[idx].1));
179        }
180    }
181
182    for (b, num_housing_units) in units_per_bldg {
183        let bldg_type = match map.get_b(b).bldg_type.clone() {
184            BuildingType::Residential { num_residents, .. } => BuildingType::Residential {
185                num_housing_units,
186                num_residents,
187            },
188            x => x,
189        };
190        map.hack_override_bldg_type(b, bldg_type);
191    }
192
193    map.save();
194}