Skip to content

Commit

Permalink
DBC22-1936: filter advisories on intersect with route cams or events
Browse files Browse the repository at this point in the history
  • Loading branch information
ray-oxd committed Apr 2, 2024
1 parent e4f328d commit 1d9fb42
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 54 deletions.
33 changes: 3 additions & 30 deletions src/frontend/src/Components/advisories/Advisories.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// React
import React, { useCallback, useEffect, useRef } from 'react';
import React from 'react';

// Redux
import { useSelector, useDispatch } from 'react-redux'
import { memoize } from 'proxy-memoize'
import { updateAdvisories } from '../../slices/cmsSlice';

// External Components
Expand All @@ -13,38 +11,13 @@ import {
} from "@fortawesome/free-solid-svg-icons";

// Components and functions
import { getAdvisories } from '../data/advisories.js';
import AdvisoriesList from './AdvisoriesList';

// Styling
import './Advisories.scss';

export default function Advisories() {
// 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()
}));
}
}

useEffect(() => {
if (isInitialMount.current) { // Only run on initial load
loadAdvisories();
isInitialMount.current = false;
}
});
export default function Advisories(props) {
const { advisories } = props;

return (advisories && !!advisories.length) ? (
<div className="advisories">
Expand Down
52 changes: 48 additions & 4 deletions src/frontend/src/pages/CamerasListPage.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
// 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 { updateCameras } from '../slices/feedsSlice';

// Third party components
import { AsyncTypeahead } from 'react-bootstrap-typeahead';
import { booleanIntersects, point, polygon } from '@turf/turf';
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';

// Components and functions
import { getAdvisories } from '../Components/data/advisories';
import { collator, getCameras, addCameraGroups } from '../Components/data/webcams';
import { updateCameras } from '../slices/feedsSlice';
import Advisories from '../Components/advisories/Advisories';
import CameraList from '../Components/cameras/CameraList';
import Footer from '../Footer.js';
Expand All @@ -25,7 +30,8 @@ export default function CamerasListPage() {

// Redux
const dispatch = useDispatch();
const { cameras, camTimeStamp, selectedRoute } = useSelector(useCallback(memoize(state => ({
const { advisories, cameras, camTimeStamp, selectedRoute } = useSelector(useCallback(memoize(state => ({
advisories: state.cms.advisories.list,
cameras: state.feeds.cameras.list,
camTimeStamp: state.feeds.cameras.routeTimeStamp,
selectedRoute: state.routes.selectedRoute
Expand All @@ -35,11 +41,12 @@ export default function CamerasListPage() {
const isInitialMount = useRef(true);

// UseState hooks
const [advisoriesInRoute, setAdvisoriesInRoute] = useState([]);
const [displayedCameras, setDisplayedCameras] = useState(null);
const [processedCameras, setProcessedCameras] = useState(null);
const [searchText, setSearchText] = useState('');

// UseEffect hooks and data functions
// Data functions
const getCamerasData = async () => {
const newRouteTimestamp = selectedRoute ? selectedRoute.searchTimestamp : null;

Expand Down Expand Up @@ -69,8 +76,44 @@ export default function CamerasListPage() {
});

setProcessedCameras(finalCameras);
getAdvisoriesData(finalCameras);
};

const getAdvisoriesData = async (camsData) => {
let advData = advisories;

if (!advisories) {
advData = await getAdvisories();

dispatch(updateAdvisories({
list: advData,
timeStamp: new Date().getTime()
}));
}

// load all advisories if no route selected
const resAdvisories = !selectedRoute ? advData : [];

// Route selected, load advisories that intersect with at least one cam on route
if (selectedRoute && advData && advData.length > 0 && camsData && camsData.length > 0) {
for (const adv of advData) {
const advPoly = polygon(adv.geometry.coordinates);

for (const cam of camsData) {
const camPoint = point(cam.location.coordinates);
if (booleanIntersects(advPoly, camPoint)) {
// advisory intersects with a camera, add to list and break loop
resAdvisories.push(adv);
break;
}
}
}
}

setAdvisoriesInRoute(resAdvisories);
};

// useEffect hooks
useEffect(() => {
getCamerasData();

Expand Down Expand Up @@ -114,6 +157,7 @@ export default function CamerasListPage() {
}
}, [displayedCameras]);

// Rendering
return (
<div className="cameras-page">
<PageHeader
Expand All @@ -122,7 +166,7 @@ export default function CamerasListPage() {
</PageHeader>

<Container className="outer-container">
<Advisories />
<Advisories advisories={advisoriesInRoute} />

<div className="controls-container">
<div className="route-display-container">
Expand Down
81 changes: 61 additions & 20 deletions src/frontend/src/pages/EventsListPage.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// React
import React, { useCallback, useContext, useEffect, useState, useRef } from 'react';
import { memoize } from 'proxy-memoize';
import { updateEvents } from '../slices/feedsSlice';
import { useNavigate } from 'react-router-dom';

// Redux
import { useSelector, useDispatch } from 'react-redux';
import { memoize } from 'proxy-memoize';
import { updateAdvisories } from '../slices/cmsSlice';
import { updateEvents } from '../slices/feedsSlice';

// External imports
import { booleanIntersects, point, lineString, polygon } from '@turf/turf';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faAngleDown,
Expand All @@ -19,10 +23,10 @@ import DropdownButton from 'react-bootstrap/DropdownButton';
import InfiniteScroll from 'react-infinite-scroll-component';

// Internal imports
import { getAdvisories } from '../Components/data/advisories';
import { getEvents } from '../Components/data/events';
import { MapContext } from '../App.js';
import { defaultSortFn, routeSortFn, severitySortFn } from '../Components/events/functions';

import Advisories from '../Components/advisories/Advisories';
import EventCard from '../Components/events/EventCard';
import EventsTable from '../Components/events/EventsTable';
Expand Down Expand Up @@ -67,25 +71,13 @@ export default function EventsListPage() {

// Redux
const dispatch = useDispatch();
const { events, eventTimeStamp, selectedRoute } = useSelector(useCallback(memoize(state => ({
const { advisories, events, eventTimeStamp, selectedRoute } = useSelector(useCallback(memoize(state => ({
advisories: state.cms.advisories.list,
events: state.feeds.events.list,
eventTimeStamp: state.feeds.events.routeTimeStamp,
selectedRoute: state.routes.selectedRoute
}))));

const loadEvents = async (route) => {
const newRouteTimestamp = route ? route.searchTimestamp : null;

// Fetch data if it doesn't already exist or route was updated
if (!events || (eventTimeStamp != newRouteTimestamp)) {
dispatch(updateEvents({
list: await getEvents(route ? route.points : null),
routeTimeStamp: newRouteTimestamp,
timeStamp: new Date().getTime()
}));
}
}

// Context
const { mapContext, setMapContext } = useContext(MapContext);

Expand All @@ -98,14 +90,62 @@ export default function EventsListPage() {
'futureEvents': mapContext.visible_layers.futureEvents,
'roadConditions': false,
});

const [processedEvents, setProcessedEvents] = useState([]); // Nulls for mapping loader
const [showLoader, setShowLoader] = useState(false);
const [advisoriesInRoute, setAdvisoriesInRoute] = useState([]);

// Refs
const isInitialMount = useRef(true);

// Data loading
// Data functions
const getAdvisoriesData = async (eventsData) => {
let advData = advisories;

if (!advisories) {
advData = await getAdvisories();

dispatch(updateAdvisories({
list: advData,
timeStamp: new Date().getTime()
}));
}

// load all advisories if no route selected
const resAdvisories = !selectedRoute ? advData : [];

// Route selected, load advisories that intersect with at least one event on route
if (selectedRoute && advData && advData.length > 0 && eventsData && eventsData.length > 0) {
for (const adv of advData) {
const advPoly = polygon(adv.geometry.coordinates);

for (const event of eventsData) {
// Event geometry, point or line based on type
const eventGeom = event.location.type == 'Point' ? point(event.location.coordinates) : lineString(event.location.coordinates);
if (booleanIntersects(advPoly, eventGeom)) {
// advisory intersects with an event, add to list and break loop
resAdvisories.push(adv);
break;
}
}
}
}

setAdvisoriesInRoute(resAdvisories);
};

const loadEvents = async (route) => {
const newRouteTimestamp = route ? route.searchTimestamp : null;

// Fetch data if it doesn't already exist or route was updated
if (!events || (eventTimeStamp != newRouteTimestamp)) {
dispatch(updateEvents({
list: await getEvents(route ? route.points : null),
routeTimeStamp: newRouteTimestamp,
timeStamp: new Date().getTime()
}));
}
}

const processEvents = () => {
const hasTrue = (val) => !!val;
const hasFilterOn = Object.values(eventCategoryFilter).some(hasTrue);
Expand All @@ -123,6 +163,7 @@ export default function EventsListPage() {
// Sort
sortEvents(res, sortingKey);
setProcessedEvents(res);
getAdvisoriesData(res);
};

// useEffect hooks
Expand Down Expand Up @@ -221,7 +262,7 @@ export default function EventsListPage() {
</PageHeader>

<Container>
<Advisories />
<Advisories advisories={advisoriesInRoute} />

<div className="controls-container">
{ largeScreen &&
Expand Down

0 comments on commit 1d9fb42

Please sign in to comment.