aboutsummaryrefslogtreecommitdiff
path: root/modern/src/MainMap.js
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2020-03-22 23:07:16 -0700
committerGitHub <noreply@github.com>2020-03-22 23:07:16 -0700
commit48a726021f5d3c741749094891d529ccb3ba59b4 (patch)
tree8df80eca54f9dd39664f63365ffcc2ec248fb3df /modern/src/MainMap.js
parentf5165c8e897e8d9cf4219d943e2d34b61adb48b5 (diff)
parentba9cc86f667486a09edb323402c2d63ada5ea639 (diff)
downloadtrackermap-web-48a726021f5d3c741749094891d529ccb3ba59b4.tar.gz
trackermap-web-48a726021f5d3c741749094891d529ccb3ba59b4.tar.bz2
trackermap-web-48a726021f5d3c741749094891d529ccb3ba59b4.zip
Merge pull request #768 from traccar/modern
Create a new React web app
Diffstat (limited to 'modern/src/MainMap.js')
-rw-r--r--modern/src/MainMap.js172
1 files changed, 172 insertions, 0 deletions
diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js
new file mode 100644
index 00000000..35b933b4
--- /dev/null
+++ b/modern/src/MainMap.js
@@ -0,0 +1,172 @@
+import 'mapbox-gl/dist/mapbox-gl.css';
+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
+ }
+}
+
+const mapStateToProps = state => ({
+ mapCenter: calculateMapCenter(state),
+ data: {
+ type: 'FeatureCollection',
+ features: Array.from(state.positions.values()).map(position => ({
+ type: 'Feature',
+ geometry: {
+ type: 'Point',
+ coordinates: [position.longitude, position.latitude]
+ },
+ properties: mapFeatureProperties(state, position)
+ }))
+ }
+});
+
+class MainMap extends Component {
+ componentDidMount() {
+ const map = new mapboxgl.Map({
+ container: this.mapContainer,
+ style: 'https://cdn.traccar.com/map/basic.json',
+ center: [0, 0],
+ zoom: 1
+ });
+
+ map.on('load', () => this.mapDidLoad(map));
+ }
+
+ loadImage(key, 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;
+
+ Promise.all([
+ this.loadImage('background', 'images/background.svg'),
+ this.loadImage('icon-marker', 'images/icon/marker.svg')
+ ]).then(() => {
+ this.imagesDidLoad();
+ });
+ }
+
+ imagesDidLoad() {
+ this.map.addSource('positions', {
+ 'type': 'geojson',
+ 'data': this.props.data
+ });
+
+ this.map.addLayer({
+ 'id': 'device-background',
+ 'type': 'symbol',
+ 'source': 'positions',
+ 'layout': {
+ 'icon-image': 'background',
+ 'icon-allow-overlap': true,
+ 'text-field': '{name}',
+ 'text-allow-overlap': true,
+ 'text-anchor': 'bottom',
+ 'text-offset': [0, -2],
+ 'text-font': ['Roboto Regular'],
+ 'text-size': 12
+ },
+ 'paint':{
+ 'text-halo-color': 'white',
+ 'text-halo-width': 1
+ }
+ });
+
+ this.map.addLayer({
+ 'id': 'device-icon',
+ 'type': 'symbol',
+ 'source': 'positions',
+ 'layout': {
+ 'icon-image': 'icon-marker',
+ 'icon-allow-overlap': true
+ }
+ });
+
+ this.map.addControl(new mapboxgl.NavigationControl());
+
+ this.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) {
+ 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);
+ }
+ }
+ }
+
+ render() {
+ const style = {
+ position: 'relative',
+ overflow: 'hidden',
+ width: '100%',
+ height: '100%'
+ };
+ return <div style={style} ref={el => this.mapContainer = el} />;
+ }
+}
+
+export default connect(mapStateToProps)(MainMap);