Skip to content

Commit

Permalink
Refactor the logic of distributing N people to K buildings. This was
Browse files Browse the repository at this point in the history
duplicated between the Berlin importer and the new census-based popdat
crate, and I suspect some form of it might get used for the actdev
integration. #424
  • Loading branch information
dabreegster committed Jan 13, 2021
1 parent 38fa22d commit d49c6c5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 80 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions importer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ anyhow = "1.0.37"
collisions = { path = "../collisions" }
convert_osm = { path = "../convert_osm" }
csv = "1.1.4"
geo = "0.16.0"
geojson = "0.21.0"
geom = { path = "../geom" }
gdal = { version = "0.7.0", optional = true }
kml = { path = "../kml" }
map_model = { path = "../map_model" }
popdat = { path = "../popdat" }
rand = "0.8.1"
rand_xorshift = "0.3.0"
serde = "1.0.116"
Expand Down
50 changes: 11 additions & 39 deletions importer/src/berlin.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::fs::File;

use rand::{Rng, SeedableRng};
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use serde::Deserialize;

use abstutil::{prettyprint_usize, Timer};
use geom::{Polygon, Ring};
use abstutil::Timer;
use geom::Ring;
use kml::ExtraShapes;
use map_model::raw::RawMap;
use map_model::BuildingType;
Expand Down Expand Up @@ -86,45 +86,17 @@ pub fn distribute_residents(map: &mut map_model::Map, timer: &mut Timer) {
continue;
}
let region = Ring::must_new(pts).to_polygon();
let bldgs: Vec<map_model::BuildingID> = map
.all_buildings()
.into_iter()
.filter(|b| region.contains_pt(b.label_center) && b.bldg_type.has_residents())
.map(|b| b.id)
.collect();
let orig_num_residents = shape.attributes["num_residents"].parse::<f64>().unwrap();

// If the region is partly out-of-bounds, then scale down the number of residents linearly
// based on area of the overlapping part of the polygon.
let pct_overlap = Polygon::union_all(region.intersection(map.get_boundary_polygon()))
.area()
/ region.area();
let num_residents = (pct_overlap * orig_num_residents) as usize;
timer.note(format!(
"Distributing {} residents in {} to {} buildings. {}% of this area overlapped with \
the map, scaled residents accordingly.",
prettyprint_usize(num_residents),
shape.attributes["spatial_alias"],
prettyprint_usize(bldgs.len()),
(pct_overlap * 100.0) as usize
));

// Deterministically seed using the planning area's ID.
let mut rng =
XorShiftRng::seed_from_u64(shape.attributes["spatial_name"].parse::<u64>().unwrap());

// How do you randomly distribute num_residents into some buildings?
// https://stackoverflow.com/questions/2640053/getting-n-random-numbers-whose-sum-is-m
// TODO Problems:
// - Because of how we round, the sum might not exactly be num_residents
// - This is not a uniform distribution, per stackoverflow
// - Larger buildings should get more people

