diff --git a/src/frontend/src/Components/Map.js b/src/frontend/src/Components/Map.js index d7e1b6b5..7309062d 100644 --- a/src/frontend/src/Components/Map.js +++ b/src/frontend/src/Components/Map.js @@ -19,6 +19,7 @@ import { updateRestStops, } from '../slices/feedsSlice'; import { updateMapState } from '../slices/mapSlice'; +import { updateAdvisories } from '../slices/cmsSlice'; // External Components import Button from 'react-bootstrap/Button'; @@ -40,6 +41,7 @@ import { getRegionalPopup, getRestStopPopup, } from './map/mapPopup.js'; +import { getAdvisories } from './data/advisories.js'; import { getEvents } from './data/events.js'; import { getWeather, getRegional } from './data/weather.js'; import { getRestStops } from './data/restStops.js'; @@ -69,10 +71,11 @@ import CurrentCameraIcon from './CurrentCameraIcon'; import Filters from './Filters.js'; import RouteSearch from './map/RouteSearch.js'; -// OpenLayers +// OpenLayers & turf import { applyStyle } from 'ol-mapbox-style'; import { fromLonLat, toLonLat, transformExtent } from 'ol/proj'; import { ScaleLine } from 'ol/control.js'; +import { getBottomLeft, getTopRight } from 'ol/extent'; import Map from 'ol/Map'; import Overlay from 'ol/Overlay.js'; import Geolocation from 'ol/Geolocation.js'; @@ -80,6 +83,7 @@ import MVT from 'ol/format/MVT.js'; import VectorTileLayer from 'ol/layer/VectorTile.js'; import VectorTileSource from 'ol/source/VectorTile.js'; import View from 'ol/View'; +import { booleanIntersects, polygon } from '@turf/turf'; // Styling import { @@ -211,6 +215,8 @@ export default function MapWrapper({ setClickedRestStop(feature); } + const [advisoriesInView, setAdvisoriesInView] = useState([]); + // Define the function to be executed after the delay function resetCameraPopupRef() { cameraPopupRef.current = null; @@ -806,7 +812,51 @@ export default function MapWrapper({ } }; + // Advisories helper functions + function wrapLon(value) { + const worlds = Math.floor((value + 180) / 360); + return value - worlds * 360; + } + + function onMoveEnd(evt) { + // calculate polygon based on map extent + const map = evt.map; + const extent = map.getView().calculateExtent(map.getSize()); + const bottomLeft = toLonLat(getBottomLeft(extent)); + const topRight = toLonLat(getTopRight(extent)); + + const mapPoly = polygon([[ + [wrapLon(bottomLeft[0]), topRight[1]], // Top left + [wrapLon(bottomLeft[0]), bottomLeft[1]], // Bottom left + [wrapLon(topRight[0]), bottomLeft[1]], // Bottom right + [wrapLon(topRight[0]), topRight[1]], // Top right + [wrapLon(bottomLeft[0]), topRight[1]], // Top left + ]]); + + // Update state with advisories that intersect with map extent + const resAdvisories = []; + if (advisories && advisories.length > 0) { + advisories.forEach(advisory => { + const advPoly = polygon(advisory.geometry.coordinates); + if (booleanIntersects(mapPoly, advPoly)) { + resAdvisories.push(advisory); + } + }); + } + setAdvisoriesInView(resAdvisories); + } + // Advisories layer + const loadAdvisories = async () => { + // Fetch data if it doesn't already exist + if (!advisories) { + dispatch(updateAdvisories({ + list: await getAdvisories(), + timeStamp: new Date().getTime() + })); + } + }; + useEffect(() => { // Remove layer if it already exists if (mapLayers.current['advisoriesLayer']) { @@ -824,6 +874,10 @@ export default function MapWrapper({ mapRef.current.addLayer(mapLayers.current['advisoriesLayer']); mapLayers.current['advisoriesLayer'].setZIndex(55); + + if (mapRef.current) { + mapRef.current.on('moveend', onMoveEnd); + } } }, [advisories]); @@ -874,6 +928,7 @@ export default function MapWrapper({ loadWeather(); loadRegional(); loadRestStops(); + loadAdvisories(); // Zoom/pan to route on route updates if (!isInitialMount) { @@ -887,6 +942,7 @@ export default function MapWrapper({ loadWeather(); loadRegional(); loadRestStops(); + loadAdvisories(); } }; @@ -1135,7 +1191,7 @@ export default function MapWrapper({ {!isPreview && (
- +
)} diff --git a/src/frontend/src/Components/advisories/AdvisoriesOnMap.js b/src/frontend/src/Components/advisories/AdvisoriesOnMap.js index 1b850f93..f219ed80 100644 --- a/src/frontend/src/Components/advisories/AdvisoriesOnMap.js +++ b/src/frontend/src/Components/advisories/AdvisoriesOnMap.js @@ -1,13 +1,7 @@ // React -import React, { useCallback, useEffect, useRef, useState } from 'react'; - -// Redux -import { useSelector, useDispatch } from 'react-redux' -import { memoize } from 'proxy-memoize' -import { updateAdvisories } from '../../slices/cmsSlice'; +import React, { useState } from 'react'; // Components and functions -import { getAdvisories } from '../data/advisories.js'; import AdvisoriesList from './AdvisoriesList'; // Third party packages @@ -21,33 +15,9 @@ import Button from 'react-bootstrap/Button'; // Styling import './AdvisoriesOnMap.scss'; -export default function AdvisoriesOnMap() { - // Redux - const dispatch = useDispatch(); - const { advisories } = useSelector(useCallback(memoize(state => ({ - advisories: state.cms.advisories.list, - })))); - - // Refs - const isInitialMount = useRef(true); - - // Data loading - const loadAdvisories = async () => { - if (!advisories) { - dispatch(updateAdvisories({ - list: await getAdvisories(), - timeStamp: new Date().getTime() - })); - } - } +export default function AdvisoriesOnMap(props) { + const { advisories } = props; - useEffect(() => { - if (isInitialMount.current) { // Only run on initial load - loadAdvisories(); - isInitialMount.current = false; - } - }); - // States const [open, setOpen] = useState(false);