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

Add an IMD layer #309

Merged
merged 1 commit into from
Aug 14, 2023
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
26 changes: 10 additions & 16 deletions src/lib/browse/CensusOutputAreaLayerControl.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { MapGeoJSONFeature } from "maplibre-gl";
import {
hoveredToggle,
makeColorRamp,
overwriteLineLayer,
overwritePmtilesSource,
overwritePolygonLayer,
Expand Down Expand Up @@ -32,7 +33,11 @@
colorBy = "";
}
if (colorBy) {
$map.setPaintProperty(name, "fill-color", makeColorRamp());
$map.setPaintProperty(
name,
"fill-color",
makeColorRamp(["get", colorBy], makeLimits(), colorScale)
);
// InteractiveLayer manages the polygon layer, but we also need to control the outline
$map.setLayoutProperty(outlineLayer, "visibility", "visible");
} else {
Expand Down Expand Up @@ -105,20 +110,6 @@
return [0, 4700, 13000, 33000, 94000, 1980000];
}
}
function makeColorRamp(): any[] {
let limits = makeLimits();
let fillColor: any[] = ["step", ["get", colorBy]];
for (let i = 1; i < limits.length; i++) {
fillColor.push(colorScale[i - 1]);
fillColor.push(limits[i]);
}
// Repeat the last color. The upper limit is exclusive, meaning a value
// exactly equal to it will use this fallback. For things like percentages,
// we want to set 100 as the cap.
fillColor.push(colorScale[colorScale.length - 1]);
return fillColor;
}
</script>

