Skip to content

Commit

Permalink
feat(frontend): aggregated risk map layer (risk hotspots)
Browse files Browse the repository at this point in the history
New raster layer showing aggregated risk values (direct damages or economic losses) for:
- variables: "total value", "economic use", "population use", "total risk", EAD, EAEL.
- hazards: "none", cyclone, "all flooding".

Includes:
- New aggregated risk map layer and params in `src/state`.
- New config in `src/config/risks`.
- New sidebar controls in `src/sidebar/risks`.
  • Loading branch information
eatyourgreens committed Jun 19, 2024
1 parent a1fdaff commit 0a9e82f
Show file tree
Hide file tree
Showing 21 changed files with 438 additions and 23 deletions.
24 changes: 24 additions & 0 deletions frontend/src/config/color-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,30 @@ export const RASTER_COLOR_MAPS = {
scheme: 'reds',
range: [0, 75],
},
totalValue: {
scheme: 'reds',
range: [0, 10],
},
economicUse: {
scheme: 'blues',
range: [0, 10],
},
populationUse: {
scheme: 'purples',
range: [0, 10],
},
totalRisk: {
scheme: 'greens',
range: [0, 10],
},
ead: {
scheme: 'oranges',
range: [0, 10],
},
eael: {
scheme: 'purples',
range: [0, 10],
},
};

