266 lines
7.3 KiB
JavaScript
266 lines
7.3 KiB
JavaScript
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 { ValidateLocationData, 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(async (result) => {
|
|
let resultArray = Object.entries(result)
|
|
const points = []
|
|
|
|
// validate each location
|
|
for (let vinLocations of resultArray) {
|
|
if (vinLocations[0]) {
|
|
let path = [];
|
|
path[0] = vinLocations[0];
|
|
path[1] = [];
|
|
if (vinLocations[1] && vinLocations[1][0]) {
|
|
for (let location of vinLocations[1]) {
|
|
if (ValidateLocationVehiclePathsData(location) !== false) {
|
|
path[1].push(location);
|
|
}
|
|
}
|
|
} else {
|
|
await getState(token, vinLocations[0]).then((stateResult) => {
|
|
if (stateResult.data && stateResult.data.location) {
|
|
if (ValidateLocationData(stateResult.data.location) !== false) {
|
|
path[1].push([stateResult.data.location.latitude, stateResult.data.location.longitude]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
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;
|