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 for:
- variables: "exposure value", "population affected", "loss GDP", "demand affected".
- hazards: "none", cyclone, "all flooding".

Includes:
- New aggregated risk map layer and params in `src/state/layers/modules/risks`.
- New config in `src/config/risks`. Parameters defined in `src/config/risks/domains`.
- New sidebar controls in `src/sidebar/risks`.
- New legends and tooltips in `src/config/risks`.
- Makes a start on generic formats for data values eg. financial or population.
  • Loading branch information
eatyourgreens committed Nov 5, 2024
1 parent 2d58835 commit ddb5d3a
Show file tree
Hide file tree
Showing 22 changed files with 422 additions and 18 deletions.
8 changes: 8 additions & 0 deletions frontend/src/app/config/interaction-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export const INTERACTION_GROUPS = new Map<string, InteractionGroupConfig>([
pickMultiple: true,
},
],
[
'risks',
{
id: 'risks',
type: 'raster',
pickMultiple: false,
},
],
[
'regions',
{
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/config/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NETWORK_STYLES } from 'data-layers/networks/styles';
import { REGION_STYLES } from 'data-layers/regions/styles';
import { MARINE_STYLES } from 'data-layers/marine/styles';
import { TERRESTRIAL_STYLES } from 'data-layers/terrestrial/styles';
import { RISK_STYLES } from 'data-layers/risks/styles';

export const SECTIONS_CONFIG: Record<string, { styles?: Record<string, StyleSelectionOption> }> = {
assets: {
Expand All @@ -14,6 +15,9 @@ export const SECTIONS_CONFIG: Record<string, { styles?: Record<string, StyleSele
styles: DROUGHT_STYLES,
},
hazards: {},
risks: {
styles: RISK_STYLES,
},
buildings: {
styles: BUILDING_STYLES,
},
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/config/view-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const VIEW_LAYERS = [
'terrestrial',
'marine',
'hazards',
'risks',
'buildings',
'networks',
'droughtOptions',
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/app/config/views.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { RISKS_METADATA } from 'data-layers/risks/metadata';

export interface ViewSectionConfig {
expanded: boolean;
visible: boolean;
Expand Down Expand Up @@ -52,6 +54,13 @@ export const VIEW_SECTIONS: Record<string, Record<string, ViewSectionConfig>> =
styles: ['type', 'damages'],
defaultStyle: 'damages',
},
risks: {
expanded: true,
visible: true,

styles: Object.keys(RISKS_METADATA),
defaultStyle: Object.keys(RISKS_METADATA)[0],
},
hazards: {
expanded: false,
visible: true,
Expand Down
25 changes: 20 additions & 5 deletions frontend/src/app/map/legend/RasterLegend.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { FC, useCallback } from 'react';

import { numFormatMoney } from 'lib/helpers';
import { GradientLegend } from './GradientLegend';
import { useRasterColorMapValues } from './use-color-map-values';
export interface ColorValue {
Expand All @@ -10,18 +12,31 @@ export interface RasterColorMapValues {
rangeTruncated: [boolean, boolean];
}

const formatter = {
hazard: (value, dataUnit) => `${value.toLocaleString()} ${dataUnit}`,
financial: numFormatMoney,
integer: (value) =>
value.toLocaleString(undefined, {
maximumSignificantDigits: 3,
maximumFractionDigits: 0,
roundingPriority: 'lessPrecision',
}),
population: (value) =>
value.toLocaleString(undefined, {
maximumSignificantDigits: 3,
}),
};

export const RasterLegend: FC<{
label: string;
dataUnit: string;
scheme: string;
range: [number, number];
}> = ({ label, dataUnit, scheme, range }) => {
type?: string;
}> = ({ label, dataUnit, scheme, range, type = 'hazard' }) => {
const { error, loading, colorMapValues } = useRasterColorMapValues(scheme, range);

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

return (
<GradientLegend
Expand Down
9 changes: 3 additions & 6 deletions frontend/src/app/map/legend/VectorLegend.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import { colorScaleValues } from 'lib/color-map';
import { ColorMap, FormatConfig } from 'lib/data-map/view-layers';
import { FC, useMemo } from 'react';
import { FC } from 'react';
import { GradientLegend } from './GradientLegend';

export const VectorLegend: FC<{ colorMap: ColorMap; legendFormatConfig: FormatConfig }> = ({
colorMap,
legendFormatConfig,
}) => {
const { colorSpec, fieldSpec } = colorMap;
const colorMapValues = useMemo(() => colorScaleValues(colorSpec, 255), [colorSpec]);
const colorMapValues = colorScaleValues(colorSpec, 255);

const { getDataLabel, getValueFormatted } = legendFormatConfig;

const label = getDataLabel(fieldSpec);
const getValueLabel = useMemo(
() => (value) => getValueFormatted(value, fieldSpec),
[fieldSpec, getValueFormatted],
);
const getValueLabel = (value) => getValueFormatted(value, fieldSpec);

return (
<GradientLegend
Expand Down
26 changes: 22 additions & 4 deletions frontend/src/app/map/tooltip/content/RasterHoverDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRasterColorMapValues } from '../../legend/use-color-map-values';
import { ColorBox } from './ColorBox';
import { Box } from '@mui/material';
import { DataItem } from 'details/features/detail-components';
import { numFormatMoney } from 'lib/helpers';

function useRasterColorMapLookup(colorMapValues) {
return useMemo(
Expand All @@ -14,11 +15,26 @@ function useRasterColorMapLookup(colorMapValues) {
);
}

function formatHazardValue(color, value, dataUnit) {
const formatter = {
hazard: (value, dataUnit) => value.toFixed(1) + dataUnit,
financial: numFormatMoney,
integer: (value) =>
value.toLocaleString(undefined, {
maximumSignificantDigits: 3,
maximumFractionDigits: 0,
roundingPriority: 'lessPrecision',
}),
population: (value) =>
value.toLocaleString(undefined, {
maximumSignificantDigits: 3,
}),
};

function formatValue(color, value, dataUnit, type) {
return (
<>
<ColorBox color={color} />
{value == null ? '' : value.toFixed(1) + dataUnit}
{value == null ? '' : formatter[type](value, dataUnit)}
</>
);
}
Expand All @@ -29,17 +45,19 @@ export const RasterHoverDescription: FC<{
dataUnit: string;
scheme: string;
range: [number, number];
}> = ({ color, label, dataUnit, scheme, range }) => {
type?: string;
}> = ({ color, label, dataUnit, scheme, range, type = 'hazard' }) => {
const title = `${label}`;

const { colorMapValues } = useRasterColorMapValues(scheme, range);
const rasterValueLookup = useRasterColorMapLookup(colorMapValues);

const colorString = `rgb(${color[0]},${color[1]},${color[2]})`;
const value = rasterValueLookup?.[colorString];

return (
<Box>
<DataItem label={title} value={formatHazardValue(colorString, value, dataUnit)} />
<DataItem label={title} value={formatValue(colorString, value, dataUnit, type)} />
</Box>
);
};
10 changes: 10 additions & 0 deletions frontend/src/app/sidebar/SidebarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DroughtsSection } from 'data-layers/droughtRisks/sidebar/DroughtsSectio
import { HazardsSection } from 'data-layers/hazards/sidebar/HazardsSection';
import { NetworksSection } from 'data-layers/networks/sidebar/NetworksSection';
import { RegionsSection } from 'data-layers/regions/sidebar/RegionsSection';
import { RisksSection } from 'data-layers/risks/sidebar/RisksSection';
import { MarineSection } from 'data-layers/marine/sidebar/MarineSection';
import { TerrestrialSection } from 'data-layers/terrestrial/sidebar/TerrestrialSection';
import { ErrorBoundary } from 'lib/react/ErrorBoundary';
Expand All @@ -25,10 +26,19 @@ const SidebarContent: FC = () => {
const view = useRecoilValue(viewState);
switch (view) {
case 'exposure':
return (
<>
<NetworksSection />
<HazardsSection />
<BuildingsSection />
<RegionsSection />
</>
);
case 'risk':
return (
<>
<NetworksSection />
<RisksSection />
<HazardsSection />
<BuildingsSection />
<RegionsSection />
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/state/data-params.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HAZARD_DOMAINS } from 'data-layers/hazards/domains';
import { NETWORK_DOMAINS } from 'data-layers/networks/domains';
import { RISK_DOMAINS } from 'data-layers/risks/domains';
import {
DataParamGroupConfig,
Param,
Expand All @@ -21,6 +22,7 @@ export type DataParamParam = Readonly<{
export const dataParamConfig: Record<string, DataParamGroupConfig> = {
...HAZARD_DOMAINS,
...NETWORK_DOMAINS,
risks: RISK_DOMAINS,
};

export const dataParamNamesByGroup = mapValues(dataParamConfig, (groupConfig) =>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/data-layers/assets/data-formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ function getSourceLabel(eadSource: string) {
return HAZARDS_METADATA[eadSource].label;
}

function getDamageTypeLabel(field) {
function getDamageTypeLabel(field: string) {
if (field === 'ead_mean') return 'Direct Damages';
else if (field === 'eael_mean') return 'Economic Losses';
}

function formatDamageValue(value) {
function formatDamageValue(value: number) {
if (isNullish(value)) return value;

return `$${numFormatMoney(value)}`;
return numFormatMoney(value);
}
const DAMAGES_EXPECTED_DEFAULT_FORMAT: FormatConfig = {
getDataLabel: (colorField) => {
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/data-layers/risks/RiskHoverDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FC } from 'react';

import { RasterHoverDescription } from 'lib/data-map/types';
import { RasterHoverDescription as RasterTooltip } from 'app/map/tooltip/content/RasterHoverDescription';

import * as RISKS_COLOR_MAPS from './color-maps';
import { RISKS_METADATA } from './metadata';

export const RiskHoverDescription: FC<RasterHoverDescription> = ({ target, viewLayer }) => {
const { label, dataUnit, format } = RISKS_METADATA[viewLayer.id];
const { scheme, range } = RISKS_COLOR_MAPS[viewLayer.id];
return (
<RasterTooltip
color={target.color}
label={label}
dataUnit={dataUnit}
scheme={scheme}
range={range}
type={format}
/>
);
};
16 changes: 16 additions & 0 deletions frontend/src/data-layers/risks/RiskLegend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FC } from 'react';

import { RasterLegend } from 'app/map/legend/RasterLegend';
import { ViewLayer } from 'lib/data-map/view-layers';

import * as RISKS_COLOR_MAPS from './color-maps';
import { RISKS_METADATA } from './metadata';

export const RiskLegend: FC<{ viewLayer: ViewLayer }> = ({ viewLayer }) => {
const { id } = viewLayer;
const { label, dataUnit, format } = RISKS_METADATA[id];
const { scheme, range } = RISKS_COLOR_MAPS[id];
return (
<RasterLegend label={label} dataUnit={dataUnit} scheme={scheme} range={range} type={format} />
);
};
18 changes: 18 additions & 0 deletions frontend/src/data-layers/risks/color-maps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const exposureValue = {
scheme: 'reds',
range: [0, 1e9],
};
export const lossGdp = {
scheme: 'blues',
range: [0, 5e6],
};

export const populationAffected = {
scheme: 'purples',
range: [0, 1e4],
};

export const demandAffected = {
scheme: 'greens',
range: [0, 1e3],
};
66 changes: 66 additions & 0 deletions frontend/src/data-layers/risks/domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { DataParamGroupConfig } from 'lib/controls/data-params';

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

/*
Default parameter ranges for each hazard type.
These are used to define ranges for input controls in the sidebar.
*/
const hazardParamDomains = {
none: {
returnPeriod: [0],
epoch: [2010],
rcp: ['baseline'],
confidence: ['None'],
},
cyclone: {
returnPeriod: [0],
epoch: [2010],
rcp: ['baseline'],
confidence: ['None'],
},
fluvial: {
returnPeriod: [0],
epoch: [2010],
rcp: ['baseline'],
confidence: ['None'],
},
};

export const RISK_DOMAINS: DataParamGroupConfig<RiskParams> = {
/*
Default parameter ranges for each risk type.
*/
paramDomains: {
hazard: ['none', 'cyclone', 'fluvial'],
returnPeriod: [0],
epoch: [2010],
rcp: ['baseline'],
confidence: ['None'],
},
/*
Default parameter values for each risk type.
*/
paramDefaults: {
hazard: 'none',
returnPeriod: 0,
epoch: 2010,
rcp: 'baseline',
confidence: 'None',
},
/*
Callback functions to define custom parameter ranges based on selected hazard etc.
*/
paramDependencies: {
rcp: ({ hazard }) => hazardParamDomains[hazard].rcp,
epoch: ({ hazard }) => hazardParamDomains[hazard].epoch,
returnPeriod: ({ hazard }) => hazardParamDomains[hazard].returnPeriod,
confidence: ({ hazard }) => hazardParamDomains[hazard].confidence,
},
};
Loading

0 comments on commit ddb5d3a

Please sign in to comment.