From da578b6ac7ba43b8d437aebbfe36777278370e5e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Mon, 3 Sep 2018 22:51:20 +1200 Subject: First hacky implementation --- modern/src/MainMap.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 modern/src/MainMap.js (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js new file mode 100644 index 0000000..27c58bc --- /dev/null +++ b/modern/src/MainMap.js @@ -0,0 +1,29 @@ +import React, { Component } from 'react'; +import { Map, TileLayer, Marker, Popup } from 'react-leaflet'; + +class MainMap extends Component { + state = { + lat: 51.505, + lng: -0.09, + zoom: 13, + } + + render() { + const position = [this.state.lat, this.state.lng] + return ( + + + + + A pretty CSS3 popup.
Easily customizable. +
+
+
+ ) + } +} + +export default MainMap; -- cgit v1.2.3 From 411376bc7774b79076cf179c559865fb56801e79 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 5 Sep 2018 15:36:52 +1200 Subject: Change map server --- modern/src/MainMap.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 27c58bc..3388d67 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -13,9 +13,8 @@ class MainMap extends Component { return ( + attribution='© OpenStreetMap contributors, © CARTO' + url="https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png" /> A pretty CSS3 popup.
Easily customizable. -- cgit v1.2.3 From 72aa50650203d6db0b17de59bfd98c9d5b06e3e7 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 7 Sep 2018 16:09:01 +1200 Subject: Add redux and load positions --- modern/package.json | 4 +++- modern/src/MainMap.js | 28 +++++++++++++++++++--------- modern/src/SocketContoller.js | 7 ++++--- modern/src/actions/index.js | 14 ++++++++++++++ modern/src/index.js | 13 ++++++++++--- modern/src/reducers/index.js | 18 ++++++++++++++++++ 6 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 modern/src/actions/index.js create mode 100644 modern/src/reducers/index.js (limited to 'modern/src/MainMap.js') diff --git a/modern/package.json b/modern/package.json index 0c1a1cb..457650c 100644 --- a/modern/package.json +++ b/modern/package.json @@ -10,8 +10,10 @@ "react-container-dimensions": "^1.4.1", "react-dom": "^16.4.2", "react-leaflet": "^2.0.1", + "react-redux": "^5.0.7", "react-router-dom": "^4.3.1", - "react-scripts": "^1.1.5" + "react-scripts": "^1.1.5", + "redux": "^4.0.0" }, "scripts": { "start": "react-scripts start", diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 3388d67..0e93ddb 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -1,28 +1,38 @@ import React, { Component } from 'react'; import { Map, TileLayer, Marker, Popup } from 'react-leaflet'; +import { connect } from 'react-redux' + +const mapStateToProps = state => ({ + positions: state.positions +}); class MainMap extends Component { state = { - lat: 51.505, - lng: -0.09, - zoom: 13, + lat: 0, + lng: 0, + zoom: 3, } render() { const position = [this.state.lat, this.state.lng] + + const markers = this.props.positions.map((position) => + + + A pretty CSS3 popup.
Easily customizable. +
+
+ ); + return ( - - - A pretty CSS3 popup.
Easily customizable. -
-
+ {markers}
) } } -export default MainMap; +export default connect(mapStateToProps)(MainMap); diff --git a/modern/src/SocketContoller.js b/modern/src/SocketContoller.js index c07fcff..fe686ed 100644 --- a/modern/src/SocketContoller.js +++ b/modern/src/SocketContoller.js @@ -1,4 +1,6 @@ import { Component } from 'react'; +import { connect } from 'react-redux'; +import { updatePositions } from './actions'; class SocketController extends Component { connectSocket() { @@ -12,8 +14,7 @@ class SocketController extends Component { socket.onmessage = (event) => { const data = JSON.parse(event.data); if (data.positions) { - // TODO update positions - console.log(data.positions); + this.props.dispatch(updatePositions(data.positions)); } } } @@ -27,4 +28,4 @@ class SocketController extends Component { } } -export default SocketController; +export default connect()(SocketController); diff --git a/modern/src/actions/index.js b/modern/src/actions/index.js new file mode 100644 index 0000000..8ef49fa --- /dev/null +++ b/modern/src/actions/index.js @@ -0,0 +1,14 @@ +export const updateDevices = devices => ({ + type: 'UPDATE_DEVICES', + devices +}) + +export const updatePositions = positions => ({ + type: 'UPDATE_POSITIONS', + positions +}); + +export const updateEvents = events => ({ + type: 'UPDATE_EVENTS', + events +}) diff --git a/modern/src/index.js b/modern/src/index.js index 016a4af..66d3b04 100644 --- a/modern/src/index.js +++ b/modern/src/index.js @@ -1,13 +1,20 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom' +import { Provider } from 'react-redux'; +import { createStore } from 'redux' +import rootReducer from './reducers'; import App from './App'; import registerServiceWorker from './registerServiceWorker'; +const store = createStore(rootReducer); + ReactDOM.render(( - - - + + + + + ), document.getElementById('root')); registerServiceWorker(); diff --git a/modern/src/reducers/index.js b/modern/src/reducers/index.js new file mode 100644 index 0000000..b9c9477 --- /dev/null +++ b/modern/src/reducers/index.js @@ -0,0 +1,18 @@ +const initialState = { + devices: [], + positions: [], + events: [] +}; + +function rootReducer(state = initialState, action) { + switch (action.type) { + case 'UPDATE_POSITIONS': + return Object.assign({}, { + positions: [...state.positions, ...action.positions] + }); + default: + return state; + } +} + +export default rootReducer -- cgit v1.2.3 From 1461b376e41cf41cd9e49f6df200ba3f573fa127 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 8 Sep 2018 11:40:01 +1200 Subject: Implement device list --- modern/src/DeviceList.js | 57 +++++++++++++++++++++++++++++++++++++++++++ modern/src/MainMap.js | 2 +- modern/src/MainPage.js | 2 ++ modern/src/SocketContoller.js | 5 +++- modern/src/reducers/index.js | 6 +++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 modern/src/DeviceList.js (limited to 'modern/src/MainMap.js') diff --git a/modern/src/DeviceList.js b/modern/src/DeviceList.js new file mode 100644 index 0000000..602a6d9 --- /dev/null +++ b/modern/src/DeviceList.js @@ -0,0 +1,57 @@ +import React, { Component, Fragment } from 'react'; +import { connect } from 'react-redux'; +import { updateDevices } from './actions'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import Avatar from '@material-ui/core/Avatar'; +import LocationOnIcon from '@material-ui/icons/LocationOn'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import IconButton from '@material-ui/core/IconButton'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import Divider from '@material-ui/core/Divider'; + +const mapStateToProps = state => ({ + devices: state.devices +}); + +class DeviceList extends Component { + componentDidMount() { + fetch('/api/devices').then(response => { + if (response.ok) { + response.json().then(devices => { + this.props.dispatch(updateDevices(devices)); + }); + } + }); + } + + render() { + const devices = this.props.devices.map(device => + + + + + + + + + + + + +
  • + +
  • +
    + ); + + return ( + + {devices} + + ); + } +} + +export default connect(mapStateToProps)(DeviceList); diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 0e93ddb..7655aee 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -16,7 +16,7 @@ class MainMap extends Component { render() { const position = [this.state.lat, this.state.lng] - const markers = this.props.positions.map((position) => + const markers = this.props.positions.map(position => A pretty CSS3 popup.
    Easily customizable. diff --git a/modern/src/MainPage.js b/modern/src/MainPage.js index ce4c577..c64665b 100644 --- a/modern/src/MainPage.js +++ b/modern/src/MainPage.js @@ -6,6 +6,7 @@ import Drawer from '@material-ui/core/Drawer'; import withStyles from '@material-ui/core/styles/withStyles'; import SocketController from './SocketContoller'; import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; +import DeviceList from './DeviceList'; const styles = theme => ({ root: { @@ -73,6 +74,7 @@ class MainPage extends Component { anchor={isWidthUp('sm', this.props.width) ? "left" : "bottom"} variant="permanent" classes={{ paper: classes.drawerPaper }}> +
    diff --git a/modern/src/SocketContoller.js b/modern/src/SocketContoller.js index 3cf7feb..f65fc6c 100644 --- a/modern/src/SocketContoller.js +++ b/modern/src/SocketContoller.js @@ -1,6 +1,6 @@ import { Component } from 'react'; import { connect } from 'react-redux'; -import { updatePositions } from './actions'; +import { updateDevices, updatePositions } from './actions'; const displayNotifications = events => { if ("Notification" in window) { @@ -30,6 +30,9 @@ class SocketController extends Component { socket.onmessage = (event) => { const data = JSON.parse(event.data); + if (data.devices) { + this.props.dispatch(updateDevices(data.devices)); + } if (data.positions) { this.props.dispatch(updatePositions(data.positions)); } diff --git a/modern/src/reducers/index.js b/modern/src/reducers/index.js index b9c9477..962d83c 100644 --- a/modern/src/reducers/index.js +++ b/modern/src/reducers/index.js @@ -6,8 +6,14 @@ const initialState = { function rootReducer(state = initialState, action) { switch (action.type) { + case 'UPDATE_DEVICES': + return Object.assign({}, { + ...state, + devices: [...state.devices, ...action.devices] + }); case 'UPDATE_POSITIONS': return Object.assign({}, { + ...state, positions: [...state.positions, ...action.positions] }); default: -- cgit v1.2.3 From cabb500189f027bc64d6b62bde9cf017efa4c0d4 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 20 Sep 2018 14:57:42 +1200 Subject: Div icon as marker --- modern/src/MainMap.js | 12 +++--- modern/src/leaflet/DivIcon.js | 99 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 modern/src/leaflet/DivIcon.js (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 7655aee..fab1777 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { Map, TileLayer, Marker, Popup } from 'react-leaflet'; import { connect } from 'react-redux' +import DivIcon from './leaflet/DivIcon'; const mapStateToProps = state => ({ positions: state.positions @@ -17,11 +18,12 @@ class MainMap extends Component { const position = [this.state.lat, this.state.lng] const markers = this.props.positions.map(position => - - - A pretty CSS3 popup.
    Easily customizable. -
    -
    + + + TEST + + ); return ( diff --git a/modern/src/leaflet/DivIcon.js b/modern/src/leaflet/DivIcon.js new file mode 100644 index 0000000..6673c56 --- /dev/null +++ b/modern/src/leaflet/DivIcon.js @@ -0,0 +1,99 @@ +import React, {Component} from 'react'; +import {render} from 'react-dom'; +import {DivIcon, marker} from 'leaflet'; +import {MapLayer, withLeaflet} from 'react-leaflet'; +import PropTypes from 'prop-types'; + +function createContextProvider(context) { + class ContextProvider extends Component { + getChildContext() { + return context; + } + + render() { + return this.props.children; + } + } + + ContextProvider.childContextTypes = {}; + Object.keys(context).forEach(key => { + ContextProvider.childContextTypes[key] = PropTypes.any; + }); + return ContextProvider; +} + +export class Divicon extends MapLayer { + static propTypes = { + opacity: PropTypes.number, + zIndexOffset: PropTypes.number, + } + + static childContextTypes = { + popupContainer: PropTypes.object, + } + + getChildContext() { + return { + popupContainer: this.leafletElement, + } + } + + // See https://github.com/PaulLeCam/react-leaflet/issues/275 + createLeafletElement(newProps) { + const {map: _map, layerContainer: _lc, position, ...props} = newProps; + this.icon = new DivIcon(props); + return marker(position, {icon: this.icon, ...props}); + } + + updateLeafletElement(fromProps, toProps) { + if (toProps.position !== fromProps.position) { + this.leafletElement.setLatLng(toProps.position); + } + if (toProps.zIndexOffset !== fromProps.zIndexOffset) { + this.leafletElement.setZIndexOffset(toProps.zIndexOffset); + } + if (toProps.opacity !== fromProps.opacity) { + this.leafletElement.setOpacity(toProps.opacity); + } + if (toProps.draggable !== fromProps.draggable) { + if (toProps.draggable) { + this.leafletElement.dragging.enable(); + } + else { + this.leafletElement.dragging.disable(); + } + } + } + + componentDidMount() { + super.componentDidMount(); + this.renderComponent(); + } + + componentDidUpdate(fromProps) { + this.renderComponent(); + this.updateLeafletElement(fromProps, this.props); + } + + renderComponent = () => { + const ContextProvider = createContextProvider({...this.context, ...this.getChildContext()}); + const container = this.leafletElement._icon; + const component = ( + + {this.props.children} + + ); + if (container) { + render( + component, + container + ); + } + } + + render() { + return null; + } +} + +export default withLeaflet(Divicon) -- cgit v1.2.3 From 396abfe03f85f294d911d8362b7afce5600620c6 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Tue, 2 Oct 2018 23:11:09 +1300 Subject: Update map icons --- modern/public/category/car.svg | 4 ++++ modern/src/MainMap.js | 13 +++++++------ modern/src/reducers/index.js | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 modern/public/category/car.svg (limited to 'modern/src/MainMap.js') diff --git a/modern/public/category/car.svg b/modern/public/category/car.svg new file mode 100644 index 0000000..7dad87d --- /dev/null +++ b/modern/public/category/car.svg @@ -0,0 +1,4 @@ + + + + diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index fab1777..5a096c5 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -18,10 +18,11 @@ class MainMap extends Component { const position = [this.state.lat, this.state.lng] const markers = this.props.positions.map(position => - - - TEST + + + + + ); @@ -29,8 +30,8 @@ class MainMap extends Component { return ( + attribution="© OpenStreetMap contributors" + url="https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png" /> {markers} ) diff --git a/modern/src/reducers/index.js b/modern/src/reducers/index.js index 962d83c..ac592bf 100644 --- a/modern/src/reducers/index.js +++ b/modern/src/reducers/index.js @@ -9,12 +9,12 @@ function rootReducer(state = initialState, action) { case 'UPDATE_DEVICES': return Object.assign({}, { ...state, - devices: [...state.devices, ...action.devices] + devices: [...action.devices] }); case 'UPDATE_POSITIONS': return Object.assign({}, { ...state, - positions: [...state.positions, ...action.positions] + positions: [...action.positions] }); default: return state; -- cgit v1.2.3 From 4176a083468c13c1a8a8a4b0ff4ae5c36eb2951f Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 5 Oct 2018 17:02:04 +1300 Subject: Fix file name --- modern/src/MainMap.js | 2 +- modern/src/MainPage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 5a096c5..0b68d84 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -21,7 +21,7 @@ class MainMap extends Component { - + diff --git a/modern/src/MainPage.js b/modern/src/MainPage.js index c64665b..450a5e0 100644 --- a/modern/src/MainPage.js +++ b/modern/src/MainPage.js @@ -4,7 +4,7 @@ import MainToobar from './MainToolbar'; import MainMap from './MainMap'; import Drawer from '@material-ui/core/Drawer'; import withStyles from '@material-ui/core/styles/withStyles'; -import SocketController from './SocketContoller'; +import SocketController from './SocketController'; import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; import DeviceList from './DeviceList'; -- cgit v1.2.3 From 3d05383151ad7ce44c6297fc46bf5bf8457db7c6 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 18 Mar 2020 23:03:31 -0700 Subject: Replace map with OpenLayers --- modern/.vscode/launch.json | 12 ++++++------ modern/package.json | 2 ++ modern/src/MainMap.js | 39 ++++++++++++--------------------------- 3 files changed, 20 insertions(+), 33 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/.vscode/launch.json b/modern/.vscode/launch.json index 9c243a0..8bd51ee 100644 --- a/modern/.vscode/launch.json +++ b/modern/.vscode/launch.json @@ -1,15 +1,15 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { + "name": "Chrome", "type": "chrome", "request": "launch", - "name": "Launch Chrome against localhost", "url": "http://localhost:3000", - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}/src", + "sourceMapPathOverrides": { + "webpack:///src/*": "${webRoot}/*" + } } ] -} \ No newline at end of file +} diff --git a/modern/package.json b/modern/package.json index a19f528..79fa97d 100644 --- a/modern/package.json +++ b/modern/package.json @@ -6,6 +6,8 @@ "@material-ui/core": "^4.9.7", "@material-ui/icons": "^4.9.1", "leaflet": "^1.6.0", + "ol": "^6.2.1", + "ol-mapbox-style": "^6.0.1", "react": "^16.13.0", "react-container-dimensions": "^1.4.1", "react-dom": "^16.13.0", diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 0b68d84..1bda966 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -1,40 +1,25 @@ +import 'ol/ol.css'; import React, { Component } from 'react'; -import { Map, TileLayer, Marker, Popup } from 'react-leaflet'; -import { connect } from 'react-redux' -import DivIcon from './leaflet/DivIcon'; +import { connect } from 'react-redux'; +import olms from 'ol-mapbox-style'; const mapStateToProps = state => ({ positions: state.positions }); class MainMap extends Component { - state = { - lat: 0, - lng: 0, - zoom: 3, + componentDidMount() { + olms(this.el, 'https://cdn.traccar.com/map/basic.json'); } render() { - const position = [this.state.lat, this.state.lng] - - const markers = this.props.positions.map(position => - - - - - - - - ); - - return ( - - - {markers} - - ) + const style = { + position: 'relative', + overflow: 'hidden', + width: '100%', + height: '100%' + }; + return
    this.el = el} />; } } -- cgit v1.2.3 From 889bc0667e21d1154ac2749842696aac0812ef8c Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Wed, 18 Mar 2020 23:53:48 -0700 Subject: Change map initialization --- modern/src/MainMap.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 1bda966..8b363c9 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -1,6 +1,8 @@ import 'ol/ol.css'; import React, { Component } from 'react'; import { connect } from 'react-redux'; +import { Map, View } from 'ol'; +import { fromLonLat } from 'ol/proj'; import olms from 'ol-mapbox-style'; const mapStateToProps = state => ({ @@ -9,7 +11,15 @@ const mapStateToProps = state => ({ class MainMap extends Component { componentDidMount() { - olms(this.el, 'https://cdn.traccar.com/map/basic.json'); + this.map = new Map({ + target: this.el, + view: new View({ + constrainResolution: true, + center: fromLonLat([14.5, 46.05]), + zoom: 3 + }) + }); + olms(this.map, 'https://cdn.traccar.com/map/basic.json'); } render() { -- cgit v1.2.3 From efe2c2cc2d59be87048ed12a29736a5d9eaf597f Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 19 Mar 2020 23:19:34 -0700 Subject: Use raster map in production --- modern/src/MainMap.js | 16 +++++++++++++--- modern/src/MainToolbar.js | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 8b363c9..ff51b56 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -4,6 +4,8 @@ import { connect } from 'react-redux'; import { Map, View } from 'ol'; import { fromLonLat } from 'ol/proj'; import olms from 'ol-mapbox-style'; +import TileLayer from 'ol/layer/Tile'; +import OSM from 'ol/source/OSM'; const mapStateToProps = state => ({ positions: state.positions @@ -15,11 +17,19 @@ class MainMap extends Component { target: this.el, view: new View({ constrainResolution: true, - center: fromLonLat([14.5, 46.05]), - zoom: 3 + center: fromLonLat([25.65, 60.98]), + zoom: 9 }) }); - olms(this.map, 'https://cdn.traccar.com/map/basic.json'); + if (location.hostname === 'localhost') { + olms(this.map, 'https://cdn.traccar.com/map/basic.json'); + } else { + this.map.addLayer( + new TileLayer({ + source: new OSM() + }) + ); + } } render() { diff --git a/modern/src/MainToolbar.js b/modern/src/MainToolbar.js index c5af9d8..6e2e4d6 100644 --- a/modern/src/MainToolbar.js +++ b/modern/src/MainToolbar.js @@ -97,7 +97,7 @@ class MainToobar extends Component { - + @@ -106,7 +106,7 @@ class MainToobar extends Component { - + -- cgit v1.2.3 From e8e42979e8dc4fbeb5ced188bfa8a27b07913487 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Thu, 19 Mar 2020 23:22:00 -0700 Subject: Fix warning --- modern/src/MainMap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index ff51b56..00204b2 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -21,7 +21,7 @@ class MainMap extends Component { zoom: 9 }) }); - if (location.hostname === 'localhost') { + if (window.location.hostname === 'localhost') { olms(this.map, 'https://cdn.traccar.com/map/basic.json'); } else { this.map.addLayer( -- cgit v1.2.3 From 5e98816922c284805714e35a678f1a7dfa3facb8 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 22 Mar 2020 12:17:27 -0700 Subject: Use Mapbox map library --- modern/public/category/car.svg | 4 -- modern/public/images/background.svg | 10 ++++ modern/src/MainMap.js | 96 ++++++++++++++++++++++++++++--------- 3 files changed, 83 insertions(+), 27 deletions(-) delete mode 100644 modern/public/category/car.svg create mode 100644 modern/public/images/background.svg (limited to 'modern/src/MainMap.js') diff --git a/modern/public/category/car.svg b/modern/public/category/car.svg deleted file mode 100644 index 7dad87d..0000000 --- a/modern/public/category/car.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/modern/public/images/background.svg b/modern/public/images/background.svg new file mode 100644 index 0000000..df2204d --- /dev/null +++ b/modern/public/images/background.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 00204b2..925b402 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -1,34 +1,84 @@ -import 'ol/ol.css'; +import 'mapbox-gl/src/css/mapbox-gl.css'; import React, { Component } from 'react'; import { connect } from 'react-redux'; -import { Map, View } from 'ol'; -import { fromLonLat } from 'ol/proj'; -import olms from 'ol-mapbox-style'; -import TileLayer from 'ol/layer/Tile'; -import OSM from 'ol/source/OSM'; +import mapboxgl from 'mapbox-gl'; const mapStateToProps = state => ({ - positions: state.positions + data: { + type: 'FeatureCollection', + features: state.positions.map(position => ({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [position.longitude, position.latitude] + }, + properties: { + ...position + } + })) + } }); class MainMap extends Component { componentDidMount() { - this.map = new Map({ - target: this.el, - view: new View({ - constrainResolution: true, - center: fromLonLat([25.65, 60.98]), - zoom: 9 - }) + const map = new mapboxgl.Map({ + container: this.mapContainer, + style: 'https://cdn.traccar.com/map/basic.json', + center: [25.65, 60.98], + zoom: 0 + }); + + map.on('load', () => this.mapDidLoad(map)); + } + + loadImage(key, url) { + const image = new Image(); + image.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = image.width * window.devicePixelRatio; + canvas.height = image.height * window.devicePixelRatio; + canvas.style.width = `${image.width}px`; + canvas.style.height = `${image.height}px`; + const context = canvas.getContext('2d'); + context.imageSmoothingEnabled = false; + context.drawImage(image, 0, 0, canvas.width, canvas.height); + this.map.addImage(key, context.getImageData(0, 0, canvas.width, canvas.height), { + pixelRatio: window.devicePixelRatio + }); + } + image.src = url; + } + + mapDidLoad(map) { + this.map = map; + + this.loadImage('background', 'images/background.svg'); + + map.addSource('positions', { + 'type': 'geojson', + 'data': this.props.data }); - if (window.location.hostname === 'localhost') { - olms(this.map, 'https://cdn.traccar.com/map/basic.json'); - } else { - this.map.addLayer( - new TileLayer({ - source: new OSM() - }) - ); + + map.addLayer({ + 'id': 'device-background', + 'type': 'symbol', + 'source': 'positions', + 'layout': { + 'icon-image': 'background', + 'text-field': 'Test Device Name', + 'text-font': ['Roboto Regular'], + 'text-size': 11 + }, + 'paint':{ + 'text-halo-color': 'white', + 'text-halo-width': 1 + } + }); + } + + componentDidUpdate(prevProps) { + if (this.map && prevProps.data !== this.props.data) { + this.map.getSource('positions').setData(this.props.data); } } @@ -39,7 +89,7 @@ class MainMap extends Component { width: '100%', height: '100%' }; - return
    this.el = el} />; + return
    this.mapContainer = el} />; } } -- cgit v1.2.3 From 00daddce89f70989baecd8725e6df9bc88257b53 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 22 Mar 2020 17:32:36 -0700 Subject: Display features --- modern/public/images/background.svg | 2 +- modern/public/images/icon/marker.svg | 2 ++ modern/src/DeviceList.js | 13 +------------ modern/src/MainMap.js | 32 +++++++++++++++++++++++++------- modern/src/SocketController.js | 9 ++++++++- modern/src/reducers/index.js | 16 +++++++++++----- 6 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 modern/public/images/icon/marker.svg (limited to 'modern/src/MainMap.js') diff --git a/modern/public/images/background.svg b/modern/public/images/background.svg index df2204d..3dcb687 100644 --- a/modern/public/images/background.svg +++ b/modern/public/images/background.svg @@ -1,4 +1,4 @@ - + diff --git a/modern/public/images/icon/marker.svg b/modern/public/images/icon/marker.svg new file mode 100644 index 0000000..f626547 --- /dev/null +++ b/modern/public/images/icon/marker.svg @@ -0,0 +1,2 @@ + + diff --git a/modern/src/DeviceList.js b/modern/src/DeviceList.js index c500242..f636921 100644 --- a/modern/src/DeviceList.js +++ b/modern/src/DeviceList.js @@ -1,6 +1,5 @@ import React, { Component, Fragment } from 'react'; import { connect } from 'react-redux'; -import { updateDevices } from './actions'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemAvatar from '@material-ui/core/ListItemAvatar'; @@ -13,20 +12,10 @@ import MoreVertIcon from '@material-ui/icons/MoreVert'; import Divider from '@material-ui/core/Divider'; const mapStateToProps = state => ({ - devices: state.devices + devices: Array.from(state.devices.values()) }); class DeviceList extends Component { - componentDidMount() { - fetch('/api/devices').then(response => { - if (response.ok) { - response.json().then(devices => { - this.props.dispatch(updateDevices(devices)); - }); - } - }); - } - render() { const devices = this.props.devices.map((device, index, list) => diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 925b402..1a8542a 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -3,18 +3,22 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import mapboxgl from 'mapbox-gl'; +const mapFeatureProperties = (state, position) => { + return { + name: state.devices.get(position.deviceId).name + } +} + const mapStateToProps = state => ({ data: { type: 'FeatureCollection', - features: state.positions.map(position => ({ + features: Array.from(state.positions.values()).map(position => ({ type: 'Feature', geometry: { type: 'Point', coordinates: [position.longitude, position.latitude] }, - properties: { - ...position - } + properties: mapFeatureProperties(state, position) })) } }); @@ -40,7 +44,6 @@ class MainMap extends Component { canvas.style.width = `${image.width}px`; canvas.style.height = `${image.height}px`; const context = canvas.getContext('2d'); - context.imageSmoothingEnabled = false; context.drawImage(image, 0, 0, canvas.width, canvas.height); this.map.addImage(key, context.getImageData(0, 0, canvas.width, canvas.height), { pixelRatio: window.devicePixelRatio @@ -53,6 +56,7 @@ class MainMap extends Component { this.map = map; this.loadImage('background', 'images/background.svg'); + this.loadImage('icon-marker', 'images/icon/marker.svg'); map.addSource('positions', { 'type': 'geojson', @@ -65,15 +69,29 @@ class MainMap extends Component { 'source': 'positions', 'layout': { 'icon-image': 'background', - 'text-field': 'Test Device Name', + 'icon-allow-overlap': true, + 'text-field': '{name}', + 'text-allow-overlap': true, + 'text-anchor': 'bottom', + 'text-offset': [0, -2], 'text-font': ['Roboto Regular'], - 'text-size': 11 + 'text-size': 12 }, 'paint':{ 'text-halo-color': 'white', 'text-halo-width': 1 } }); + + map.addLayer({ + 'id': 'device-icon', + 'type': 'symbol', + 'source': 'positions', + 'layout': { + 'icon-image': 'icon-marker', + 'icon-allow-overlap': true + } + }); } componentDidUpdate(prevProps) { diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js index f65fc6c..b89845f 100644 --- a/modern/src/SocketController.js +++ b/modern/src/SocketController.js @@ -43,7 +43,14 @@ class SocketController extends Component { } componentDidMount() { - this.connectSocket(); + fetch('/api/devices').then(response => { + if (response.ok) { + response.json().then(devices => { + this.props.dispatch(updateDevices(devices)); + }); + } + this.connectSocket(); + }); } render() { diff --git a/modern/src/reducers/index.js b/modern/src/reducers/index.js index ac592bf..4593d1f 100644 --- a/modern/src/reducers/index.js +++ b/modern/src/reducers/index.js @@ -1,20 +1,26 @@ const initialState = { - devices: [], - positions: [], - events: [] + devices: new Map(), + positions: new Map() }; +function updateMap(map, array, key) { + for (let value of array) { + map.set(value[key], value); + } + return map; +} + function rootReducer(state = initialState, action) { switch (action.type) { case 'UPDATE_DEVICES': return Object.assign({}, { ...state, - devices: [...action.devices] + devices: updateMap(state.devices, action.devices, 'id') }); case 'UPDATE_POSITIONS': return Object.assign({}, { ...state, - positions: [...action.positions] + positions: updateMap(state.positions, action.positions, 'deviceId') }); default: return state; -- cgit v1.2.3 From d9ac1837c3f1640a0b1daf201bf281cbc2f35bcb Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 22 Mar 2020 18:05:16 -0700 Subject: Initial map zoom --- modern/src/MainMap.js | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index 1a8542a..d934c51 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -1,4 +1,4 @@ -import 'mapbox-gl/src/css/mapbox-gl.css'; +import 'mapbox-gl/dist/mapbox-gl.css'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import mapboxgl from 'mapbox-gl'; @@ -28,8 +28,8 @@ class MainMap extends Component { const map = new mapboxgl.Map({ container: this.mapContainer, style: 'https://cdn.traccar.com/map/basic.json', - center: [25.65, 60.98], - zoom: 0 + center: [0, 0], + zoom: 1 }); map.on('load', () => this.mapDidLoad(map)); @@ -92,6 +92,37 @@ class MainMap extends Component { 'icon-allow-overlap': true } }); + + map.addControl(new mapboxgl.NavigationControl()); + + map.fitBounds(this.calculateBounds(), { + padding: 100, + maxZoom: 9 + }); + } + + calculateBounds() { + if (this.props.data.features) { + const first = this.props.data.features[0].geometry.coordinates; + const bounds = [[...first], [...first]]; + for (let feature of this.props.data.features) { + const longitude = feature.geometry.coordinates[0] + const latitude = feature.geometry.coordinates[1] + if (longitude < bounds[0][0]) { + bounds[0][0] = longitude; + } else if (longitude > bounds[1][0]) { + bounds[1][0] = longitude; + } + if (latitude < bounds[0][1]) { + bounds[0][1] = latitude; + } else if (latitude > bounds[1][1]) { + bounds[1][1] = latitude; + } + } + return bounds; + } else { + return [[0, 0], [0, 0]]; + } } componentDidUpdate(prevProps) { -- cgit v1.2.3 From 80f36b23de8557445623e530708298a557f9fa2e Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 22 Mar 2020 18:12:53 -0700 Subject: Wait for image loading --- modern/src/MainMap.js | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index d934c51..f030560 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -36,34 +36,43 @@ class MainMap extends Component { } loadImage(key, url) { - const image = new Image(); - image.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = image.width * window.devicePixelRatio; - canvas.height = image.height * window.devicePixelRatio; - canvas.style.width = `${image.width}px`; - canvas.style.height = `${image.height}px`; - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0, canvas.width, canvas.height); - this.map.addImage(key, context.getImageData(0, 0, canvas.width, canvas.height), { - pixelRatio: window.devicePixelRatio - }); - } - image.src = url; + return new Promise(resolutionFunc => { + const image = new Image(); + image.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = image.width * window.devicePixelRatio; + canvas.height = image.height * window.devicePixelRatio; + canvas.style.width = `${image.width}px`; + canvas.style.height = `${image.height}px`; + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0, canvas.width, canvas.height); + this.map.addImage(key, context.getImageData(0, 0, canvas.width, canvas.height), { + pixelRatio: window.devicePixelRatio + }); + resolutionFunc() + } + image.src = url; + }); } mapDidLoad(map) { this.map = map; - this.loadImage('background', 'images/background.svg'); - this.loadImage('icon-marker', 'images/icon/marker.svg'); + Promise.all([ + this.loadImage('background', 'images/background.svg'), + this.loadImage('icon-marker', 'images/icon/marker.svg') + ]).then(() => { + this.imagesDidLoad(); + }); + } - map.addSource('positions', { + imagesDidLoad() { + this.map.addSource('positions', { 'type': 'geojson', 'data': this.props.data }); - map.addLayer({ + this.map.addLayer({ 'id': 'device-background', 'type': 'symbol', 'source': 'positions', @@ -83,7 +92,7 @@ class MainMap extends Component { } }); - map.addLayer({ + this.map.addLayer({ 'id': 'device-icon', 'type': 'symbol', 'source': 'positions', @@ -93,9 +102,9 @@ class MainMap extends Component { } }); - map.addControl(new mapboxgl.NavigationControl()); + this.map.addControl(new mapboxgl.NavigationControl()); - map.fitBounds(this.calculateBounds(), { + this.map.fitBounds(this.calculateBounds(), { padding: 100, maxZoom: 9 }); -- cgit v1.2.3 From 990d485a21c945e7d57b85378650a65f3e79eed3 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sun, 22 Mar 2020 19:30:28 -0700 Subject: Handle list clicks --- modern/src/DeviceList.js | 7 ++++++- modern/src/MainMap.js | 22 ++++++++++++++++++++-- modern/src/actions/index.js | 5 +++++ modern/src/reducers/index.js | 5 +++++ 4 files changed, 36 insertions(+), 3 deletions(-) (limited to 'modern/src/MainMap.js') diff --git a/modern/src/DeviceList.js b/modern/src/DeviceList.js index f636921..03c5126 100644 --- a/modern/src/DeviceList.js +++ b/modern/src/DeviceList.js @@ -10,16 +10,21 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; import IconButton from '@material-ui/core/IconButton'; import MoreVertIcon from '@material-ui/icons/MoreVert'; import Divider from '@material-ui/core/Divider'; +import { selectDevice } from './actions'; const mapStateToProps = state => ({ devices: Array.from(state.devices.values()) }); class DeviceList extends Component { + handleClick(device) { + this.props.dispatch(selectDevice(device)); + } + render() { const devices = this.props.devices.map((device, index, list) => - + this.handleClick(device)}> diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js index f030560..35b933b 100644 --- a/modern/src/MainMap.js +++ b/modern/src/MainMap.js @@ -3,6 +3,16 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import mapboxgl from 'mapbox-gl'; +const calculateMapCenter = (state) => { + if (state.selectedDevice) { + const position = state.positions.get(state.selectedDevice); + if (position) { + return [position.longitude, position.latitude]; + } + } + return null; +} + const mapFeatureProperties = (state, position) => { return { name: state.devices.get(position.deviceId).name @@ -10,6 +20,7 @@ const mapFeatureProperties = (state, position) => { } const mapStateToProps = state => ({ + mapCenter: calculateMapCenter(state), data: { type: 'FeatureCollection', features: Array.from(state.positions.values()).map(position => ({ @@ -135,8 +146,15 @@ class MainMap extends Component { } componentDidUpdate(prevProps) { - if (this.map && prevProps.data !== this.props.data) { - this.map.getSource('positions').setData(this.props.data); + if (this.map) { + if (prevProps.mapCenter !== this.props.mapCenter) { + this.map.easeTo({ + center: this.props.mapCenter + }); + } + if (prevProps.data.features !== this.props.data.features) { + this.map.getSource('positions').setData(this.props.data); + } } } diff --git a/modern/src/actions/index.js b/modern/src/actions/index.js index 8ef49fa..5527810 100644 --- a/modern/src/actions/index.js +++ b/modern/src/actions/index.js @@ -12,3 +12,8 @@ export const updateEvents = events => ({ type: 'UPDATE_EVENTS', events }) + +export const selectDevice = device => ({ + type: 'SELECT_DEVICE', + device +}) diff --git a/modern/src/reducers/index.js b/modern/src/reducers/index.js index 4593d1f..752a4c3 100644 --- a/modern/src/reducers/index.js +++ b/modern/src/reducers/index.js @@ -22,6 +22,11 @@ function rootReducer(state = initialState, action) { ...state, positions: updateMap(state.positions, action.positions, 'deviceId') }); + case 'SELECT_DEVICE': + return Object.assign({}, { + ...state, + selectedDevice: action.device.id + }); default: return state; } -- cgit v1.2.3