aboutsummaryrefslogtreecommitdiff
path: root/modern/src/map
diff options
context:
space:
mode:
Diffstat (limited to 'modern/src/map')
-rw-r--r--modern/src/map/Map.js37
-rw-r--r--modern/src/map/PositionsMap.js37
-rw-r--r--modern/src/map/SelectedDeviceMap.js4
-rw-r--r--modern/src/map/StatusView.js109
-rw-r--r--modern/src/map/mapStyles.js8
-rw-r--r--modern/src/map/mapUtil.js8
6 files changed, 111 insertions, 92 deletions
diff --git a/modern/src/map/Map.js b/modern/src/map/Map.js
index dcf5a92..8c9bf51 100644
--- a/modern/src/map/Map.js
+++ b/modern/src/map/Map.js
@@ -8,10 +8,9 @@ import { SwitcherControl } from './switcher/switcher';
import deviceCategories from '../common/deviceCategories';
import { prepareIcon, loadImage } from './mapUtil';
import {
- styleCarto, styleLocationIq, styleMapbox, styleMapTiler, styleOsm,
+ styleLocationIq, styleCarto, styleOsm, styleGmapsStreets, styleGmapsSatellite, styleGmapsHybrid
} from './mapStyles';
import { useAttributePreference } from '../common/preferences';
-import palette from '../theme/palette';
import { useTranslation } from '../LocalizationProvider';
const element = document.createElement('div');
@@ -20,6 +19,8 @@ element.style.height = '100%';
export const map = new maplibregl.Map({
container: element,
+ center: [-100.360, 23.191],
+ zoom: 5
});
let ready = false;
@@ -43,18 +44,16 @@ const initMap = async () => {
if (ready) return;
if (!map.hasImage('background')) {
const background = await loadImage('images/background.svg');
- map.addImage('background', await prepareIcon(background), {
+ map.addImage('background', prepareIcon(background), {
pixelRatio: window.devicePixelRatio,
});
await Promise.all(deviceCategories.map(async (category) => {
const results = [];
- ['green', 'red', 'gray'].forEach((color) => {
- results.push(loadImage(`images/icon/${category}.svg`).then((icon) => {
- map.addImage(`${category}-${color}`, prepareIcon(background, icon, palette.common[color]), {
- pixelRatio: window.devicePixelRatio,
- });
- }));
- });
+ results.push(loadImage(`images/icon/${category.toLowerCase()}.png`).then((icon) => {
+ map.addImage(`${category.toLowerCase()}-map`, prepareIcon(background, icon, null), {
+ pixelRatio: window.devicePixelRatio,
+ });
+ }));
await Promise.all(results);
}));
}
@@ -62,7 +61,7 @@ const initMap = async () => {
};
map.addControl(new maplibregl.NavigationControl({
- showCompass: false,
+ showCompass: false
}));
const switcher = new SwitcherControl(
@@ -70,7 +69,7 @@ const switcher = new SwitcherControl(
() => {
const waiting = () => {
if (!map.loaded()) {
- setTimeout(waiting, 100);
+ setTimeout(waiting, 2000);
} else {
initMap();
}
@@ -98,17 +97,13 @@ const Map = ({ children }) => {
useEffect(() => {
switcher.updateStyles([
{ id: 'locationIqStreets', title: t('mapLocationIqStreets'), uri: styleLocationIq('streets', locationIqKey) },
- { id: 'locationIqEarth', title: t('mapLocationIqEarth'), uri: styleLocationIq('earth', locationIqKey) },
- { id: 'locationIqHybrid', title: t('mapLocationIqHybrid'), uri: styleLocationIq('hybrid', locationIqKey) },
{ id: 'osm', title: t('mapOsm'), uri: styleOsm() },
{ id: 'carto', title: t('mapCarto'), uri: styleCarto() },
- { id: 'mapboxStreets', title: t('mapMapboxStreets'), uri: styleMapbox('streets-v11') },
- { id: 'mapboxOutdoors', title: t('mapMapboxOutdoors'), uri: styleMapbox('outdoors-v11') },
- { id: 'mapboxSatellite', title: t('mapMapboxSatellite'), uri: styleMapbox('satellite-v9') },
- { id: 'mapTilerBasic', title: t('mapMapTilerBasic'), uri: styleMapTiler('basic', mapTilerKey) },
- { id: 'mapTilerHybrid', title: t('mapMapTilerHybrid'), uri: styleMapTiler('hybrid', mapTilerKey) },
- ], 'locationIqStreets');
- }, [mapTilerKey]);
+ { id: 'gmapsStreets', title: t('mapGmapsStreets'), uri: styleGmapsStreets() },
+ { id: 'gmapsSatellite', title: t('mapGmapsSatellite'), uri: styleGmapsSatellite() },
+ { id: 'gmapsHybrid', title: t('mapGmapsHybrid'), uri: styleGmapsHybrid() },
+ ], 'gmapsStreets');
+ }, [locationIqKey]);
useEffect(() => {
const listener = (ready) => setMapReady(ready);
diff --git a/modern/src/map/PositionsMap.js b/modern/src/map/PositionsMap.js
index 8d10053..b702400 100644
--- a/modern/src/map/PositionsMap.js
+++ b/modern/src/map/PositionsMap.js
@@ -18,19 +18,24 @@ const PositionsMap = ({ positions }) => {
const devices = useSelector((state) => state.devices.items);
const deviceColor = (device) => {
- switch (device.status) {
- case 'online':
+ const position = positions[device.id];
+ if (position) {
+ if (position.attributes.ignition) {
return 'green';
- case 'offline':
- return 'red';
- default:
+ } else if (position.attributes.ignition === undefined) {
return 'gray';
+ } else {
+ return 'red';
+ }
+ } else {
+ return 'gray';
}
};
const createFeature = (devices, position) => {
const device = devices[position.deviceId];
return {
+ position: position,
deviceId: position.deviceId,
name: device.name,
category: device.category || 'default',
@@ -48,15 +53,20 @@ const PositionsMap = ({ positions }) => {
coordinates[0] += event.lngLat.lng > coordinates[0] ? 360 : -360;
}
+ console.log(event);
+
+ const position = JSON.parse(feature.properties.position);
const placeholder = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<ThemeProvider theme={theme}>
- <StatusView
- deviceId={feature.properties.deviceId}
- onShowDetails={(positionId) => history.push(`/position/${positionId}`)}
- onShowHistory={() => history.push('/replay')}
- onEditClick={(deviceId) => history.push(`/device/${deviceId}`)}
+ <StatusView
+ position={position}
+ deviceId={feature.properties.deviceId}
+ onShowDetails={(positionId) => history.push(`/position/${positionId}`)}
+ onShowHistory={(deviceId) => history.push(`/replay/${deviceId}`)}
+ onEditClick={(deviceId) => history.push(`/device/${deviceId}`)}
+ onCommandsClick={(deviceId) => history.push(`/device/${deviceId}/commands`) }
/>
</ThemeProvider>
</Provider>,
@@ -104,18 +114,19 @@ const PositionsMap = ({ positions }) => {
source: id,
filter: ['!', ['has', 'point_count']],
layout: {
- 'icon-image': '{category}-{color}',
+ 'icon-image': ['concat', ['downcase', ['get', 'category']], '-map'],
'icon-allow-overlap': true,
'text-field': '{name}',
'text-allow-overlap': true,
'text-anchor': 'bottom',
'text-offset': [0, -2],
- 'text-font': ['Roboto Regular'],
+ 'text-font': ['Roboto Bold'],
'text-size': 12,
},
paint: {
+ 'text-color': 'black',
'text-halo-color': 'white',
- 'text-halo-width': 1,
+ 'text-halo-width': 2,
},
});
map.addLayer({
diff --git a/modern/src/map/SelectedDeviceMap.js b/modern/src/map/SelectedDeviceMap.js
index 6384717..d05394b 100644
--- a/modern/src/map/SelectedDeviceMap.js
+++ b/modern/src/map/SelectedDeviceMap.js
@@ -16,10 +16,10 @@ const SelectedDeviceMap = () => {
useEffect(() => {
if (mapCenter) {
- map.easeTo({ center: mapCenter.position });
+ map.easeTo({ center: mapCenter.position, zoom:18 });
}
}, [mapCenter]);
-
+
return null;
};
diff --git a/modern/src/map/StatusView.js b/modern/src/map/StatusView.js
index 5526e14..b892e77 100644
--- a/modern/src/map/StatusView.js
+++ b/modern/src/map/StatusView.js
@@ -4,17 +4,18 @@ import {
} from '@material-ui/core';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
-import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import ListItemText from '@material-ui/core/ListItemText';
import { useSelector } from 'react-redux';
-import ReplayIcon from '@material-ui/icons/Replay';
-import ExitToAppIcon from '@material-ui/icons/ExitToApp';
import EditIcon from '@material-ui/icons/Edit';
+import SendIcon from '@material-ui/icons/Send';
import DeleteIcon from '@material-ui/icons/Delete';
+import LinkIcon from '@material-ui/icons/Link';
+import InfoIcon from '@material-ui/icons/Info';
+import PlayCircleFilledIcon from '@material-ui/icons/PlayCircleFilled';
import {
- formatPosition, getStatusColor, getBatteryStatus, formatDistance, formatSpeed,
+ formatSpeed, formatHours, formatPosition
} from '../common/formatter';
import { useAttributePreference } from '../common/preferences';
import RemoveDialog from '../RemoveDialog';
@@ -27,19 +28,23 @@ const useStyles = makeStyles((theme) => ({
},
...theme.palette.colors,
listItemContainer: {
- maxWidth: '240px',
+ maxWidth: '300px',
},
+ listItemRoot: {
+ paddingTop: '0px',
+ paddingBottom: '0px',
+ }
}));
const StatusView = ({
- deviceId, onShowDetails, onShowHistory, onEditClick,
+ position, deviceId, onShowDetails, onShowHistory, onEditClick, onCommandsClick,
}) => {
const classes = useStyles();
const t = useTranslation();
const [removeDialogShown, setRemoveDialogShown] = useState(false);
+ const session = useSelector((state) => state.session);
const device = useSelector((state) => state.devices.items[deviceId]);
- const position = useSelector(getPosition(deviceId));
const distanceUnit = useAttributePreference('distanceUnit');
const speedUnit = useAttributePreference('speedUnit');
@@ -54,6 +59,11 @@ const StatusView = ({
onEditClick(deviceId);
};
+ const handleCommandsClick = (e) => {
+ e.preventDefault();
+ onCommandsClick(deviceId);
+ }
+
const handleRemove = () => {
setRemoveDialogShown(true);
};
@@ -62,72 +72,71 @@ const StatusView = ({
setRemoveDialogShown(false);
};
+ const handleGotoLink = () => {
+ const url = `https://maps.google.com/maps?q=${position.latitude},${position.longitude}&z=18`;
+ window.open(url, "_blank");
+ }
+
return (
<>
<Paper className={classes.paper} elevation={0} square>
<Grid container direction="column">
<Grid item>
<List>
- <ListItem classes={{ container: classes.listItemContainer }}>
- <ListItemText primary={t('deviceStatus')} />
- <ListItemSecondaryAction>
- <span className={classes[getStatusColor(device.status)]}>{device.status}</span>
- </ListItemSecondaryAction>
- </ListItem>
- <ListItem classes={{ container: classes.listItemContainer }}>
- <ListItemText primary={t('positionSpeed')} />
- <ListItemSecondaryAction>
- {formatSpeed(position.speed, speedUnit, t)}
- </ListItemSecondaryAction>
+ <ListItem classes={{ container: classes.listItemContainer, root: classes.listItemRoot }} >
+ <ListItemText primary={t('positionDatetime')} secondary={formatPosition(position, 'fixTime', t)} />
</ListItem>
- {position.attributes.batteryLevel && (
- <ListItem classes={{ container: classes.listItemContainer }}>
- <ListItemText primary={t('positionBattery')} />
- <ListItemSecondaryAction>
- <span className={classes[getBatteryStatus(position.attributes.batteryLevel)]}>
- {formatPosition(position.attributes.batteryLevel, 'batteryLevel', t)}
- </span>
- </ListItemSecondaryAction>
- </ListItem>
- )}
- <ListItem classes={{ container: classes.listItemContainer }}>
- <ListItemText primary={t('positionDistance')} />
- <ListItemSecondaryAction>
- {formatDistance(position.attributes.totalDistance, distanceUnit, t)}
- </ListItemSecondaryAction>
- </ListItem>
- <ListItem classes={{ container: classes.listItemContainer }}>
- <ListItemText primary={t('positionCourse')} />
- <ListItemSecondaryAction>
- {formatPosition(position.course, 'course', t)}
- </ListItemSecondaryAction>
+ <ListItem classes={{ container: classes.listItemContainer, root: classes.listItemRoot }}>
+ <ListItemText primary={t('positionSpeed')} secondary={formatSpeed(position.speed, speedUnit, t)} />
</ListItem>
+ {device.category
+ && (device.category.toLowerCase() === 'backhoe' || device.category.toLowerCase() === 'tractor')
+ && position.attributes.hours
+ && (
+ <ListItem classes={{ container: classes.listItemContainer, root: classes.listItemRoot }}>
+ <ListItemText primary={t('positionHours')} secondary={formatHours(position.attributes.hours, t)} />
+ </ListItem>)}
+ {position.address && (
+ <ListItem classes={{ container: classes.listItemContainer, root: classes.listItemRoot }}>
+ <ListItemText primary={t('positionAddress')} secondary={position.address} />
+ </ListItem>)}
</List>
</Grid>
<Grid item container>
<Grid item>
- <Button color="secondary" onClick={handleClick}>More Info</Button>
- </Grid>
- <Grid item>
- <IconButton onClick={onShowHistory}>
- <ReplayIcon />
+ <IconButton onClick={handleClick}>
+ <InfoIcon />
</IconButton>
</Grid>
<Grid item>
- <IconButton>
- <ExitToAppIcon />
+ <IconButton onClick={() => onShowHistory(deviceId)}>
+ <PlayCircleFilledIcon />
</IconButton>
</Grid>
<Grid item>
- <IconButton onClick={handleEditClick}>
- <EditIcon />
+ <IconButton onClick={handleGotoLink}>
+ <LinkIcon />
</IconButton>
</Grid>
<Grid item>
- <IconButton onClick={handleRemove} className={classes.red}>
- <DeleteIcon />
+ <IconButton onClick={handleCommandsClick}>
+ <SendIcon />
</IconButton>
</Grid>
+ {!session.server.deviceReadonly && (
+ <>
+ <Grid item>
+ <IconButton onClick={handleEditClick}>
+ <EditIcon />
+ </IconButton>
+ </Grid>
+ <Grid item>
+ <IconButton onClick={handleRemove} className={classes.red}>
+ <DeleteIcon />
+ </IconButton>
+ </Grid>
+ </>
+ )}
</Grid>
</Grid>
</Paper>
diff --git a/modern/src/map/mapStyles.js b/modern/src/map/mapStyles.js
index 86813a1..a6e84fd 100644
--- a/modern/src/map/mapStyles.js
+++ b/modern/src/map/mapStyles.js
@@ -53,3 +53,11 @@ export const styleMapbox = (style) => `mapbox://styles/mapbox/${style}`;
export const styleMapTiler = (style, key) => `https://api.maptiler.com/maps/${style}/style.json?key=${key}`;
export const styleLocationIq = (style, key) => `https://tiles.locationiq.com/v3/${style}/vector.json?key=${key}`;
+
+// Google Maps
+
+export const styleGmapsStreets = () => styleCustom('https://mt0.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}&s=Ga', '');
+
+export const styleGmapsSatellite = () => styleCustom('https://mt0.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}&s=Ga', '');
+
+export const styleGmapsHybrid = () => styleCustom('https://mt0.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga', '');
diff --git a/modern/src/map/mapUtil.js b/modern/src/map/mapUtil.js
index 2aa86c6..e93c146 100644
--- a/modern/src/map/mapUtil.js
+++ b/modern/src/map/mapUtil.js
@@ -39,14 +39,10 @@ export const prepareIcon = (background, icon, color) => {
context.drawImage(background, 0, 0, canvas.width, canvas.height);
if (icon) {
- const iconRatio = 0.5;
+ const iconRatio = 0.7;
const imageWidth = canvas.width * iconRatio;
const imageHeight = canvas.height * iconRatio;
- if (navigator.userAgent.indexOf('Firefox') > 0) {
- context.drawImage(icon, (canvas.width - imageWidth) / 2, (canvas.height - imageHeight) / 2, imageWidth, imageHeight);
- } else {
- context.drawImage(canvasTintImage(icon, color), (canvas.width - imageWidth) / 2, (canvas.height - imageHeight) / 2, imageWidth, imageHeight);
- }
+ context.drawImage(icon, (canvas.width - imageWidth) / 2, (canvas.height - imageHeight) / 2, imageWidth, imageHeight);
}
return context.getImageData(0, 0, canvas.width, canvas.height);