Skip to content

Commit

Permalink
Use a choropleth to show OD zone demand
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Feb 9, 2025
1 parent b73b53e commit 2ae78a1
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 11 deletions.
74 changes: 64 additions & 10 deletions web/src/DebugDemandMode.svelte
Original file line number Diff line number Diff line change
@@ -1,31 +1,76 @@
<script lang="ts">
import type { Feature, FeatureCollection, MultiPolygon } from "geojson";
import type { DataDrivenPropertyValueSpecification } from "maplibre-gl";
import {
FillLayer,
GeoJSON,
hoverStateFilter,
LineLayer,
} from "svelte-maplibre";
import { emptyGeojson } from "svelte-utils/map";
import { SequentialLegend } from "svelte-utils";
import { emptyGeojson, makeRamp } from "svelte-utils/map";
import { SplitComponent } from "svelte-utils/top_bar_layout";
import BackButton from "./BackButton.svelte";
import { layerId, Link, sum } from "./common";
import { layerId, Link } from "./common";
import { demandColorScale } from "./common/colors";
import { backend, mode } from "./stores";
import type { ZoneDemandProps } from "./wasm";
let gj = emptyGeojson() as FeatureCollection<MultiPolygon, ZoneDemandProps>;
try {
gj = $backend!.getDemandModel();
console.log(gj);
} catch (err) {
window.alert("No demand model for this area");
$mode = { mode: "pick-neighbourhood" };
}
let showTo = false;
let hovered: Feature | null = null;
$: hoveredId = hovered == null ? null : (hovered.id as number);
// MapLibre doesn't preserve the arrays in properties, so use the original version
$: current = hoveredId != null ? gj.features[hoveredId] : null;
$: [limits, fillColor] = getLimitsAndColor(hoveredId, showTo);
function getLimitsAndColor(
hoveredId: number | null,
showTo: boolean,
): [string[], DataDrivenPropertyValueSpecification<string>] {
if (hoveredId == null) {
let key: "sum_from" | "sum_to" = showTo ? "sum_to" : "sum_from";
let max = Math.max(...gj.features.map((f) => f.properties[key]));
let limits = maxIntoBuckets(max);
return [
limits.map((l) => l.toLocaleString()),
makeRamp(["get", key], limits, demandColorScale),
];
} else {
// The arrays get stringified as properties and can't be used. Make an
// expression with a copy of the numbers.
let key: "counts_from" | "counts_to" = showTo
? "counts_to"
: "counts_from";
let counts = [...gj.features.map((f) => f.properties[key][hoveredId])];
let max = Math.max(...counts);
let limits = maxIntoBuckets(max);
return [
limits.map((l) => l.toLocaleString()),
makeRamp(["at", ["id"], ["literal", counts]], limits, demandColorScale),
];
}
}
function maxIntoBuckets(max: number): number[] {
let n = demandColorScale.length + 1;
return Array.from(Array(n).keys()).map((i) =>
Math.round((max / (n - 1)) * i),
);
}
</script>

<SplitComponent>
Expand All @@ -52,17 +97,19 @@

<p>{gj.features.length.toLocaleString()} zones</p>

<label>
Trips from
<input type="checkbox" role="switch" bind:checked={showTo} />
Trips to
</label>

{#if current && hoveredId != null}
<u>{current.properties.name}</u>
<p>
Total trips from here: {sum(
current.properties.counts_from,
).toLocaleString()}
Total trips from here: {current.properties.sum_from.toLocaleString()}
</p>
<p>
Total trips to here: {sum(
current.properties.counts_to,
).toLocaleString()}
Total trips to here: {current.properties.sum_to.toLocaleString()}
</p>
<p>
Total intra-zonal trips starting and ending here: {current.properties
Expand All @@ -71,14 +118,21 @@
{:else}
<p>Hover on a zone</p>
{/if}

<hr />
<p>
Trips {showTo ? "from" : "to"}
{hoveredId == null ? "each zone" : "this zone"}:
</p>
<SequentialLegend colorScale={demandColorScale} {limits} />
</div>

<div slot="map">
<GeoJSON data={gj} generateId>
<FillLayer
{...layerId("debug-demand-fill")}
paint={{
"fill-color": "grey",
"fill-color": fillColor,
"fill-opacity": hoverStateFilter(0.5, 0.1),
}}
manageHoverState
Expand Down
9 changes: 9 additions & 0 deletions web/src/common/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ export const Style = {
},
},
};

// From https://www.ons.gov.uk/census/maps/choropleth
export let demandColorScale = [
"#CDE594",
"#80C6A3",
"#1F9EB7",
"#186290",
"#080C54",
];
10 changes: 9 additions & 1 deletion web/src/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
Polygon,
} from "geojson";
import type { LngLat } from "maplibre-gl";
import { sum } from "./common";
import type { Intersection, IntersectionFeature } from "./common/Intersection";

// This is a thin TS wrapper around the auto-generated TS API. The TS
Expand Down Expand Up @@ -207,14 +208,21 @@ export class Backend {
}

getDemandModel(): FeatureCollection<MultiPolygon, ZoneDemandProps> {
return JSON.parse(this.inner.getDemandModel());
let gj = JSON.parse(this.inner.getDemandModel());
for (let f of gj.features) {
f.properties.sum_from = sum(f.properties.counts_from);
f.properties.sum_to = sum(f.properties.counts_to);
}
return gj;
}
}

export type ZoneDemandProps = {
name: string;
counts_from: number[];
counts_to: number[];
sum_from: number;
sum_to: number;
};

export interface RenderNeighbourhoodOutput {
Expand Down

0 comments on commit 2ae78a1

Please sign in to comment.