CEC-3933 - use VehiclePaths for location drawing (#306)
* CEC-3933 - Parse VehiclePaths location data * changes * fixes * stuff * sort of works * fix * progress * refactor * fix vehicle paths query * digital twin shows map * new dashboard * wider digital twin map * snapshot * latest; using polylines * lag lng changes * stuff * path showing up * stuff * things * revert home page * whitespace * validation * more stuff * fix button issue * tests pass without mocking data * fix code smells * remove map from digital twin, add to tab * fix bug * marker click event working * individual colors * possible fix * fix warning * merge and remove unused code * small fixes * re add dashboard * snaps
This commit is contained in:
253
src/components/VehiclePathsMap/index.jsx
Normal file
253
src/components/VehiclePathsMap/index.jsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import { Button } from "@material-ui/core";
|
||||
import L from "leaflet";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from "react-leaflet";
|
||||
import useStyles from "../useStyles";
|
||||
|
||||
import GrayMarkerIcon from "../../assets/gray-marker.png";
|
||||
import GreenMarkerIcon from "../../assets/green-marker.png";
|
||||
import { logger } from "../../services/monitoring";
|
||||
import { ValidateLocationVehiclePathsData } from "../../utils/locations";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
|
||||
import { VehiclePopUp } from "../VehicleMap/popup";
|
||||
|
||||
const ComponentVehiclePathsMap = (props) => {
|
||||
const classes = useStyles();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
},
|
||||
} = useUserContext();
|
||||
const { getConnections, getLocationsVehiclePaths, getState } = useVehicleContext();
|
||||
|
||||
const REQUEST_INTERVAL = 10000;
|
||||
|
||||
const [center, setCenter] = useState([0, 0]);
|
||||
const [zoom, setZoom] = useState(2);
|
||||
const [markers, setMarkers] = useState([]);
|
||||
const [connections, setConnections] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
retrieveAndStoreLocations(token).then((points) => {
|
||||
centerAroundMarkers(points);
|
||||
});
|
||||
const id = setInterval(function () {
|
||||
retrieveAndStoreLocations(token);
|
||||
}, REQUEST_INTERVAL);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [token]);
|
||||
|
||||
const retrieveAndStoreLocations = (accessToken) => {
|
||||
let vinsToShowOnMap = [...props.vinsToShowOnMapColors.keys()];
|
||||
let vinsParam = ""
|
||||
for (let vinToShowOnMap of vinsToShowOnMap) {
|
||||
vinsParam += "vins="
|
||||
vinsParam += vinToShowOnMap
|
||||
vinsParam += "&"
|
||||
}
|
||||
vinsParam += "lookback_hours="
|
||||
vinsParam += props.lookbackHours
|
||||
|
||||
return getLocationsVehiclePaths(accessToken, vinsParam)
|
||||
.then((result) => {
|
||||
let resultArray = Object.entries(result)
|
||||
const points = []
|
||||
|
||||
// validate each location
|
||||
for (let vinLocations of resultArray) {
|
||||
let path = []
|
||||
path[0] = vinLocations[0]
|
||||
path[1] = []
|
||||
for (let location of vinLocations[1]) {
|
||||
if (ValidateLocationVehiclePathsData(location) !== false) {
|
||||
path[1].push(location);
|
||||
}
|
||||
}
|
||||
points.push(path)
|
||||
}
|
||||
|
||||
setMarkers(points);
|
||||
return points;
|
||||
})
|
||||
.catch((error) => logger.warn(error.stack));
|
||||
};
|
||||
|
||||
const centerAroundMarkers = (points) => {
|
||||
// default center
|
||||
let center = [37.0902, -95.7129]
|
||||
|
||||
// center is the very first geographical point
|
||||
if (points && points[0] && points[0][1] && points[0][1][0]) {
|
||||
center = points[0][1][0]
|
||||
}
|
||||
|
||||
setCenter(center);
|
||||
setZoom(4.5);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
|
||||
const vins = []
|
||||
|
||||
for (let vinLocations of markers) {
|
||||
vins.push(vinLocations[0])
|
||||
}
|
||||
|
||||
if (vins.length === 0) return;
|
||||
|
||||
getConnections(vins, token).then((conns) => {
|
||||
setConnections(conns);
|
||||
});
|
||||
// eslint-disable-next-line
|
||||
}, [markers, token]);
|
||||
|
||||
const [selectedVIN, setSelectedVIN] = useState(null);
|
||||
const [carState, setCarState] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedVIN != null) {
|
||||
retrieveAndStoreCarState(selectedVIN);
|
||||
const id = setInterval(function () {
|
||||
retrieveAndStoreCarState(selectedVIN);
|
||||
}, REQUEST_INTERVAL);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [selectedVIN]);
|
||||
|
||||
const selectCar = (e, vin) => {
|
||||
e.preventDefault();
|
||||
setSelectedVIN(vin);
|
||||
};
|
||||
|
||||
const retrieveAndStoreCarState = (vin) => {
|
||||
getState(token, vin).then((results) => {
|
||||
setCarState({ ...results.data, vin: vin });
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setSelectedVIN(null);
|
||||
setCarState(null);
|
||||
};
|
||||
|
||||
const isOnline = (vin) => {
|
||||
return connections[vin];
|
||||
};
|
||||
|
||||
const getZIndex = (vin) => {
|
||||
if (isOnline(vin)) return 1000;
|
||||
return 0;
|
||||
};
|
||||
|
||||
function getCarIcon(vin) {
|
||||
let icon = GrayMarkerIcon;
|
||||
|
||||
if (isOnline(vin)) {
|
||||
icon = GreenMarkerIcon;
|
||||
}
|
||||
|
||||
return new L.Icon({
|
||||
iconUrl: icon,
|
||||
iconAnchor: [24, 42],
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<MapContainer
|
||||
center={center}
|
||||
zoom={zoom}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "900px",
|
||||
}}
|
||||
>
|
||||
<TileLayer
|
||||
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<CenterFocus center={center} zoom={zoom} />
|
||||
{markers && markers.map((vinLocations) => (
|
||||
<div key={vinLocations[0]}>
|
||||
<Polyline
|
||||
key={'line' + vinLocations[0]}
|
||||
positions={vinLocations[1]}
|
||||
pathOptions={{
|
||||
color: props.vinsToShowOnMapColors && props.vinsToShowOnMapColors.get(vinLocations[0]),
|
||||
}}
|
||||
/>
|
||||
{vinLocations[1][0] &&
|
||||
<Marker
|
||||
icon={getCarIcon(vinLocations[0])}
|
||||
key={'marker' + vinLocations[0]}
|
||||
position={vinLocations[1][0]}
|
||||
title={vinLocations[0]}
|
||||
opacity={0.9}
|
||||
zIndexOffset={getZIndex(vinLocations[0])}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
setCenter(vinLocations[1][0]);
|
||||
setZoom(16);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Popup>
|
||||
<div align="center">
|
||||
<p className={classes.markerTitle}>
|
||||
<b>{vinLocations[0]}</b>
|
||||
</p>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={(e) => selectCar(e, vinLocations[0])}
|
||||
>
|
||||
View Stats
|
||||
</Button>
|
||||
</div>
|
||||
</Popup>
|
||||
</Marker>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{carState ? (
|
||||
<VehiclePopUp
|
||||
{...carState}
|
||||
className={classes.popup}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
) : null}
|
||||
</MapContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const CenterFocus = ({ center, zoom }) => {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (center[0] === 0 && center[1] === 0) {
|
||||
map.flyTo([0, 0], 2, { duration: 1.5 });
|
||||
} else {
|
||||
map.flyTo(center, zoom, { duration: 1.5 });
|
||||
}
|
||||
}, [center, zoom, map]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const VehiclePathsMap = (props) => (
|
||||
<VehicleProvider>
|
||||
<ComponentVehiclePathsMap vinsToShowOnMapColors={props.vinsToShowOnMapColors} lookbackHours={props.lookbackHours} />
|
||||
</VehicleProvider>
|
||||
);
|
||||
|
||||
export default VehiclePathsMap;
|
||||
Reference in New Issue
Block a user