<Checkbox
Expand Down Expand Up @@ -234,7 +225,10 @@
</Checkbox>
{#if colorBy == "population_density"}
<p>(people per square kilometres)</p>
<SequentialLegend {colorScale} limits={makeLimits()} />
<SequentialLegend
{colorScale}
limits={makeLimits().map((x) => x.toLocaleString())}
/>
{/if}

<InteractiveLayer
Expand Down
96 changes: 96 additions & 0 deletions src/lib/browse/ImdLayerControl.svelte
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't find any useful reference to open for clicking; http://dclgapps.communities.gov.uk/imd/iod_index.html doesn't have URL controls

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it might be valuable to still send the user through to that page, even if we can't send them to the specific LSOA info?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's listed in the help popup. Feels unintuitive to make a click on a specific LSOA in the map have the same effect everywhere

Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script lang="ts">
import type { MapGeoJSONFeature } from "maplibre-gl";
import {
hoveredToggle,
makeColorRamp,
overwriteLineLayer,
overwritePmtilesSource,
overwritePolygonLayer,
} from "../../maplibre_helpers";
import { map } from "../../stores";
import { ExternalLink, HelpButton, InteractiveLayer } from "../common";
import { Checkbox } from "../govuk";
import { colors } from "./colors";
import SequentialLegend from "./SequentialLegend.svelte";

let name = "imd";
let outlineLayer = `${name}-outline`;

let colorScale = colors.sequential_low_to_high;
// The deciles are [1, 10]. The 5 colors cover two each.
let limits = [0, 2, 4, 6, 8, 10];

overwritePmtilesSource(
$map,
name,
`https://atip.uk/layers/v1/${name}.pmtiles`
);

overwritePolygonLayer($map, {
id: name,
source: name,
sourceLayer: name,
// Decile 1 is the most deprived, but we want to invert for the color scale
color: makeColorRamp(["-", 10, ["get", "decile"]], limits, colorScale),
opacity: hoveredToggle(0.5, 0.7),
});
overwriteLineLayer($map, {
id: outlineLayer,
source: name,
sourceLayer: name,
color: "black",
width: 0.5,
});

let show = false;
// InteractiveLayer manages the polygon layer, but we also need to control the outline
$: {
if ($map.getLayer(outlineLayer)) {
$map.setLayoutProperty(
outlineLayer,
"visibility",
show ? "visible" : "none"
);
}
}

function tooltip(feature: MapGeoJSONFeature): string {
return (
`<p>${feature.properties.LSOA11CD} has an IMD score of <b>${feature.properties.score}</b></p>` +
`<p>Rank: <b>${feature.properties.rank.toLocaleString()}</b> / 32,844 LSOAs</p>`
);
}
</script>

<Checkbox id={name} bind:checked={show}>
Indices of Multiple Deprivation
<span slot="right">
<HelpButton>
<p>
The 2019 English IMD scores come from <ExternalLink
href="https://data-communities.opendata.arcgis.com/datasets/communities::indices-of-multiple-deprivation-imd-2019-1/explore"
>
DLUCH GIS
</ExternalLink>. Note the LSOAs identified are from the 2011 census. A
detailed breakdown of the score across different categories can be found <ExternalLink
href="http://dclgapps.communities.gov.uk/imd/iod_index.html"
>
here
</ExternalLink>.
</p>
<p>
License: <ExternalLink
href="http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/"
>
Open Government License
</ExternalLink>. Contains OS data &copy; Crown copyright and database
right 2023.
</p>
</HelpButton>
</span>
</Checkbox>
{#if show}
<SequentialLegend {colorScale} limits={["Least deprived", "Most deprived"]} />
{/if}

<InteractiveLayer layer={name} {tooltip} {show} clickable={false} />
4 changes: 2 additions & 2 deletions src/lib/browse/SequentialLegend.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
export let colorScale: string[];
export let limits: number[];
export let limits: any[];
</script>

<div style="display: flex">
Expand All @@ -12,6 +12,6 @@
</div>
<div style="display: flex; justify-content: space-between;">
{#each limits as limit}
<span>{limit.toLocaleString()}</span>
<span>{limit}</span>
{/each}
</div>
20 changes: 20 additions & 0 deletions src/maplibre_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,24 @@ export function hoveredToggle<Type>(
] as DataDrivenPropertyValueSpecification<Type>;
}

// Helper for https://maplibre.org/maplibre-style-spec/expressions/#step.
export function makeColorRamp(
input: DataDrivenPropertyValueSpecification<number>,
limits: number[],
colorScale: string[]
): DataDrivenPropertyValueSpecification<string> {
let step: any[] = ["step", input];
for (let i = 1; i < limits.length; i++) {
step.push(colorScale[i - 1]);
step.push(limits[i]);
}
// Repeat the last color. The upper limit is exclusive, meaning a value
// exactly equal to it will use this fallback. For things like percentages,
// we want to set 100 as the cap.
step.push(colorScale[colorScale.length - 1]);
return step as DataDrivenPropertyValueSpecification<string>;
}

// Suitable for passing to map.fitBounds. Work around https://github.com/Turfjs/turf/issues/1807.
export function bbox(gj: GeoJSON): [number, number, number, number] {
return turfBbox(gj) as [number, number, number, number];
Expand Down Expand Up @@ -291,6 +309,8 @@ const layerZorder = [
"local_planning_authorities-outline",
"census_output_areas",
"census_output_areas-outline",
"imd",
"imd-outline",
// Then smaller optional layers on top
"schools",
"hospitals",
Expand Down
2 changes: 2 additions & 0 deletions src/pages/BrowseSchemes.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { processInput, type Scheme } from "../lib/browse/data";
import Filters from "../lib/browse/Filters.svelte";
import HospitalsLayerControl from "../lib/browse/HospitalsLayerControl.svelte";
import ImdLayerControl from "../lib/browse/ImdLayerControl.svelte";
import LocalAuthorityDistrictsLayerControl from "../lib/browse/LocalAuthorityDistrictsLayerControl.svelte";
import LocalPlanningAuthoritiesLayerControl from "../lib/browse/LocalPlanningAuthoritiesLayerControl.svelte";
import MrnLayerControl from "../lib/browse/MrnLayerControl.svelte";
Expand Down Expand Up @@ -179,6 +180,7 @@
</CollapsibleCard>
<CollapsibleCard label="Census">
<CensusOutputAreaLayerControl />
<ImdLayerControl />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible to one of three OA-level census layers alongside the IMD layer. This is awkward, but I don't know how to solve it (UX-wise) yet. I think we only ever want one boundary/census/"covers everything" layer on at a time, and not sure how to communicate that as radio buttons across categories

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I don't know how to communicate it, and it's worth checking with users first, but making these and boundaries all mutually exclusive sounds like a good future task

</CollapsibleCard>
<BaselayerSwitcher {style} />
</CollapsibleCard>
Expand Down
Loading