let mut rand_nums: Vec<f64> = (0..bldgs.len()).map(|_| rng.gen_range(0.0..1.0)).collect();
let sum: f64 = rand_nums.iter().sum();
for b in bldgs {
let n = (rand_nums.pop().unwrap() / sum * (num_residents as f64)) as usize;
let bldg_type = match map.get_b(b).bldg_type {
for (home, n) in popdat::distribute_population_to_homes(
geo::Polygon::from(region),
shape.attributes["num_residents"].parse::<usize>().unwrap(),
map,
&mut rng,
) {
let bldg_type = match map.get_b(home).bldg_type {
BuildingType::Residential {
num_housing_units, ..
} => BuildingType::Residential {
Expand All @@ -136,7 +108,7 @@ pub fn distribute_residents(map: &mut map_model::Map, timer: &mut Timer) {
}
_ => unreachable!(),
};
map.hack_override_bldg_type(b, bldg_type);
map.hack_override_bldg_type(home, bldg_type);
}
}

Expand Down
93 changes: 52 additions & 41 deletions popdat/src/distribute_people.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rand::Rng;
use rand_xorshift::XorShiftRng;

use abstutil::prettyprint_usize;
use map_model::Map;
use map_model::{BuildingID, Map};

use crate::{CensusArea, CensusPerson, Config};

Expand All @@ -14,48 +14,11 @@ pub fn assign_people_to_houses(
_config: &Config,
) -> Vec<CensusPerson> {
let mut people = Vec::new();

let map_boundary = geo::Polygon::from(map.get_boundary_polygon().clone());
for area in areas {
let bldgs: Vec<map_model::BuildingID> = map
.all_buildings()
.into_iter()
.filter(|b| {
area.polygon.contains(&geo::Point::from(b.label_center))
&& b.bldg_type.has_residents()
})
.map(|b| b.id)
.collect();

// If the area is partly out-of-bounds, then scale down the number of residents linearly
// based on area of the overlapping part of the polygon.
use geo_booleanop::boolean::BooleanOp;
let pct_overlap =
area.polygon.intersection(&map_boundary).unsigned_area() / area.polygon.unsigned_area();
let num_residents = (pct_overlap * (area.population as f64)) as usize;
debug!(
"Distributing {} residents to {} buildings. {}% of this area overlapped with the map, \
scaled residents accordingly.",
prettyprint_usize(num_residents),
prettyprint_usize(bldgs.len()),
(pct_overlap * 100.0) as usize
);

// How do you randomly distribute num_residents into some buildings?
// https://stackoverflow.com/questions/2640053/getting-n-random-numbers-whose-sum-is-m
// TODO Problems:
// - Because of how we round, the sum might not exactly be num_residents
// - This is not a uniform distribution, per stackoverflow
// - Larger buildings should get more people

let mut rand_nums: Vec<f64> = (0..bldgs.len()).map(|_| rng.gen_range(0.0..1.0)).collect();
let sum: f64 = rand_nums.iter().sum();
for b in bldgs {
let n = (rand_nums.pop().unwrap() / sum * (num_residents as f64)) as usize;

for (home, n) in distribute_population_to_homes(area.polygon, area.population, map, rng) {
for _ in 0..n {
people.push(CensusPerson {
home: b,
home,
// TODO Making this up for now. We can either move this to Config or see if we
// can extract it from the census. Also, not even sure which of these
// attributes are useful later in the pipeline.
Expand All @@ -66,6 +29,54 @@ pub fn assign_people_to_houses(
}
}
}

people
}

/// Starting from some number of total people living in a polygonal area, randomly distribute them
/// to residential buildings within that area. Returns a list of homes with the number of residents
/// in each.
pub fn distribute_population_to_homes(
polygon: geo::Polygon<f64>,
population: usize,
map: &Map,
rng: &mut XorShiftRng,
) -> Vec<(BuildingID, usize)> {
let map_boundary = geo::Polygon::from(map.get_boundary_polygon().clone());
let bldgs: Vec<map_model::BuildingID> = map
.all_buildings()
.into_iter()
.filter(|b| {
polygon.contains(&geo::Point::from(b.label_center)) && b.bldg_type.has_residents()
})
.map(|b| b.id)
.collect();

// If the area is partly out-of-bounds, then scale down the number of residents linearly
// based on area of the overlapping part of the polygon.
use geo_booleanop::boolean::BooleanOp;
let pct_overlap = polygon.intersection(&map_boundary).unsigned_area() / polygon.unsigned_area();
let num_residents = (pct_overlap * (population as f64)) as usize;
debug!(
"Distributing {} residents to {} buildings. {}% of this area overlapped with the map, \
scaled residents accordingly.",
prettyprint_usize(num_residents),
prettyprint_usize(bldgs.len()),
(pct_overlap * 100.0) as usize
);

// How do you randomly distribute num_residents into some buildings?
// https://stackoverflow.com/questions/2640053/getting-n-random-numbers-whose-sum-is-m
// TODO Problems:
// - Because of how we round, the sum might not exactly be num_residents
// - This is not a uniform distribution, per stackoverflow
// - Larger buildings should get more people

let mut count_per_home = Vec::new();
let mut rand_nums: Vec<f64> = (0..bldgs.len()).map(|_| rng.gen_range(0.0..1.0)).collect();
let sum: f64 = rand_nums.iter().sum();
for b in bldgs {
let n = (rand_nums.pop().unwrap() / sum * (num_residents as f64)) as usize;
count_per_home.push((b, n));
}
count_per_home
}
2 changes: 2 additions & 0 deletions popdat/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use geom::{Distance, Time};
use map_model::{BuildingID, Map};
use sim::Scenario;

pub use self::distribute_people::distribute_population_to_homes;

mod activities;
mod distribute_people;
mod import_census;
Expand Down

0 comments on commit d49c6c5

Please sign in to comment.