Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tune population density quintiles for Scotland #219

Merged
merged 1 commit into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions data_prep/scotland/src/bin/density_buckets.rs
Original file line number Diff line number Diff line change
@@ -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:?}");
}
57 changes: 7 additions & 50 deletions data_prep/scotland/src/bin/generate_prioritization.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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()?;
Expand All @@ -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,
});
}
}
Expand Down Expand Up @@ -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<Vec<Self>> {
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<Vec<(PreparedGeometry<'static, MultiPolygon>, 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 {
Expand Down
51 changes: 50 additions & 1 deletion data_prep/scotland/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use geo::{MultiPolygon, PreparedGeometry};
use serde::Deserialize;
use serde::{Deserialize, Deserializer};

#[derive(Deserialize)]
pub struct StudyArea {
Expand Down Expand Up @@ -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<PreparedGeometry<'static, MultiPolygon>, 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<Vec<Self>> {
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()
}
}
11 changes: 9 additions & 2 deletions web/src/common/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down