diff --git a/backend/src/graph/mod.rs b/backend/src/graph/mod.rs index cef7e82..126b8a1 100644 --- a/backend/src/graph/mod.rs +++ b/backend/src/graph/mod.rs @@ -8,7 +8,7 @@ mod transit_route; use anyhow::Result; use enum_map::{Enum, EnumMap}; use geo::{Coord, LineLocatePoint, LineString, MultiPolygon, Point, Polygon}; -use geojson::{Feature, GeoJson, Geometry}; +use geojson::{Feature, FeatureCollection, GeoJson, Geometry}; use rstar::{primitives::GeomWithData, RTree}; use serde::{Deserialize, Serialize}; use utils::Mercator; @@ -190,12 +190,27 @@ impl Graph { /// Returns a GeoJSON string pub fn render_zones(&self) -> Result { let mut features = Vec::new(); + let mut max_density: f64 = 0.0; for zone in &self.zones { let mut f = Feature::from(Geometry::from(&self.mercator.to_wgs84(&zone.geom))); f.set_property("population", zone.population); + f.set_property("density", zone.density); features.push(f); + + max_density = max_density.max(zone.density); } - Ok(serde_json::to_string(&GeoJson::from(features))?) + Ok(serde_json::to_string(&FeatureCollection { + features, + bbox: None, + foreign_members: Some( + serde_json::json!({ + "max_density": max_density, + }) + .as_object() + .unwrap() + .clone(), + ), + })?) } } @@ -257,4 +272,6 @@ pub struct Zone { pub geom: MultiPolygon, // TODO Later on, this could be generic or user-supplied pub population: u32, + // People per square km + pub density: f64, } diff --git a/backend/src/graph/scrape.rs b/backend/src/graph/scrape.rs index aededb7..4dec7e4 100644 --- a/backend/src/graph/scrape.rs +++ b/backend/src/graph/scrape.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use anyhow::Result; use enum_map::EnumMap; use flatgeobuf::{FeatureProperties, FgbFeature, GeozeroGeometry, HttpFgbReader}; -use geo::{Coord, EuclideanLength, MultiPolygon}; +use geo::{Area, Coord, EuclideanLength, MultiPolygon}; use muv_osm::{AccessLevel, TMode}; use osm_reader::OsmID; use rstar::RTree; @@ -303,10 +303,14 @@ async fn load_zones(url: String, mercator: &Mercator) -> Result> { // TODO Could intersect with boundary_polygon, but some extras nearby won't hurt anything let mut geom = get_multipolygon(feature)?; mercator.to_mercator_in_place(&mut geom); + let area_km2 = 1e-6 * geom.unsigned_area(); + // TODO Re-encode as UInt + let population = feature.property::("population")?.try_into()?; + zones.push(Zone { geom, - // TODO Re-encode as UInt - population: feature.property::("population")?.try_into()?, + population, + density: (population as f64) / area_km2, }); } Ok(zones) diff --git a/web/src/App.svelte b/web/src/App.svelte index 06925d7..51eb4e7 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -170,7 +170,9 @@ {/if} {#if $showPopulation} - + {#await notNull($backend).renderZones() then gj} + + {/await} {/if} {/if} diff --git a/web/src/colors.ts b/web/src/colors.ts index 0eb1f10..1489d7c 100644 --- a/web/src/colors.ts +++ b/web/src/colors.ts @@ -1,3 +1,4 @@ +// From colorbrewer export const colorScale = [ "#CDE594", "#80C6A3", @@ -5,3 +6,12 @@ export const colorScale = [ "#186290", "#080C54", ]; + +// Just something different, +export const populationColorScale = [ + "#ffffb2", + "#fecc5c", + "#fd8d3c", + "#f03b20", + "#bd0026", +]; diff --git a/web/src/common/PopulationLayer.svelte b/web/src/common/PopulationLayer.svelte index 83bcdfb..4171384 100644 --- a/web/src/common/PopulationLayer.svelte +++ b/web/src/common/PopulationLayer.svelte @@ -5,23 +5,35 @@ FillLayer, hoverStateFilter, } from "svelte-maplibre"; - import { notNull } from "svelte-utils"; - import { Popup } from "svelte-utils/map"; - import { backend } from "../stores"; + import type { FeatureCollection } from "geojson"; + import { makeColorRamp, Popup } from "svelte-utils/map"; + import { populationColorScale } from "../colors"; + + export let gj: FeatureCollection; + + // TODO Should the limits be fixed? But this varies so much regionally + let limits = Array.from(Array(6).keys()).map( + (i) => (gj.max_density / (6 - 1)) * i, + ); -{#await notNull($backend).renderZones() then data} - - + + {props.population.toLocaleString()} people live here ({Math.round( + props.density, + ).toLocaleString()} people / square kilometer) - {props.population.toLocaleString()} - - - -{/await} + + +