diff options
Diffstat (limited to 'modern/src/map')
-rw-r--r-- | modern/src/map/Map.js | 37 | ||||
-rw-r--r-- | modern/src/map/PositionsMap.js | 37 | ||||
-rw-r--r-- | modern/src/map/SelectedDeviceMap.js | 4 | ||||
-rw-r--r-- | modern/src/map/StatusView.js | 109 | ||||
-rw-r--r-- | modern/src/map/mapStyles.js | 8 | ||||
-rw-r--r-- | modern/src/map/mapUtil.js | 8 |
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); |