diff --git a/src/frontend/src/Components/Map.js b/src/frontend/src/Components/Map.js index 8606916b2..7309062d8 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'; @@ -33,7 +34,6 @@ import { // Components and functions import CamPopup from './map/camPopup.js'; -import { getCamerasLayer } from './map/layers/camerasLayer.js'; import { getEventPopup, getFerryPopup, @@ -41,9 +41,12 @@ 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'; +import { getAdvisoriesLayer } from './map/layers/advisoriesLayer.js'; +import { getCamerasLayer } from './map/layers/camerasLayer.js'; import { getRestStopsLayer } from './map/layers/restStopsLayer.js'; import { loadEventsLayers } from './map/layers/eventsLayer.js'; import { loadWeatherLayers } from './map/layers/weatherLayer.js'; @@ -68,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'; @@ -79,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 { @@ -103,8 +108,9 @@ export default function MapWrapper({ camTimeStamp, // Cameras events, eventTimeStamp, // Events + advisories, // CMS ferries, - ferriesTimeStamp, // CMS + ferriesTimeStamp, // Ferries weather, weatherTimeStamp, // Current Weather regional, @@ -125,6 +131,8 @@ export default function MapWrapper({ events: state.feeds.events.list, eventTimeStamp: state.feeds.events.routeTimeStamp, // CMS + advisories: state.cms.advisories.list, + // Ferries ferries: state.feeds.ferries.list, ferriesTimeStamp: state.feeds.ferries.routeTimeStamp, // Current Weather @@ -207,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; @@ -631,6 +641,7 @@ export default function MapWrapper({ } }, [searchLocationFrom]); + // Route layer useEffect(() => { if (isInitialMountRoute.current) { // Do nothing on first load @@ -646,6 +657,7 @@ export default function MapWrapper({ loadData(false); }, [selectedRoute]); + // Camera layer useEffect(() => { // Remove layer if it already exists if (mapLayers.current['highwayCams']) { @@ -687,6 +699,7 @@ export default function MapWrapper({ } }; + // Event layers useEffect(() => { loadEventsLayers(events, mapContext, mapLayers, mapRef); }, [events]); @@ -706,6 +719,7 @@ export default function MapWrapper({ } }; + // Ferries and rest stops layers useEffect(() => { // Remove layer if it already exists if (mapLayers.current['inlandFerries']) { @@ -767,6 +781,7 @@ export default function MapWrapper({ } }; + // Weather layers useEffect(() => { if (mapLayers.current['weather']) { mapRef.current.removeLayer(mapLayers.current['weather']); @@ -797,6 +812,75 @@ 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']) { + mapRef.current.removeLayer(mapLayers.current['advisoriesLayer']); + } + + // Add layer if array exists + if (advisories) { + // Generate and add layer + mapLayers.current['advisoriesLayer'] = getAdvisoriesLayer( + advisories, + mapRef.current.getView().getProjection().getCode(), + mapContext, + ); + + mapRef.current.addLayer(mapLayers.current['advisoriesLayer']); + mapLayers.current['advisoriesLayer'].setZIndex(55); + + if (mapRef.current) { + mapRef.current.on('moveend', onMoveEnd); + } + } + }, [advisories]); + useEffect(() => { if (mapLayers.current['regional']) { mapRef.current.removeLayer(mapLayers.current['regional']); @@ -844,6 +928,7 @@ export default function MapWrapper({ loadWeather(); loadRegional(); loadRestStops(); + loadAdvisories(); // Zoom/pan to route on route updates if (!isInitialMount) { @@ -857,6 +942,7 @@ export default function MapWrapper({ loadWeather(); loadRegional(); loadRestStops(); + loadAdvisories(); } }; @@ -1105,7 +1191,7 @@ export default function MapWrapper({ {!isPreview && (