Skip to content

Commit

Permalink
Divided AA into drought and storm layers and panels
Browse files Browse the repository at this point in the history
  • Loading branch information
Doniaab committed Nov 22, 2024
1 parent 5cb263b commit 3e1a407
Show file tree
Hide file tree
Showing 32 changed files with 1,547 additions and 742 deletions.
24 changes: 16 additions & 8 deletions frontend/src/components/MapView/DateSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import Draggable, { DraggableEvent } from 'react-draggable';
import { useDispatch, useSelector } from 'react-redux';
import { DateItem, DateRangeType } from 'config/types';
import {
AnticipatoryAction,
DateItem,
DateRangeType,
Panel,
} from 'config/types';
import { dateRangeSelector } from 'context/mapStateSlice/selectors';
import { locales, useSafeTranslation } from 'i18n';
import {
Expand All @@ -26,10 +31,11 @@ import { DateFormat } from 'utils/name-utils';
import { useUrlHistory } from 'utils/url-utils';
import useLayers from 'utils/layers-utils';
import { format } from 'date-fns';
import { Panel, leftPanelTabValueSelector } from 'context/leftPanelStateSlice';
import { leftPanelTabValueSelector } from 'context/leftPanelStateSlice';
import { updateDateRange } from 'context/mapStateSlice';
import { getRequestDate } from 'utils/server-utils';
import { AAAvailableDatesSelector } from 'context/anticipatoryActionStateSlice';
import { isAnticipatoryActionLayer } from 'config/utils';
import TickSvg from './tick.svg';
import DateSelectorInput from './DateSelectorInput';
import TimelineItems from './TimelineItems';
Expand All @@ -51,8 +57,10 @@ const POINTER_ID = 'datePointerSelector';
const calculateStartAndEndDates = (startDate: Date, selectedTab: string) => {
const year =
startDate.getFullYear() -
(selectedTab === 'anticipatory_action' && startDate.getMonth() < 3 ? 1 : 0);
const startMonth = selectedTab === 'anticipatory_action' ? 3 : 0; // April for anticipatory_action, January otherwise
(isAnticipatoryActionLayer(selectedTab) && startDate.getMonth() < 3
? 1
: 0);
const startMonth = isAnticipatoryActionLayer(selectedTab) ? 3 : 0; // April for anticipatory_action, January otherwise
const start = new Date(year, startMonth, 1);
const end = new Date(year, startMonth + 11, 31);

Expand Down Expand Up @@ -123,14 +131,14 @@ const DateSelector = memo(() => {
id: 'anticipatory_action_window_1',
title: 'Window 1',
dateItems: AAAvailableDates['Window 1'],
type: 'anticipatory_action',
type: AnticipatoryAction.drought,
opacity: 1,
},
{
id: 'anticipatory_action_window_2',
title: 'Window 2',
dateItems: AAAvailableDates['Window 2'],
type: 'anticipatory_action',
type: AnticipatoryAction.drought,
opacity: 1,
},
]
Expand All @@ -155,7 +163,7 @@ const DateSelector = memo(() => {
}
return 0;
})
.map(l => (l.type === 'anticipatory_action' ? AALayers : l))
.map(l => (isAnticipatoryActionLayer(l.type) ? AALayers : l))
.flat(),
[selectedLayers, AALayers],
);
Expand Down Expand Up @@ -330,7 +338,7 @@ const DateSelector = memo(() => {
);

// All dates in AA windows should be selectable, regardless of overlap
if (panelTab === Panel.AnticipatoryAction && AAAvailableDates) {
if (panelTab === Panel.AnticipatoryActionDrought && AAAvailableDates) {
// eslint-disable-next-line fp/no-mutating-methods
dates.push(
AAAvailableDates?.['Window 1']?.map(d => d.displayDate) ?? [],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import React from 'react';
import {
AdminLevelDataLayerProps,
AnticipatoryActionLayerProps,
BoundaryLayerProps,
MapEventWrapFunctionProps,
} from 'config/types';
import { useDefaultDate } from 'utils/useDefaultDate';
import { useDispatch, useSelector } from 'react-redux';
import {
layerDataSelector,
mapSelector,
} from 'context/mapStateSlice/selectors';
import { LayerData } from 'context/layers/layer-data';
import {
Layer,
MapLayerMouseEvent,
Marker,
Source,
} from 'react-map-gl/maplibre';
import {
AAFiltersSelector,
AAMarkersSelector,
AARenderedDistrictsSelector,
AASelectedDistrictSelector,
setAAMarkers,
setAASelectedDistrict,
setAAView,
} from 'context/anticipatoryActionStateSlice';
import { getAAColor } from 'components/MapView/LeftPanel/AnticipatoryActionPanel/utils';
import {
calculateCentroids,
useAAMarkerScalePercent,
useMapCallback,
} from 'utils/map-utils';
import { getBoundaryLayersByAdminLevel } from 'config/utils';
import {
calculateAAMarkers,
calculateCombinedAAMapData,
} from 'context/anticipatoryActionStateSlice/utils';
import { AAView } from 'context/anticipatoryActionStateSlice/types';
import { Tooltip } from '@material-ui/core';

// Use admin level 2 boundary layer for Anticipatory Action
const boundaryLayer = getBoundaryLayersByAdminLevel(2);

const onDistrictClick =
({ dispatch }: MapEventWrapFunctionProps<AdminLevelDataLayerProps>) =>
(evt: MapLayerMouseEvent) => {
const districtId =
evt.features?.[0]?.properties?.[boundaryLayer.adminLevelLocalNames[1]];
if (districtId) {
dispatch(setAASelectedDistrict(districtId));
dispatch(setAAView(AAView.District));
}
};

const AnticipatoryActionDrougthLayer = React.memo(
({ layer, before }: LayersProps) => {
useDefaultDate(layer.id);
const boundaryLayerState = useSelector(
layerDataSelector(boundaryLayer.id),
) as LayerData<BoundaryLayerProps> | undefined;
const { data } = boundaryLayerState || {};
const map = useSelector(mapSelector);
const dispatch = useDispatch();
const renderedDistricts = useSelector(AARenderedDistrictsSelector);
const { selectedWindow } = useSelector(AAFiltersSelector);
const selectedDistrict = useSelector(AASelectedDistrictSelector);
const markers = useSelector(AAMarkersSelector);

useMapCallback(
'click',
`anticipatory-action-fill`,
layer as any,
onDistrictClick,
);

const shouldRenderData = React.useMemo(() => {
if (selectedWindow === 'All') {
return calculateCombinedAAMapData(renderedDistricts);
}
if (selectedWindow) {
return Object.fromEntries(
Object.entries(renderedDistricts[selectedWindow]).map(
([dist, values]) => [dist, values[0]],
),
);
}
return {};
}, [renderedDistricts, selectedWindow]);

// Calculate centroids only once per data change
React.useEffect(() => {
const districtCentroids = calculateCentroids(data);
const m = calculateAAMarkers({
renderedDistricts,
selectedWindow,
districtCentroids,
});
dispatch(setAAMarkers(m));
}, [data, dispatch, renderedDistricts, selectedWindow]);

const highlightDistrictLine = React.useMemo(
() => ({
...data,
features: [
data?.features.find(
f =>
f.properties?.[boundaryLayer.adminLevelLocalNames[1]] ===
selectedDistrict,
),
].filter(x => x),
}),
[data, selectedDistrict],
);

const coloredDistrictsLayer = React.useMemo(() => {
const districtEntries = Object.entries(shouldRenderData);
if (!data || !districtEntries.length) {
return null;
}
return {
...data,
features: Object.entries(shouldRenderData)
.map(([districtId, { category, phase }]: [string, any]) => {
const feature = data?.features.find(
f =>
f.properties?.[boundaryLayer.adminLevelLocalNames[1]] ===
districtId,
);

if (!feature) {
return null;
}
const color = getAAColor(category, phase, true);
return {
...feature,
properties: {
...feature.properties,
fillColor: color || 'grey',
},
};
})
.filter(f => f !== null),
};
}, [data, shouldRenderData]);

const scalePercent = useAAMarkerScalePercent(map);

const mainLayerBefore = selectedDistrict
? 'anticipatory-action-selected-line'
: before;

return (
<>
{markers.map(marker => (
<Marker
key={`marker-${marker.district}`}
longitude={marker.longitude}
latitude={marker.latitude}
anchor="center"
>
<Tooltip title={marker.district} arrow>
<div
style={{
transform: `scale(${scalePercent})`,
cursor: 'pointer',
}}
>
{marker.icon}
</div>
</Tooltip>
</Marker>
))}
<Source
id="anticipatory-action-selected"
type="geojson"
data={highlightDistrictLine}
>
<Layer
beforeId={before}
id="anticipatory-action-selected-line"
type="line"
source="anticipatory-action-selected"
paint={{
'line-color': 'red',
'line-width': 4,
'line-opacity': highlightDistrictLine.features.length > 0 ? 1 : 0,
}}
/>
</Source>
{coloredDistrictsLayer && (
<Source
key="anticipatory-action"
id="anticipatory-action-"
type="geojson"
data={coloredDistrictsLayer}
>
<Layer
beforeId={mainLayerBefore}
type="fill"
id="anticipatory-action-fill"
source="anticipatory-action"
layout={{}}
paint={{
'fill-color': ['get', 'fillColor'],
'fill-opacity': 0.9,
}}
/>
<Layer
beforeId={mainLayerBefore}
id="anticipatory-action-boundary"
type="line"
source="anticipatory-action"
paint={{
'line-color': 'black',
}}
/>
</Source>
)}
</>
);
},
);
export interface LayersProps {
layer: AnticipatoryActionLayerProps;
before?: string;
}

export default AnticipatoryActionDrougthLayer;
Loading

0 comments on commit 3e1a407

Please sign in to comment.