function invertColorScale<T>(colorScale: (t: number) => T) {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/config/interaction-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export const INTERACTION_GROUPS = makeConfig<InteractionGroupConfig, string>([
type: 'raster',
pickMultiple: true,
},
{
id: 'risks',
type: 'raster',
pickMultiple: false,
},
{
id: 'regions',
type: 'vector',
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/config/risks/domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { DataParamGroupConfig } from 'lib/controls/data-params';

export interface RiskParams {
riskSource?: string;
returnPeriod: number;
epoch: number;
rcp: string;
confidence: string | number;
}

export const RISK_DOMAINS: Record<string, DataParamGroupConfig<RiskParams>> = {
none: {
paramDomains: {
returnPeriod: [0],
epoch: [2010],
rcp: ['baseline'],
confidence: ['None'],
},
paramDefaults: {
returnPeriod: 0,
epoch: 2010,
rcp: 'baseline',
confidence: 'None',
},
},
fluvial: {
paramDomains: {
returnPeriod: [100],
rcp: ['baseline'],
epoch: [2010],
confidence: ['None'],
},
paramDefaults: {
returnPeriod: 100,
rcp: 'baseline',
epoch: 2010,
confidence: 'None',
},
paramDependencies: {
rcp: ({ epoch }) => {
if (epoch === 2010) return ['baseline'];
else if (epoch === 2050 || epoch === 2080) return ['2.6', '4.5', '8.5'];
return [];
},
},
},
cyclone: {
paramDomains: {
returnPeriod: [100],
epoch: [2010],
rcp: ['baseline'],
confidence: [5, 50, 95],
},
paramDefaults: {
returnPeriod: 100,
epoch: 2010,
rcp: 'baseline',
confidence: 50,
},
paramDependencies: {
rcp: ({ epoch }) => {
if (epoch === 2010) return ['baseline'];
if (epoch === 2050 || epoch === 2100) return ['4.5', '8.5'];
return [];
},
},
},
};
36 changes: 36 additions & 0 deletions frontend/src/config/risks/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const HAZARDS_METADATA = {
none: { label: 'None' },
cyclone: { label: 'Cyclone' },
fluvial: { label: 'All Flooding' },
};

export const HAZARDS = Object.keys(HAZARDS_METADATA);

export const RISKS_METADATA = {
totalValue: {
label: 'Total value',
dataUnit: '',
},
economicUse: {
label: 'Economic use',
dataUnit: '',
},
populationUse: {
label: 'Population use',
dataUnit: '',
},
totalRisk: {
label: 'Total risk',
dataUnit: '',
},
ead: {
label: 'Expected Annual Damages (EAD)',
dataUnit: '',
},
eael: {
label: 'Expected Annual Economic Losses (EAEL)',
dataUnit: '',
},
};

export const RISKS = Object.keys(RISKS_METADATA);
72 changes: 72 additions & 0 deletions frontend/src/config/risks/risk-view-layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import GL from '@luma.gl/constants';
import { RiskParams } from 'config/risks/domains';

import { rasterTileLayer } from 'lib/deck/layers/raster-tile-layer';
import { ViewLayer } from 'lib/data-map/view-layers';

import { RASTER_COLOR_MAPS } from '../color-maps';
import { RISK_SOURCE } from './source';

export function getRiskId<
F extends string, //'fluvial' | 'surface' | 'coastal' | 'cyclone',
RP extends number,
RCP extends string,
E extends number,
C extends number | string,
>({
riskType,
riskSource,
returnPeriod,
rcp,
epoch,
confidence,
}: {
riskType: F;
riskSource: string;
returnPeriod: RP;
rcp: RCP;
epoch: E;
confidence: C;
}) {
return `${riskType}__${riskSource}__rp_${returnPeriod}__rcp_${rcp}__epoch_${epoch}__conf_${confidence}` as const;
}

export function riskViewLayer(riskType: string, riskParams: RiskParams): ViewLayer {
const { riskSource, returnPeriod, rcp, epoch, confidence } = riskParams;

const deckId = getRiskId({ riskType, riskSource, returnPeriod, rcp, epoch, confidence });

return {
id: riskType,
group: 'risks',
spatialType: 'raster',
interactionGroup: 'risks',
params: { riskType, riskParams },
fn: ({ deckProps }) => {
const { scheme, range } = RASTER_COLOR_MAPS[riskType];
const dataURL = RISK_SOURCE.getDataUrl(
{
riskType,
riskParams: { riskSource, returnPeriod, rcp, epoch, confidence },
},
{ scheme, range },
);

return rasterTileLayer(
{
textureParameters: {
[GL.TEXTURE_MAG_FILTER]: GL.LINEAR,
// [GL.TEXTURE_MAG_FILTER]: zoom < 12 ? GL.NEAREST : GL.NEAREST_MIPMAP_LINEAR,
},
opacity: riskType === 'cyclone' ? 0.6 : 1,
},
deckProps,
{
id: `${riskType}@${deckId}`, // follow the convention viewLayerId@deckLayerId
data: dataURL,
refinementStrategy: 'no-overlap',
},
);
},
};
}
10 changes: 10 additions & 0 deletions frontend/src/config/risks/source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const RISK_SOURCE = {
getDataUrl(
{ riskType, riskParams: { riskSource, returnPeriod, rcp, epoch, confidence } },
{ scheme, range },
) {
const sanitisedRcp = rcp?.replace('.', 'x');

return `/raster/singleband/${riskType}/${riskSource}/${returnPeriod}/${sanitisedRcp}/${epoch}/${confidence}/{z}/{x}/{y}.png?colormap=${scheme}&stretch_range=[${range[0]},${range[1]}]`;
},
};
1 change: 1 addition & 0 deletions frontend/src/config/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const SECTIONS_CONFIG: Record<string, { styles?: Record<string, StyleSele
styles: DROUGHT_STYLES,
},
hazards: {},
risks: {},
buildings: {
styles: BUILDING_STYLES,
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/recoil/grouped-family.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function groupedFamily<FVT, FPT>(
(group) =>
({ get }) => {
const groupParams = get(paramsFamily(group));
const deps = fromPairs(groupParams.map((param) => [param, family(paramFn(group, param))]));
const deps = fromPairs(groupParams?.map((param) => [param, family(paramFn(group, param))]));
return get(waitForAll(deps));
},
});
Expand Down
47 changes: 31 additions & 16 deletions frontend/src/map/legend/RasterLegend.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RASTER_COLOR_MAPS } from 'config/color-maps';
import { HAZARDS_METADATA } from 'config/hazards/metadata';
import { RISKS_METADATA } from 'config/risks/metadata';
import { ViewLayer } from 'lib/data-map/view-layers';
import { FC, useCallback } from 'react';
import { GradientLegend } from './GradientLegend';
Expand All @@ -15,24 +16,38 @@ export interface RasterColorMapValues {

export const RasterLegend: FC<{ viewLayer: ViewLayer }> = ({ viewLayer }) => {
const {
params: { hazardType },
params: { hazardType, riskType },
} = viewLayer;
const { label, dataUnit } = HAZARDS_METADATA[hazardType];
const { scheme, range } = RASTER_COLOR_MAPS[hazardType];

const { scheme, range } = RASTER_COLOR_MAPS[hazardType || riskType];
const { error, loading, colorMapValues } = useRasterColorMapValues(scheme, range);

const getValueLabel = useCallback(
(value: number) => `${value.toLocaleString()} ${dataUnit}`,
[dataUnit],
);
if (hazardType) {
const { label, dataUnit } = HAZARDS_METADATA[hazardType];

const getValueLabel = useCallback(
(value: number) => `${value.toLocaleString()} ${dataUnit}`,
[dataUnit],
);

return (
<GradientLegend
label={label}
range={range}
colorMapValues={!(error || loading) ? colorMapValues : null}
getValueLabel={getValueLabel}
/>
);
return (
<GradientLegend
label={label}
range={range}
colorMapValues={!(error || loading) ? colorMapValues : null}
getValueLabel={getValueLabel}
/>
);
}
if (riskType) {
const { label } = RISKS_METADATA[riskType];
return (
<GradientLegend
label={label}
range={range}
colorMapValues={!(error || loading) ? colorMapValues : null}
getValueLabel={(value) => `${value.toLocaleString()}`}
/>
);
}
return <p>Unknown parameter type</p>;
};
18 changes: 15 additions & 3 deletions frontend/src/map/tooltip/TooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,25 @@ const TooltipSection = ({ children }) => (

export const TooltipContent: FC = () => {
const hoveredVector = useRecoilValue(hoverState('assets')) as InteractionTarget<any>;
const hoveredRasters = useRecoilValue(hoverState('hazards')) as InteractionTarget<any>[];
const hoveredHazards = useRecoilValue(hoverState('hazards')) as InteractionTarget<any>[];
const hoveredRisks = useRecoilValue(hoverState('risks')) as InteractionTarget<any>;
const hoveredRegion = useRecoilValue(hoverState('regions')) as InteractionTarget<any>;
const hoveredSolution = useRecoilValue(hoverState('solutions')) as InteractionTarget<any>;
const hoveredDrought = useRecoilValue(hoverState('drought')) as InteractionTarget<any>;
console.log({ hoveredHazards, hoveredRisks })

const regionDataShown = useRecoilValue(showPopulationState);

const assetsHovered = hasHover(hoveredVector);
const hazardsHovered = hasHover(hoveredRasters);
const hazardsHovered = hasHover(hoveredHazards);
const risksHovered = hasHover(hoveredRisks);
const regionsHovered = hasHover(hoveredRegion);
const solutionsHovered = hasHover(hoveredSolution);
const droughtHovered = hasHover(hoveredDrought);
const doShow =
assetsHovered ||
hazardsHovered ||
risksHovered ||
(regionDataShown && regionsHovered) ||
solutionsHovered ||
droughtHovered;
Expand Down Expand Up @@ -62,14 +66,22 @@ export const TooltipContent: FC = () => {
) : null}
{hazardsHovered ? (
<TooltipSection>
{hoveredRasters.map((hr) => (
{hoveredHazards.map((hr) => (
<RasterHoverDescription
hoveredObject={hr}
key={`${hr.viewLayer.id}-${hr.target.id}`}
/>
))}
</TooltipSection>
) : null}
{risksHovered ? (
<TooltipSection>
<RasterHoverDescription
hoveredObject={hoveredRisks}
key={`${hoveredRisks.viewLayer.id}-${hoveredRisks.target.id}`}
/>
</TooltipSection>
) : null}
{regionsHovered ? (
<TooltipSection>
<RegionHoverDescription hoveredObject={hoveredRegion} />
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/map/tooltip/content/RasterHoverDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { InteractionTarget, RasterTarget } from 'lib/data-map/interactions/use-i

import { RASTER_COLOR_MAPS } from 'config/color-maps';
import { HAZARDS_METADATA } from 'config/hazards/metadata';
import { RISKS_METADATA } from 'config/risks/metadata';

import { useRasterColorMapValues } from '../../legend/use-color-map-values';
import { ColorBox } from './ColorBox';
Expand Down Expand Up @@ -36,11 +37,19 @@ export const RasterHoverDescription: FC<{ hoveredObject: InteractionTarget<Raste
const {
viewLayer: {
id,
params: { hazardType },
params: { hazardType, riskType },
},
} = hoveredObject;
const { label, dataUnit } = HAZARDS_METADATA[id];
const { scheme, range } = RASTER_COLOR_MAPS[hazardType];
const metadata = hazardType
? HAZARDS_METADATA
: riskType
? RISKS_METADATA
: {
label: 'Unknown',
dataUnit: '',
};
const { label, dataUnit } = metadata[id];
const { scheme, range } = RASTER_COLOR_MAPS[id];

const title = `${label}`;

Expand Down
Loading

0 comments on commit 0a9e82f

Please sign in to comment.