importer/
berlin.rs

1use fs_err::File;
2use rand::SeedableRng;
3use rand_xorshift::XorShiftRng;
4use serde::Deserialize;
5
6use abstutil::Timer;
7use geom::Ring;
8use kml::ExtraShapes;
9use map_model::BuildingType;
10use raw_map::RawMap;
11
12use crate::configuration::ImporterConfiguration;
13use crate::utils::{download, download_kml};
14
15pub async fn import_extra_data(
16    map: &RawMap,
17    config: &ImporterConfiguration,
18    timer: &mut Timer<'_>,
19) {
20    // From https://data.technologiestiftung-berlin.de/dataset/lor_planungsgraeume/en
21    download_kml(
22        map.get_city_name().input_path("planning_areas.bin"),
23        "https://tsb-opendata.s3.eu-central-1.amazonaws.com/lor_planungsgraeume/lor_planungsraeume.kml",
24        &map.streets.gps_bounds,
25        // Keep partly out-of-bounds polygons
26        false,
27        timer
28    ).await;
29
30    // From
31    // https://daten.berlin.de/datensaetze/einwohnerinnen-und-einwohner-berlin-lor-planungsr%C3%A4umen-am-31122018
32    download(
33        config,
34        map.get_city_name().input_path("EWR201812E_Matrix.csv"),
35        "https://www.statistik-berlin-brandenburg.de/opendata/EWR201812E_Matrix.csv",
36    )
37    .await;
38
39    // Always do this, it's idempotent and fast
40    correlate_population(
41        map.get_city_name().input_path("planning_areas.bin"),
42        map.get_city_name().input_path("EWR201812E_Matrix.csv"),
43        timer,
44    );
45}
46
47// Modify the filtered KML of planning areas with the number of residents from a different dataset.
48fn correlate_population(kml_path: String, csv_path: String, timer: &mut Timer) {
49    let mut shapes = abstio::read_binary::<ExtraShapes>(kml_path.clone(), timer);
50    for rec in csv::ReaderBuilder::new()
51        .delimiter(b';')
52        .from_reader(File::open(csv_path).unwrap())
53        .deserialize()
54    {
55        let rec: Record = rec.unwrap();
56        for shape in &mut shapes.shapes {
57            if shape.attributes.get("spatial_name") == Some(&rec.raumid) {
58                shape
59                    .attributes
60                    .insert("num_residents".to_string(), rec.e_e);
61                break;
62            }
63        }
64    }
65    abstio::write_binary(kml_path, &shapes);
66}
67
68#[derive(Debug, Deserialize)]
69struct Record {
70    // Corresponds with spatial_name from planning_areas
71    #[serde(rename = "RAUMID")]
72    raumid: String,
73    // The total residents in that area
74    #[serde(rename = "E_E")]
75    e_e: String,
76}
77
78pub fn distribute_residents(map: &mut map_model::Map, timer: &mut Timer) {
79    for shape in abstio::read_binary::<ExtraShapes>(
80        "data/input/de/berlin/planning_areas.bin".to_string(),
81        timer,
82    )
83    .shapes
84    {
85        let pts = map.get_gps_bounds().convert(&shape.points);
86        if pts
87            .iter()
88            .all(|pt| !map.get_boundary_polygon().contains_pt(*pt))
89        {
90            continue;
91        }
92        let region = Ring::must_new(pts).into_polygon();
93        // Deterministically seed using the planning area's ID.
94        let mut rng =
95            XorShiftRng::seed_from_u64(shape.attributes["spatial_name"].parse::<u64>().unwrap());
96
97        for (home, n) in popdat::distribute_population_to_homes(
98            geo::Polygon::from(region),
99            shape.attributes["num_residents"].parse::<usize>().unwrap(),
100            map,
101            &mut rng,
102        ) {
103            let bldg_type = match map.get_b(home).bldg_type {
104                BuildingType::Residential {
105                    num_housing_units, ..
106                } => BuildingType::Residential {
107                    num_housing_units,
108                    num_residents: n,
109                },
110                BuildingType::ResidentialCommercial(_, worker_cap) => {
111                    BuildingType::ResidentialCommercial(n, worker_cap)
112                }
113                _ => unreachable!(),
114            };
115            map.hack_override_bldg_type(home, bldg_type);
116        }
117    }
118
119    map.save();
120}