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

[DRAFT]: Divided AA into drought and storm layers #1370

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
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
ericboucher marked this conversation as resolved.
Show resolved Hide resolved
? 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 AnticipatoryActionDroughtLayer = 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 AnticipatoryActionDroughtLayer;
Loading
Loading