Skip to content

Commit

Permalink
Color population by density
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Aug 16, 2024
1 parent e2d4153 commit 1ef50fc
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 23 deletions.
21 changes: 19 additions & 2 deletions backend/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -190,12 +190,27 @@ impl Graph {
/// Returns a GeoJSON string
pub fn render_zones(&self) -> Result<String> {
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(),
),
})?)
}
}

Expand Down Expand Up @@ -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,
}
10 changes: 7 additions & 3 deletions backend/src/graph/scrape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -303,10 +303,14 @@ async fn load_zones(url: String, mercator: &Mercator) -> Result<Vec<Zone>> {
// 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::<i64>("population")?.try_into()?;

zones.push(Zone {
geom,
// TODO Re-encode as UInt
population: feature.property::<i64>("population")?.try_into()?,
population,
density: (population as f64) / area_km2,
});
}
Ok(zones)
Expand Down
4 changes: 3 additions & 1 deletion web/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@
{/if}

{#if $showPopulation}
<PopulationLayer />
{#await notNull($backend).renderZones() then gj}
<PopulationLayer {gj} />
{/await}
{/if}
{/if}
</MapLibre>
Expand Down
10 changes: 10 additions & 0 deletions web/src/colors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
// From colorbrewer
export const colorScale = [
"#CDE594",
"#80C6A3",
"#1F9EB7",
"#186290",
"#080C54",
];

// Just something different,
export const populationColorScale = [
"#ffffb2",
"#fecc5c",
"#fd8d3c",
"#f03b20",
"#bd0026",
];
46 changes: 29 additions & 17 deletions web/src/common/PopulationLayer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
</script>

{#await notNull($backend).renderZones() then data}
<GeoJSON {data} generateId>
<FillLayer
manageHoverState
paint={{
"fill-color": "red",
"fill-opacity": hoverStateFilter(0.2, 0.8),
}}
<GeoJSON data={gj} generateId>
<FillLayer
manageHoverState
paint={{
"fill-color": makeColorRamp(
["get", "density"],
limits,
populationColorScale,
),
"fill-opacity": hoverStateFilter(0.2, 0.8),
}}
>
<Popup openOn="hover" let:props
>{props.population.toLocaleString()} people live here ({Math.round(
props.density,
).toLocaleString()} people / square kilometer)</Popup
>
<Popup openOn="hover" let:props>{props.population.toLocaleString()}</Popup
>
</FillLayer>
<LineLayer paint={{ "line-color": "black", "line-width": 1 }} />
</GeoJSON>
{/await}
</FillLayer>
<LineLayer paint={{ "line-color": "black", "line-width": 1 }} />
</GeoJSON>

0 comments on commit 1ef50fc

Please sign in to comment.