From ce65a79720161361064f5a9fc60e61dc816771d5 Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Tue, 27 Sep 2022 16:31:01 +0100 Subject: [PATCH 1/8] fix(frontend): Fix pin functionality for group icons Fixed the pin functionality for the group icons, which turned out to be a pretty tricky change. It involved updating the base-level PinLayer to recognise the difference between a cluster and a group, then call the passed getPinColor method in that case. Then the function in mapHelpers that determines pin color had to be updated to also recognise groups, and work out if the selected item is in that group by analyising the coordinates of each. Finally, each of the Chatbot screens had to be updated to find the selected item from the list of all, then pass the id and coordinates to the layer handler in order to make all of the above possible. --- src/components/BaseMap/PinLayer.js | 17 ++++++++++++----- src/helpers/mapHelper.js | 17 ++++++++++++++++- src/pages/Chatbot/Comms/index.js | 9 ++++++++- src/pages/Chatbot/Missions/index.js | 9 ++++++++- src/pages/Chatbot/People/index.js | 9 ++++++++- src/pages/Chatbot/Reports/index.js | 10 ++++++++-- 6 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/components/BaseMap/PinLayer.js b/src/components/BaseMap/PinLayer.js index a2842242..159b265f 100644 --- a/src/components/BaseMap/PinLayer.js +++ b/src/components/BaseMap/PinLayer.js @@ -111,11 +111,18 @@ export class PinLayer extends CompositeLayer { } _getPinColor(feature) { - if ( - feature.properties.cluster && - this._getExpansionZoom(feature) <= this.props.maxZoom - ) - return [246, 190, 0, 255]; + if (feature.properties.cluster) { + const expansionZoom = this._getExpansionZoom(feature); + + const isCluster = expansionZoom <= this.props.maxZoom; + const isGroup = expansionZoom >= this.props.maxZoom; + + if (isGroup) { + return this.props.getPinColor(feature) + } else if (isCluster) { + return [246, 190, 0, 255]; + } + } if (typeof this.props.getPinColor === 'function') return this.props.getPinColor(feature); if (isArray(this.props.pinColor)) return this.props.pinColor; diff --git a/src/helpers/mapHelper.js b/src/helpers/mapHelper.js index 1674f682..6d047928 100644 --- a/src/helpers/mapHelper.js +++ b/src/helpers/mapHelper.js @@ -62,12 +62,27 @@ export const getPolygonLayer = (aoi) => { })) } +const selectedItemIsInGroup = (feature, selectedItem) => { + const clusterCoords = feature.geometry.coordinates; + const selectedItemCoords = selectedItem.center.map(coord => coord.toFixed(2)); + + return clusterCoords.every( + coord => selectedItemCoords.includes(coord.toFixed(2)) + ); +} + export const getAlertIconColorFromContext = (mapType, feature, selectedItem = {}) => { let color = DARK_GRAY; if (!feature.properties.id && !selectedItem.id) { return color; } + if (feature.properties.cluster) { + if (selectedItemIsInGroup(feature, selectedItem)) { + return ORANGE; + } + } + if (feature.properties.id === selectedItem.id) { color = ORANGE; } else if (ALERT_TYPES.red.includes(feature?.properties?.status)) { @@ -100,7 +115,7 @@ export const getIconLayer = (alerts, mapType, markerName='alert', dispatch, setV dispatch, setViewState, getPosition: (feature) => feature.geometry.coordinates, - getPinColor: feature => getAlertIconColorFromContext(mapType, feature, selectedItem), + getPinColor: (feature) => getAlertIconColorFromContext(mapType, feature, selectedItem), icon: markerName, iconColor: '#ffffff', clusterIconSize: 35, diff --git a/src/pages/Chatbot/Comms/index.js b/src/pages/Chatbot/Comms/index.js index 29fe750f..dac7cfbd 100644 --- a/src/pages/Chatbot/Comms/index.js +++ b/src/pages/Chatbot/Comms/index.js @@ -70,7 +70,14 @@ const Comms = () => { useEffect(() => { if (allReports.length > 0) { - setIconLayer(getIconLayer(allReports, MAP_TYPES.COMMUNICATIONS, 'communications', dispatch, setViewState, {id: commID})); + + const selectedComm = allReports.find(comm => comm.id === commID) + + const pinInfo = selectedComm + ? { center: selectedComm.location, id: commID } + : {}; + + setIconLayer(getIconLayer(allReports, MAP_TYPES.COMMUNICATIONS, 'communications', dispatch, setViewState, pinInfo)); } }, [allReports, commID]); diff --git a/src/pages/Chatbot/Missions/index.js b/src/pages/Chatbot/Missions/index.js index 858d82ff..de3d96f0 100644 --- a/src/pages/Chatbot/Missions/index.js +++ b/src/pages/Chatbot/Missions/index.js @@ -84,7 +84,14 @@ const Missions = () => { useEffect(() => { if (allMissions.length > 0) { - setIconLayer(getIconLayer(allMissions, MAP_TYPES.MISSIONS, 'target', dispatch, setViewState, {id: missionId})); + + const selectedMission = allMissions.find(mission => mission.id === missionId) + + const pinInfo = selectedMission + ? { center: selectedMission.location, id: missionId } + : {} + + setIconLayer(getIconLayer(allMissions, MAP_TYPES.MISSIONS, 'target', dispatch, setViewState, pinInfo)); } }, [allMissions, missionId]); diff --git a/src/pages/Chatbot/People/index.js b/src/pages/Chatbot/People/index.js index 29ca5d18..9ce23fad 100644 --- a/src/pages/Chatbot/People/index.js +++ b/src/pages/Chatbot/People/index.js @@ -78,7 +78,14 @@ const People = () => { useEffect(() => { if (allPeople.length > 0) { - setIconLayer(getIconLayer(allPeople, MAP_TYPES.PEOPLE, 'people', dispatch, setViewState, {id: peopleId})); + + const selectedPeople = allPeople.find(people => people.id === peopleId) + + const pinInfo = selectedPeople + ? { center: selectedPeople.location, id: peopleId } + : {}; + + setIconLayer(getIconLayer(allPeople, MAP_TYPES.PEOPLE, 'people', dispatch, setViewState, pinInfo)); } }, [allPeople, peopleId]); diff --git a/src/pages/Chatbot/Reports/index.js b/src/pages/Chatbot/Reports/index.js index 4521fd3b..67312084 100644 --- a/src/pages/Chatbot/Reports/index.js +++ b/src/pages/Chatbot/Reports/index.js @@ -66,9 +66,15 @@ const Reports = () => { const reshapedReports = allReports.map(report => { const {report_id: id, ...rest} = report; return { id, ...rest }; - }) + }); - setIconLayer(getIconLayer(reshapedReports, MAP_TYPES.REPORTS, 'report', dispatch, setViewState, { id: reportId }, 'report_id')); + const selectedReport = allReports.find(report => report.report_id === reportId) + + const pinInfo = selectedReport + ? { center: selectedReport.location, id: reportId } + : {}; + + setIconLayer(getIconLayer(reshapedReports, MAP_TYPES.REPORTS, 'report', dispatch, setViewState, pinInfo, 'report_id')); } }, [allReports, reportId]); From 10b8c1d01948f55d7e00f748f3857c39fc61fecf Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Thu, 29 Sep 2022 11:27:19 +0100 Subject: [PATCH 2/8] fix(frontend): Add group icon fix to in-situ cameras Added the fix to allow group icons to update to the in-situ cameras panel. There were some strange bugs here that greatly slowed down developent, one of which was there being a second GeoJsonPinLayer in the alertList for some reason, that was resetting the icons as soon one was clicked. And the other was the cameraList being initialised to an array, even though the returned data is a featureCollection object. --- src/pages/FireAlerts/index.js | 17 +++++++--- src/pages/In-situ/Components/AlertList.js | 41 +++++++++-------------- src/pages/In-situ/index.js | 34 ++++++------------- 3 files changed, 40 insertions(+), 52 deletions(-) diff --git a/src/pages/FireAlerts/index.js b/src/pages/FireAlerts/index.js index aed61b14..e1a6f8f0 100644 --- a/src/pages/FireAlerts/index.js +++ b/src/pages/FireAlerts/index.js @@ -297,12 +297,21 @@ const FireAlerts = ({ t }) => { } }; - const getCard = (card, index) => { + const handleSelectAlert = id => { + if (id === alertId) { + setSelectedAlert(undefined); + setHoverInfo(undefined) + } else { + setSelectedAlert(id); + } + } + + const getCard = (card) => { return ( @@ -396,7 +405,7 @@ const FireAlerts = ({ t }) => { - {paginatedAlerts.map((alert, index) => getCard(alert, index))} + {paginatedAlerts.map(getCard)} { - return new GeoJsonPinLayer({ - data: alerts, - dispatch, - setViewState, - getPosition: (feature) => feature.geometry.coordinates, - getPinColor: feature => getAlertIconColorFromContext(MAP_TYPES.IN_SITU,feature), - icon: 'camera', - iconColor: '#ffffff', - clusterIconSize: 35, - getPinSize: () => 35, - pixelOffset: [-18,-18], - pinSize: 25, - onGroupClick: true, - onPointClick: true, - }); - }; - useEffect(() => { if (selCam) { !_.isEqual(viewState.midPoint, cameraInfo?.geometry?.coordinates) || isViewStateChanged ? setViewState(getViewState(cameraInfo?.geometry?.coordinates, currentZoomLevel, cameraInfo, setHoverInfo, setIsViewStateChanged)) : setHoverInfo({ object: { properties: cameraInfo, geometry: cameraInfo?.geometry}, picked: true }); - setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU)); } }, [cameraInfo]); @@ -80,7 +60,9 @@ const AlertList = ({ dispatch(getCamera(selectedAlert.camera_id)); } else { setAlertId(undefined); - setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU)); + if (cameraList.features) { + setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU)); + } } } @@ -94,14 +76,23 @@ const AlertList = ({ dispatch(setPaginatedAlerts(_.cloneDeep(filteredAlerts.slice(from, to)))); }; + const handleSelectAlert = (id) => { + if (id === alertId) { + setSelectedAlert(undefined); + setHoverInfo(undefined); + } else { + setSelectedAlert(id); + } + } + return ( <> { - paginatedAlerts.map((alert, index) => ) } diff --git a/src/pages/In-situ/index.js b/src/pages/In-situ/index.js index ba219473..981d3814 100644 --- a/src/pages/In-situ/index.js +++ b/src/pages/In-situ/index.js @@ -20,12 +20,11 @@ import { } from '../../store/appAction'; import { getBoundingBox, getViewState } from '../../helpers/mapHelper'; import { PAGE_SIZE } from '../../store/events/types'; -import { GeoJsonPinLayer } from '../../components/BaseMap/GeoJsonPinLayer'; //i18n import { useTranslation } from 'react-i18next' import { MAP_TYPES } from '../../constants/common'; -import { getAlertIconColorFromContext } from '../../helpers/mapHelper'; +import { getIconLayer } from '../../helpers/mapHelper'; const InSituAlerts = () => { const defaultAoi = useSelector(state => state.user.defaultAoi); @@ -49,25 +48,6 @@ const InSituAlerts = () => { const { t } = useTranslation(); const dispatch = useDispatch(); - const getIconLayer = (alerts) => { - return new GeoJsonPinLayer({ - data: alerts, - dispatch, - setViewState, - getPosition: (feature) => feature.geometry.coordinates, - getPinColor: feature => getAlertIconColorFromContext(MAP_TYPES.IN_SITU,feature), - icon: 'camera', - iconColor: '#ffffff', - clusterIconSize: 35, - getPinSize: () => 35, - pixelOffset: [-18,-18], - pinSize: 25, - onGroupClick: true, - onPointClick: true, - }); - }; - - useEffect(() => { dispatch(getCameraSources()); }, []) @@ -108,8 +88,16 @@ const InSituAlerts = () => { }, [success, error]); useEffect(() => { - setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU)); - }, [cameraList]); + if (cameraList.features) { + const selectedAlert = alerts.find(alert => alert.id === alertId); + + const pinInfo = selectedAlert + ? { center: selectedAlert.geometry.coordinates, id: alertId } + : {}; + + setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU, 'camera', dispatch, setViewState, pinInfo)); + } + }, [cameraList, alertId]); useEffect(() => { if (!viewState) { From 4ff58d630fc73a4f5a4dabae7ed6ea157f45cc93 Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Thu, 29 Sep 2022 12:58:40 +0100 Subject: [PATCH 3/8] fix(frontend): Revert some changes, extend functionality Reverted removing the duplicated GeoJsonPinLayer in in-situ, as it doesn't need to convert to GeoJson like the helper one does. However, I had to extend the dupliated one to 1. include the pinInfo required to identify the selected icon, and 2. pass this function through to the list so that both the list and map are sharing the same GeoJsonLayer. --- src/pages/In-situ/Components/AlertList.js | 5 ++-- src/pages/In-situ/index.js | 36 ++++++++++++++++++----- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/pages/In-situ/Components/AlertList.js b/src/pages/In-situ/Components/AlertList.js index 8463666d..4b47d7e6 100644 --- a/src/pages/In-situ/Components/AlertList.js +++ b/src/pages/In-situ/Components/AlertList.js @@ -9,7 +9,6 @@ import { setCurrentPage, setInSituFavoriteAlert, setPaginatedAlerts, getCamera } import { PAGE_SIZE, SET_FAV_INSITU_ALERT_SUCCESS } from '../../../store/insitu/types'; import Alert from './Alert'; import { MAP_TYPES } from '../../../constants/common'; -import { getIconLayer } from '../../../helpers/mapHelper'; const AlertList = ({ alertId, @@ -21,7 +20,8 @@ const AlertList = ({ setIconLayer, setHoverInfo, setIsViewStateChanged, - hideTooltip + hideTooltip, + getIconLayer }) => { const { paginatedAlerts, currentPage, filteredAlerts, cameraList, cameraInfo } = useSelector(state => state.inSituAlerts); const [selCam, setsSelCam] = useState(undefined); @@ -119,5 +119,6 @@ AlertList.propTypes = { setHoverInfo: PropTypes.func, hideTooltip: PropTypes.func, setIsViewStateChanged: PropTypes.func, + getIconLayer: PropTypes.func } export default AlertList; diff --git a/src/pages/In-situ/index.js b/src/pages/In-situ/index.js index 981d3814..27735034 100644 --- a/src/pages/In-situ/index.js +++ b/src/pages/In-situ/index.js @@ -20,11 +20,12 @@ import { } from '../../store/appAction'; import { getBoundingBox, getViewState } from '../../helpers/mapHelper'; import { PAGE_SIZE } from '../../store/events/types'; +import { GeoJsonPinLayer } from '../../components/BaseMap/GeoJsonPinLayer'; +import { getAlertIconColorFromContext } from '../../helpers/mapHelper'; //i18n import { useTranslation } from 'react-i18next' import { MAP_TYPES } from '../../constants/common'; -import { getIconLayer } from '../../helpers/mapHelper'; const InSituAlerts = () => { const defaultAoi = useSelector(state => state.user.defaultAoi); @@ -48,6 +49,12 @@ const InSituAlerts = () => { const { t } = useTranslation(); const dispatch = useDispatch(); + const selectedAlert = alerts.find(alert => alert.id === alertId); + + const pinInfo = selectedAlert + ? { center: selectedAlert.geometry.coordinates, id: alertId } + : {}; + useEffect(() => { dispatch(getCameraSources()); }, []) @@ -89,13 +96,7 @@ const InSituAlerts = () => { useEffect(() => { if (cameraList.features) { - const selectedAlert = alerts.find(alert => alert.id === alertId); - - const pinInfo = selectedAlert - ? { center: selectedAlert.geometry.coordinates, id: alertId } - : {}; - - setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU, 'camera', dispatch, setViewState, pinInfo)); + setIconLayer(getIconLayer(cameraList.features)); } }, [cameraList, alertId]); @@ -115,6 +116,24 @@ const InSituAlerts = () => { dispatch(setPaginatedAlerts(_.cloneDeep(filteredAlerts.slice(0, PAGE_SIZE)))) }, [filteredAlerts]); + const getIconLayer = (data) => { + return new GeoJsonPinLayer({ + data, + dispatch, + setViewState, + getPosition: (feature) => feature.geometry.coordinates, + getPinColor: feature => getAlertIconColorFromContext(MAP_TYPES.IN_SITU, feature, pinInfo), + icon: 'camera', + iconColor: '#ffffff', + clusterIconSize: 35, + getPinSize: () => 35, + pixelOffset: [-18,-18], + pinSize: 25, + onGroupClick: true, + onPointClick: true, + }); + }; + const handleResetAOI = useCallback(() => { setViewState(getViewState(defaultAoi.features[0].properties.midPoint, defaultAoi.features[0].properties.zoomLevel)) }, []); @@ -174,6 +193,7 @@ const InSituAlerts = () => { hideTooltip={hideTooltip} setViewState={setViewState} setIsViewStateChanged={setIsViewStateChanged} + getIconLayer={getIconLayer} /> From 487c76242ec7f32c89f3bc7a9da9e489a4c75a8d Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Fri, 30 Sep 2022 10:56:20 +0100 Subject: [PATCH 4/8] fix(frontend): In-situ cameras clicks hooked up After some refactoring, the clicks for in-situ cameras (which work a bit differently from the others) are working as expected. This involved a bit of juggling between when to use alert ids and camera ids, but the camera ids seem to be the correct choice for this kind of identification. Also refactored the PinLayer to be able to differentiate between clusters and groups in the getIconColor function, and the generic mapHelper to be able to tell if incoming data is already a feature collection before reshaping. --- src/components/BaseMap/PinLayer.js | 2 +- src/helpers/mapHelper.js | 25 ++++----- src/pages/FireAlerts/index.js | 10 +++- src/pages/In-situ/Components/Alert.js | 2 +- src/pages/In-situ/Components/AlertList.js | 14 ++--- src/pages/In-situ/index.js | 68 +++++++++++------------ 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/components/BaseMap/PinLayer.js b/src/components/BaseMap/PinLayer.js index 159b265f..ae7d7d04 100644 --- a/src/components/BaseMap/PinLayer.js +++ b/src/components/BaseMap/PinLayer.js @@ -115,7 +115,7 @@ export class PinLayer extends CompositeLayer { const expansionZoom = this._getExpansionZoom(feature); const isCluster = expansionZoom <= this.props.maxZoom; - const isGroup = expansionZoom >= this.props.maxZoom; + const isGroup = expansionZoom > this.props.maxZoom; if (isGroup) { return this.props.getPinColor(feature) diff --git a/src/helpers/mapHelper.js b/src/helpers/mapHelper.js index 6d047928..edb0abe1 100644 --- a/src/helpers/mapHelper.js +++ b/src/helpers/mapHelper.js @@ -94,19 +94,18 @@ export const getAlertIconColorFromContext = (mapType, feature, selectedItem = {} } -export const getAsGeoJSON = (data) => { - return data.map((datum) => { - const { - geometry, - ...properties - } = datum; - return { - type: 'Feature', - properties, - geometry, - }; - }); -} +export const getAsGeoJSON = (data) => data.map((datum) => { + if (datum.type === 'Feature') return datum; + const { + geometry, + ...properties + } = datum; + return { + type: 'Feature', + properties, + geometry, + }; +}) export const getIconLayer = (alerts, mapType, markerName='alert', dispatch, setViewState, selectedItem = {}) => { const data = getAsGeoJSON(alerts); diff --git a/src/pages/FireAlerts/index.js b/src/pages/FireAlerts/index.js index e1a6f8f0..7d808a28 100644 --- a/src/pages/FireAlerts/index.js +++ b/src/pages/FireAlerts/index.js @@ -268,9 +268,15 @@ const FireAlerts = ({ t }) => { // Prevents clicks on grouped icons return; } else if (info.picked && info.object) { - setSelectedAlert(info.object.id); - setHoverInfo(info); + if (info.object.properties.id !== alertId) { + setSelectedAlert(info.object.properties.id); + setHoverInfo(info); + } else { + setSelectedAlert(undefined); + setHoverInfo(undefined); + } } else { + setSelectedAlert(undefined); setHoverInfo(undefined); } }; diff --git a/src/pages/In-situ/Components/Alert.js b/src/pages/In-situ/Components/Alert.js index 7b519144..b5fbff3d 100644 --- a/src/pages/In-situ/Components/Alert.js +++ b/src/pages/In-situ/Components/Alert.js @@ -77,7 +77,7 @@ const Alert = ({ card, alertId, setSelectedAlert, setFavorite, t }) => { return ( <> setSelectedAlert(card.id)} + onClick={() => setSelectedAlert(card.id, card.camera_id)} className={'alerts-card mb-2 pt-1 ' + (card.id == alertId ? 'alert-card-active' : '')}> diff --git a/src/pages/In-situ/Components/AlertList.js b/src/pages/In-situ/Components/AlertList.js index 4b47d7e6..a1cb4462 100644 --- a/src/pages/In-situ/Components/AlertList.js +++ b/src/pages/In-situ/Components/AlertList.js @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { Row } from 'reactstrap'; -import { getViewState } from '../../../helpers/mapHelper'; +import { getViewState, getIconLayer } from '../../../helpers/mapHelper'; import { setCurrentPage, setInSituFavoriteAlert, setPaginatedAlerts, getCamera } from '../../../store/appAction'; import { PAGE_SIZE, SET_FAV_INSITU_ALERT_SUCCESS } from '../../../store/insitu/types'; import Alert from './Alert'; @@ -14,6 +14,7 @@ const AlertList = ({ alertId, viewState, setAlertId, + setCameraId, currentZoomLevel, isViewStateChanged, setViewState, @@ -21,7 +22,6 @@ const AlertList = ({ setHoverInfo, setIsViewStateChanged, hideTooltip, - getIconLayer }) => { const { paginatedAlerts, currentPage, filteredAlerts, cameraList, cameraInfo } = useSelector(state => state.inSituAlerts); const [selCam, setsSelCam] = useState(undefined); @@ -60,9 +60,7 @@ const AlertList = ({ dispatch(getCamera(selectedAlert.camera_id)); } else { setAlertId(undefined); - if (cameraList.features) { - setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU)); - } + setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU)); } } @@ -76,12 +74,14 @@ const AlertList = ({ dispatch(setPaginatedAlerts(_.cloneDeep(filteredAlerts.slice(from, to)))); }; - const handleSelectAlert = (id) => { + const handleSelectAlert = (id, cameraId) => { if (id === alertId) { setSelectedAlert(undefined); setHoverInfo(undefined); + setCameraId(undefined); } else { setSelectedAlert(id); + setCameraId(cameraId) } } @@ -114,11 +114,11 @@ AlertList.propTypes = { currentZoomLevel: PropTypes.any, isViewStateChanged: PropTypes.any, setViewState: PropTypes.func, + setCameraId: PropTypes.func, setAlertId: PropTypes.func, setIconLayer: PropTypes.func, setHoverInfo: PropTypes.func, hideTooltip: PropTypes.func, setIsViewStateChanged: PropTypes.func, - getIconLayer: PropTypes.func } export default AlertList; diff --git a/src/pages/In-situ/index.js b/src/pages/In-situ/index.js index 27735034..1a7ce708 100644 --- a/src/pages/In-situ/index.js +++ b/src/pages/In-situ/index.js @@ -18,10 +18,9 @@ import { getCameraList, getCameraSources } from '../../store/appAction'; -import { getBoundingBox, getViewState } from '../../helpers/mapHelper'; +import { getBoundingBox, getViewState, getIconLayer } from '../../helpers/mapHelper'; import { PAGE_SIZE } from '../../store/events/types'; -import { GeoJsonPinLayer } from '../../components/BaseMap/GeoJsonPinLayer'; -import { getAlertIconColorFromContext } from '../../helpers/mapHelper'; + //i18n import { useTranslation } from 'react-i18next' @@ -40,6 +39,7 @@ const InSituAlerts = () => { const [boundingBox, setBoundingBox] = useState(undefined); const [currentZoomLevel, setCurrentZoomLevel] = useState(undefined); const [alertId, setAlertId] = useState(undefined); + const [cameraId, setCameraId] = useState(undefined); const [hoverInfo, setHoverInfo] = useState(undefined); const [checkedStatus, setCheckedStatus] = useState([]) const [isViewStateChanged, setIsViewStateChanged] = useState(false); @@ -49,12 +49,6 @@ const InSituAlerts = () => { const { t } = useTranslation(); const dispatch = useDispatch(); - const selectedAlert = alerts.find(alert => alert.id === alertId); - - const pinInfo = selectedAlert - ? { center: selectedAlert.geometry.coordinates, id: alertId } - : {}; - useEffect(() => { dispatch(getCameraSources()); }, []) @@ -96,9 +90,15 @@ const InSituAlerts = () => { useEffect(() => { if (cameraList.features) { - setIconLayer(getIconLayer(cameraList.features)); + const selectedAlert = alerts.find(alert => alert.camera_id === cameraId); + + const pinInfo = selectedAlert + ? { center: selectedAlert.geometry.coordinates, id: cameraId } + : {}; + + setIconLayer(getIconLayer(cameraList.features, MAP_TYPES.IN_SITU, 'camera', dispatch, setViewState, pinInfo)); } - }, [cameraList, alertId]); + }, [cameraList, cameraId]); useEffect(() => { if (!viewState) { @@ -116,33 +116,34 @@ const InSituAlerts = () => { dispatch(setPaginatedAlerts(_.cloneDeep(filteredAlerts.slice(0, PAGE_SIZE)))) }, [filteredAlerts]); - const getIconLayer = (data) => { - return new GeoJsonPinLayer({ - data, - dispatch, - setViewState, - getPosition: (feature) => feature.geometry.coordinates, - getPinColor: feature => getAlertIconColorFromContext(MAP_TYPES.IN_SITU, feature, pinInfo), - icon: 'camera', - iconColor: '#ffffff', - clusterIconSize: 35, - getPinSize: () => 35, - pixelOffset: [-18,-18], - pinSize: 25, - onGroupClick: true, - onPointClick: true, - }); - }; - const handleResetAOI = useCallback(() => { setViewState(getViewState(defaultAoi.features[0].properties.midPoint, defaultAoi.features[0].properties.zoomLevel)) }, []); + const setMapData = (info = undefined, id = undefined) => { + setHoverInfo(info); + setCameraId(id); + } + const showTooltip = info => { - if (info) { - setHoverInfo(info); + if (info.object) { + if (info.objects) { + const ids = info.objects.map(f => f.properties.id); + if (ids.includes(cameraId)) { + setMapData(); + } else { + setMapData(info, ids[0]); + } + } else { + const id = info.object.properties.id; + if (cameraId === id) { + setMapData(); + } else { + setMapData(info, id); + } + } } else { - setHoverInfo(undefined); + setMapData(); } }; @@ -188,13 +189,12 @@ const InSituAlerts = () => { isViewStateChanged={isViewStateChanged} alertId={alertId} setAlertId={setAlertId} + setCameraId={setCameraId} setIconLayer={setIconLayer} setHoverInfo={setHoverInfo} hideTooltip={hideTooltip} setViewState={setViewState} setIsViewStateChanged={setIsViewStateChanged} - getIconLayer={getIconLayer} - /> From 1e5ea3e1029a4ba8dd064c73f2fca8669872b630 Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Fri, 30 Sep 2022 11:29:37 +0100 Subject: [PATCH 5/8] fix(frontend): Refactor in-situ clicks, fix events group click Refactores the click handler for in-situ to make it cleaner, and fixed the events group click as it was not working correctly. --- src/pages/Events/Components/Alert.js | 4 +++- src/pages/Events/Components/EventList.js | 4 ++-- src/pages/Events/index.js | 5 +++++ src/pages/In-situ/index.js | 14 ++++---------- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pages/Events/Components/Alert.js b/src/pages/Events/Components/Alert.js index 8b2eee98..f4da72f0 100644 --- a/src/pages/Events/Components/Alert.js +++ b/src/pages/Events/Components/Alert.js @@ -19,7 +19,9 @@ const Alert = ({ card, alertId, setSelectedAlert, setFavorite }) => { return ( setSelectedAlert(card.id)} + onClick={() => setSelectedAlert( + card.id === alertId ? undefined : card.id + )} className={'alerts-card mb-2 ' + (card.id == alertId ? 'alert-card-active' : '')}> diff --git a/src/pages/Events/Components/EventList.js b/src/pages/Events/Components/EventList.js index 6a1495c6..fd768391 100644 --- a/src/pages/Events/Components/EventList.js +++ b/src/pages/Events/Components/EventList.js @@ -65,8 +65,8 @@ const EventList = ({ <> { - paginatedAlerts.map((alert, index) => { }; const showTooltip = info => { + if (info.objects) { + // Prevents clicks on grouped icons + return; + } if (info.picked && info.object) { setSelectedAlert(info.object.properties.id); setHoverInfo(info); @@ -203,6 +207,7 @@ const EventAlerts = ({ t }) => { setIconLayer(getIconLayer(clonedAlerts, selectedAlert)); } else { setAlertId(undefined); + setHoverInfo({}); setIconLayer(getIconLayer(filteredAlerts)); } } diff --git a/src/pages/In-situ/index.js b/src/pages/In-situ/index.js index 1a7ce708..2466e19c 100644 --- a/src/pages/In-situ/index.js +++ b/src/pages/In-situ/index.js @@ -128,19 +128,13 @@ const InSituAlerts = () => { const showTooltip = info => { if (info.object) { if (info.objects) { + // if group icon const ids = info.objects.map(f => f.properties.id); - if (ids.includes(cameraId)) { - setMapData(); - } else { - setMapData(info, ids[0]); - } + ids.includes(cameraId) ? setMapData() : setMapData(info, ids[0]) } else { + // if single icon const id = info.object.properties.id; - if (cameraId === id) { - setMapData(); - } else { - setMapData(info, id); - } + cameraId === id ? setMapData() : setMapData(info, id) } } else { setMapData(); From 802d095f269011533043691b0dbcb417547dc007 Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Fri, 30 Sep 2022 12:23:28 +0100 Subject: [PATCH 6/8] fix(frontend): Revert changes to PinLayer Reverted the condition added to the PinLayer, as it turns out it is unnecessary. IssueID: SAFB-249 --- src/components/BaseMap/PinLayer.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/components/BaseMap/PinLayer.js b/src/components/BaseMap/PinLayer.js index ae7d7d04..a2842242 100644 --- a/src/components/BaseMap/PinLayer.js +++ b/src/components/BaseMap/PinLayer.js @@ -111,18 +111,11 @@ export class PinLayer extends CompositeLayer { } _getPinColor(feature) { - if (feature.properties.cluster) { - const expansionZoom = this._getExpansionZoom(feature); - - const isCluster = expansionZoom <= this.props.maxZoom; - const isGroup = expansionZoom > this.props.maxZoom; - - if (isGroup) { - return this.props.getPinColor(feature) - } else if (isCluster) { - return [246, 190, 0, 255]; - } - } + if ( + feature.properties.cluster && + this._getExpansionZoom(feature) <= this.props.maxZoom + ) + return [246, 190, 0, 255]; if (typeof this.props.getPinColor === 'function') return this.props.getPinColor(feature); if (isArray(this.props.pinColor)) return this.props.pinColor; From 8b79aff5d2b8362ae22ed4867bbb2ba006eb7ac1 Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Mon, 10 Oct 2022 09:28:15 +0100 Subject: [PATCH 7/8] fix(frontend): Improve group icon click functionality By searching for the selected icon inside an array of cluster leaf node ids, it allows for pinpoint accurate selection, as opposed to the coordinate match method used before, which worked (kind of), but was not nearly as accurate. Is also much simpler code to read. --- src/components/BaseMap/PinLayer.js | 8 +++++--- src/helpers/mapHelper.js | 17 +++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/components/BaseMap/PinLayer.js b/src/components/BaseMap/PinLayer.js index a2842242..8a64633a 100644 --- a/src/components/BaseMap/PinLayer.js +++ b/src/components/BaseMap/PinLayer.js @@ -70,7 +70,6 @@ export class PinLayer extends CompositeLayer { } getPickingInfo({ info, mode }) { - if (info.picked) { if (info.object.properties.cluster) { info.object.properties.expansion_zoom = this._getExpansionZoom( @@ -116,8 +115,11 @@ export class PinLayer extends CompositeLayer { this._getExpansionZoom(feature) <= this.props.maxZoom ) return [246, 190, 0, 255]; - if (typeof this.props.getPinColor === 'function') - return this.props.getPinColor(feature); + if (typeof this.props.getPinColor === 'function') { + const { index } = this.state; + const leaves = index.getLeaves(feature.properties.cluster_id) + return this.props.getPinColor(feature, leaves); + } if (isArray(this.props.pinColor)) return this.props.pinColor; const colorInstance = color(this.props.pinColor); const { r, g, b } = colorInstance.rgb(); diff --git a/src/helpers/mapHelper.js b/src/helpers/mapHelper.js index edb0abe1..eca66877 100644 --- a/src/helpers/mapHelper.js +++ b/src/helpers/mapHelper.js @@ -62,23 +62,20 @@ export const getPolygonLayer = (aoi) => { })) } -const selectedItemIsInGroup = (feature, selectedItem) => { - const clusterCoords = feature.geometry.coordinates; - const selectedItemCoords = selectedItem.center.map(coord => coord.toFixed(2)); - - return clusterCoords.every( - coord => selectedItemCoords.includes(coord.toFixed(2)) - ); +const selectedItemIsInGroup = (selectedItem, clusterLeaves) => { + const clusterLeafIds = clusterLeaves.map(feature => feature.properties.id); + return clusterLeafIds.includes(selectedItem.id); } -export const getAlertIconColorFromContext = (mapType, feature, selectedItem = {}) => { +export const getAlertIconColorFromContext = (mapType, feature, selectedItem = {}, clusterLeaves) => { + let color = DARK_GRAY; if (!feature.properties.id && !selectedItem.id) { return color; } if (feature.properties.cluster) { - if (selectedItemIsInGroup(feature, selectedItem)) { + if (selectedItemIsInGroup(selectedItem, clusterLeaves)) { return ORANGE; } } @@ -114,7 +111,7 @@ export const getIconLayer = (alerts, mapType, markerName='alert', dispatch, setV dispatch, setViewState, getPosition: (feature) => feature.geometry.coordinates, - getPinColor: (feature) => getAlertIconColorFromContext(mapType, feature, selectedItem), + getPinColor: (feature, clusterLeaves) => getAlertIconColorFromContext(mapType, feature, selectedItem, clusterLeaves), icon: markerName, iconColor: '#ffffff', clusterIconSize: 35, From ac887cfa5cd21ca1953651ad16bdef5de4f9c847 Mon Sep 17 00:00:00 2001 From: Rory MacGregor Date: Mon, 10 Oct 2022 09:36:30 +0100 Subject: [PATCH 8/8] fix(frontend): Move color to constant Just moved a color from being hardcoded in place to using a constant. --- src/components/BaseMap/PinLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BaseMap/PinLayer.js b/src/components/BaseMap/PinLayer.js index 8a64633a..c0998fc6 100644 --- a/src/components/BaseMap/PinLayer.js +++ b/src/components/BaseMap/PinLayer.js @@ -114,7 +114,7 @@ export class PinLayer extends CompositeLayer { feature.properties.cluster && this._getExpansionZoom(feature) <= this.props.maxZoom ) - return [246, 190, 0, 255]; + return COLOR_PRIMARY; if (typeof this.props.getPinColor === 'function') { const { index } = this.state; const leaves = index.getLeaves(feature.properties.cluster_id)