diff --git a/data_prep/scotland/src/bin/density_buckets.rs b/data_prep/scotland/src/bin/density_buckets.rs new file mode 100644 index 0000000..866a356 --- /dev/null +++ b/data_prep/scotland/src/bin/density_buckets.rs @@ -0,0 +1,28 @@ +use data_prep::PopulationZoneInput; + +/// Print the Quintile boundaries for all of Scotland's population zones +fn main() { + let mut population_zones = PopulationZoneInput::read_all_from_file().unwrap(); + population_zones.sort_by(|zone_a, zone_b| { + zone_a + .density_per_km2() + .partial_cmp(&zone_b.density_per_km2()) + .unwrap() + }); + + let buckets = 5; + let bucket_width = population_zones.len() / buckets; + + let limits: Vec<_> = (0..=buckets) + .map(|bucket| { + let limit_idx = bucket * bucket_width; + population_zones[limit_idx] + .density_per_km2() + .round() as u64 + }) + .collect(); + + println!("most_dense: {}", population_zones.iter().rev().next().unwrap().density_per_km2()); + // > Raw limits for Scotland density (/ km²): [0, 1324, 2940, 4247, 5858, 52389] + println!("Raw limits for Scotland density (/ km²): {limits:?}"); +} diff --git a/data_prep/scotland/src/bin/generate_prioritization.rs b/data_prep/scotland/src/bin/generate_prioritization.rs index 14bf109..ea3c870 100644 --- a/data_prep/scotland/src/bin/generate_prioritization.rs +++ b/data_prep/scotland/src/bin/generate_prioritization.rs @@ -1,7 +1,7 @@ use anyhow::Result; use backend::boundary_stats::{ContextData, POIKind, PopulationZone, POI}; -use data_prep::StudyArea; -use geo::{MultiPolygon, Point, PreparedGeometry, Relate}; +use data_prep::{PopulationZoneInput, StudyArea}; +use geo::{Point, Relate}; use serde::Deserialize; use std::time::Instant; @@ -11,7 +11,7 @@ fn main() -> Result<()> { let study_areas = StudyArea::read_all_prepared_from_file()?; println!("Time since start {:?}", start.elapsed()); - let population_zone_inputs = PopulationZoneInput::read_all_prepared_from_file()?; + let population_zone_inputs = PopulationZoneInput::read_all_from_file()?; println!("Time since start {:?}", start.elapsed()); let stats19_collisions = Stats19Input::read_all_from_file()?; let pois = InputPOI::read_all_from_files()?; @@ -28,13 +28,13 @@ fn main() -> Result<()> { for population_zone_input in &population_zone_inputs { if study_area .0 - .relate(&population_zone_input.0) + .relate(&population_zone_input.geometry) .is_intersects() { context_data.population_zones.push(PopulationZone { - geometry: population_zone_input.1.geometry.clone(), - imd_percentile: population_zone_input.1.imd_percentile, - population: population_zone_input.1.population, + geometry: population_zone_input.geometry.geometry().clone(), + imd_percentile: population_zone_input.imd_percentile, + population: population_zone_input.population, }); } } @@ -69,49 +69,6 @@ fn main() -> Result<()> { Ok(()) } -#[derive(Clone, Debug, Deserialize)] -struct PopulationZoneInput { - #[serde(deserialize_with = "geojson::de::deserialize_geometry")] - geometry: MultiPolygon, - - // "id": "S01006506", - // (unused) - - // "imd_rank": 4691, - // (unused) - - // "imd_percentile": 68, - imd_percentile: u8, - - // "population": 894, - population: u32, - // "area": 4388802.1221970674 - // (unused - though maybe we would find it helpful for pre-computing density or to save the cost of calculating area live) -} - -impl PopulationZoneInput { - fn read_all_from_file() -> Result> { - let population_zones = geojson::de::deserialize_feature_collection_str_to_vec( - &fs_err::read_to_string("tmp/population.geojson")?, - )?; - println!("Read {} population zones", population_zones.len()); - Ok(population_zones) - } - - fn read_all_prepared_from_file() -> Result, Self)>> - { - let iter = Self::read_all_from_file()? - .into_iter() - .map(|population_zone| { - ( - PreparedGeometry::from(population_zone.geometry.clone()), - population_zone, - ) - }); - Ok(iter.collect()) - } -} - // Ignore all properties #[derive(Deserialize)] struct Stats19Input { diff --git a/data_prep/scotland/src/lib.rs b/data_prep/scotland/src/lib.rs index f2b456e..7d4f3a1 100644 --- a/data_prep/scotland/src/lib.rs +++ b/data_prep/scotland/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::Result; use geo::{MultiPolygon, PreparedGeometry}; -use serde::Deserialize; +use serde::{Deserialize, Deserializer}; #[derive(Deserialize)] pub struct StudyArea { @@ -29,3 +29,52 @@ impl StudyArea { Ok(iter.collect()) } } + +#[derive(Deserialize)] +pub struct PopulationZoneInput { + #[serde(deserialize_with = "deserialize_prepared_multipolygon")] + pub geometry: PreparedGeometry<'static, MultiPolygon>, + + // "id": "S01006506", + // (unused) + + // "imd_rank": 4691, + // (unused) + + // "imd_percentile": 68, + pub imd_percentile: u8, + + // "population": 894, + pub population: u32, + + // "area": 4388802.1221970674 + pub area: f64, +} + +pub fn deserialize_prepared_multipolygon<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let multi_polygon: MultiPolygon = geojson::de::deserialize_geometry(deserializer)?; + Ok(PreparedGeometry::from(multi_polygon)) +} + +impl PopulationZoneInput { + pub fn read_all_from_file() -> Result> { + let population_zones = geojson::de::deserialize_feature_collection_str_to_vec( + &fs_err::read_to_string("tmp/population.geojson")?, + )?; + println!("Read {} population zones", population_zones.len()); + Ok(population_zones) + } + + pub fn area_km2(&self) -> f64 { + self.area / 1000.0 / 1000.0 + } + + pub fn density_per_km2(&self) -> f64 { + self.population as f64 / self.area_km2() + } +} diff --git a/web/src/common/colors.ts b/web/src/common/colors.ts index b7abb8d..35462e4 100644 --- a/web/src/common/colors.ts +++ b/web/src/common/colors.ts @@ -22,8 +22,15 @@ export let simdColorScale = commonQuintileColorScale; export let simdLimits = [0, 20, 40, 60, 80, 100]; export let densityColorScale = commonQuintileColorScale.toReversed(); -// Use the same (slightly rounded) buckets as https://www.ons.gov.uk/census/maps/choropleth/population/population-density/population-density/persons-per-square-kilometre. TODO Adapt for Scotland. -export let densityLimits = [0, 4700, 13000, 33000, 94000, 1980000]; + +// To get raw quintiles, run: +// +// cd data_prep/scotland && cargo run --bin density_buckets +// > Raw limits for Scotland density (/ km²): [0, 1324, 2940, 4247, 5858, 52389] +// +// Slightly round those raw numbers: +export let densityLimits = [0, 1_300, 3_000, 4_200, 5_900, 52_000] + export let demandColorScale = commonQuintileColorScale.toReversed();