diff --git a/package-lock.json b/package-lock.json index 241d7ff..a16af5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -689,6 +689,16 @@ "to-fast-properties": "^2.0.0" } }, + "@mapbox/corslite": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/corslite/-/corslite-0.0.7.tgz", + "integrity": "sha1-KfW2oYi6lG5RS98LZAHtT74To54=" + }, + "@mapbox/polyline": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-0.2.0.tgz", + "integrity": "sha1-biWYB0SqIjMflLZFpULALT/P7pc=" + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -7418,6 +7428,16 @@ "resolved": "https://registry.npmjs.org/leaflet-editable/-/leaflet-editable-1.1.0.tgz", "integrity": "sha1-93dZekCoGic/KHtIn9D+XM1gyNA=" }, + "leaflet-routing-machine": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/leaflet-routing-machine/-/leaflet-routing-machine-3.2.8.tgz", + "integrity": "sha512-cJPN//kMu6J7L7M/apfjx3SEAt7TFSFGj285To4vLTnq4h9uPo5u6Rd8JdIipyBJXED/UvUVG7LlpguQ+G7gqA==", + "requires": { + "@mapbox/corslite": "0.0.7", + "@mapbox/polyline": "^0.2.0", + "osrm-text-instructions": "^0.11.5" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -8881,6 +8901,11 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "osrm-text-instructions": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/osrm-text-instructions/-/osrm-text-instructions-0.11.5.tgz", + "integrity": "sha512-EKCfIXhJHsYQLcuctymvSVH7ulRXx5sGb2MdZL3NzD6XhRVZRkqwRicd9/QI27A5oXW4ojOEJ81RGay7bO6dbA==" + }, "output-file-sync": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz", diff --git a/package.json b/package.json index d57b7dd..c453214 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "history": "^4.7.2", "leaflet": "^1.3.3", "leaflet-editable": "^1.1.0", + "leaflet-routing-machine": "^3.2.8", "react": "^16.3.2", "react-dom": "^16.3.2", "react-hot-loader": "^4.1.1", diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..e1b5973 --- /dev/null +++ b/src/config.js @@ -0,0 +1,3 @@ +export const CONFIG = { + OSRM_URL: 'http://vault48.org:5000/route/v1', +}; diff --git a/src/constants/modes.js b/src/constants/modes.js index efa6a0c..4a65951 100644 --- a/src/constants/modes.js +++ b/src/constants/modes.js @@ -1,5 +1,6 @@ export const MODES = { POLY: 'POLY', STICKERS: 'STICKERS', + ROUTER: 'ROUTER', NONE: 'NONE', }; diff --git a/src/containers/App.jsx b/src/containers/App.jsx index 0defa4e..42f41b0 100644 --- a/src/containers/App.jsx +++ b/src/containers/App.jsx @@ -30,6 +30,8 @@ export class App extends React.Component { startStickerMode = () => this.editor.changeMode(MODES.STICKERS); + startRouterMode = () => this.editor.changeMode(MODES.ROUTER); + render() { const { mode } = this.state; @@ -43,6 +45,9 @@ export class App extends React.Component { + ); diff --git a/src/modules/Editor.js b/src/modules/Editor.js index f02957e..714d96b 100644 --- a/src/modules/Editor.js +++ b/src/modules/Editor.js @@ -1,7 +1,8 @@ import { Map } from '$modules/Map'; -import { Poly } from "$modules/Poly"; +import { Poly } from '$modules/Poly'; import { MODES } from '$constants/modes'; import { Stickers } from '$modules/Stickers'; +import { Router } from '$modules/Router'; export class Editor { constructor({ @@ -10,8 +11,12 @@ export class Editor { setMode }) { this.map = new Map({ container }); + + const { lockMapClicks, map: { map } } = this; + this.poly = new Poly({ map: this.map.map }); - this.stickers = new Stickers({ map: this.map.map }); + this.stickers = new Stickers({ map, lockMapClicks }); + this.router = new Router({ map, lockMapClicks }); this.setMode = setMode; this.mode = mode; @@ -20,14 +25,20 @@ export class Editor { [MODES.POLY]: { start: this.poly.continue, stop: this.poly.stop, + }, + [MODES.ROUTER]: { + start: this.routerSetStart, } }; this.clickHandlers = { - [MODES.STICKERS]: this.stickers.createOnClick + [MODES.STICKERS]: this.stickers.createOnClick, + [MODES.ROUTER]: this.router.pushWaypointOnClick, }; - this.map.map.addEventListener('mousedown', this.onClick); + map.addEventListener('mouseup', this.onClick); + map.addEventListener('dragstart', () => lockMapClicks(true)); + map.addEventListener('dragstop', () => lockMapClicks(false)); } changeMode = mode => { @@ -52,6 +63,29 @@ export class Editor { }; onClick = e => { + if (e.originalEvent.which === 3) return; // skip right click if (this.clickHandlers[this.mode]) this.clickHandlers[this.mode](e); }; + + lockMapClicks = lock => { + if (lock) { + this.map.map.removeEventListener('mouseup', this.onClick); + this.map.map.addEventListener('mouseup', this.unlockMapClicks); + } else { + this.map.map.removeEventListener('mouseup', this.unlockMapClicks); + this.map.map.addEventListener('mouseup', this.onClick); + } + }; + + unlockMapClicks = () => { + this.lockMapClicks(false); + }; + + routerSetStart = () => { + const { latlngs } = this.poly; + + if (!latlngs || !latlngs.length) return; + + this.router.startFrom(latlngs.pop()); + }; } diff --git a/src/modules/Map.js b/src/modules/Map.js index 6f92870..ee001d2 100644 --- a/src/modules/Map.js +++ b/src/modules/Map.js @@ -8,7 +8,7 @@ export class Map { constructor({ container }) { this.map = L.map(container, { editable: true }).setView([55.0153275, 82.9071235], 13); - this.tileLayer = L.tileLayer(providers.dgis, { + this.tileLayer = L.tileLayer(providers.default, { attribution: 'Независимое Велосообщество', maxNativeZoom: 18, maxZoom: 18, diff --git a/src/modules/Router.js b/src/modules/Router.js new file mode 100644 index 0000000..9a6d4a7 --- /dev/null +++ b/src/modules/Router.js @@ -0,0 +1,100 @@ +import L from 'leaflet'; +import 'leaflet-routing-machine'; +import { CONFIG } from '$config'; +import { DomMarker } from '$utils/DomMarker'; + +export class Router { + constructor({ map, lockMapClicks }) { + const routeLine = r => L.Routing.line(r, { + styles: [ + { color: 'white', opacity: 0.8, weight: 6 }, + { color: '#4597d0', opacity: 1, weight: 4, dashArray: '15,10' } + ], + addWaypoints: true, + }).on('linetouched', this.lockPropagations); + + this.router = L.Routing.control({ + serviceUrl: CONFIG.OSRM_URL, + profile: 'bike', + fitSelectedRoutes: false, + routeLine, + altLineOptions: { + styles: [{ color: '#4597d0', opacity: 1, weight: 3 }] + }, + show: false, + plan: L.Routing.plan([], { + createMarker: (i, wp) => L.marker(wp.latLng, { + draggable: true, + icon: this.createWaypointMarker(), + }), + routeWhileDragging: true, + }), + routeWhileDragging: true + }); + // .on('waypointschanged', this.updateWaypointsByEvent); + + this.router.addTo(map); + + this.waypoints = []; + this.lockMapClicks = lockMapClicks; + + console.log('router', this.router); + // this.router._line.on('mousedown', console.log); + console.log('map', map); + } + // + pushWaypointOnClick = ({ latlng: { lat, lng } }) => { + const waypoints = this.router.getWaypoints().filter(({ latLng }) => !!latLng); + console.log('push', waypoints); + this.router.setWaypoints([...waypoints, { lat, lng }]); + }; + // + // pushWaypoint = latlng => { + // this.waypoints.push(latlng); + // this.updateWaypoints(); + // }; + // + // updateWaypointsByEvent = (e) => { + // console.log('upd', e); + // // this.waypoints = waypoints.map(({ latlng }) => latlng); + // + // }; + // + // updateWaypoints = () => { + // this.router.setWaypoints(this.waypoints); + // }; + // + createWaypointMarker = () => { + const element = document.createElement('div'); + + element.addEventListener('mousedown', this.lockPropagations); + element.addEventListener('mouseup', this.unlockPropagations); + + return new DomMarker({ + element, + className: 'router-waypoint', + }); + }; + // + lockPropagations = () => { + console.log('lock'); + window.addEventListener('mouseup', this.unlockPropagations); + this.lockMapClicks(true); + }; + // + unlockPropagations = e => { + console.log('unlock'); + if (e && e.preventPropagations) { + console.log('stop'); + e.preventDefault(); + e.preventPropagations(); + } + + window.removeEventListener('mouseup', this.unlockPropagations); + setTimeout(() => this.lockMapClicks(false), 300); + }; + + startFrom = latlngs => { + this.router.setWaypoints([{ ...latlngs }]); + } +} diff --git a/src/modules/Sticker.js b/src/modules/Sticker.js index fe7b1cc..e74e9cd 100644 --- a/src/modules/Sticker.js +++ b/src/modules/Sticker.js @@ -1,17 +1,18 @@ import L from 'leaflet'; import 'leaflet-editable'; -import { DomMarker } from '$utils/leafletDomMarkers'; +import { DomMarker } from '$utils/DomMarker'; export class Sticker { constructor({ - latlng, deleteSticker, map + latlng, deleteSticker, map, lockMapClicks }) { this.angle = 2.2; this.isDragging = false; this.map = map; this.deleteSticker = deleteSticker; + this.lockMapClicks = lockMapClicks; this.element = document.createElement('div'); this.stickerImage = document.createElement('div'); @@ -34,14 +35,12 @@ export class Sticker { this.sticker = L.marker(latlng, { icon: marker }); - this.stickerImage.addEventListener('mousedown', this.onDragStart); - this.stickerImage.addEventListener('mouseup', this.onDragStop); - this.stickerImage.addEventListener('click', this.preventPropagations); - - this.element.addEventListener('mousedown', this.preventPropagations); - this.setAngle(this.angle); + this.stickerImage.addEventListener('mousedown', this.onDragStart); + this.stickerImage.addEventListener('mouseup', this.onDragStop); + + this.element.addEventListener('mouseup', this.preventPropagations); this.stickerDelete.addEventListener('click', this.onDelete); } @@ -55,6 +54,8 @@ export class Sticker { this.isDragging = true; this.sticker.disableEdit(); + this.lockMapClicks(true); + window.addEventListener('mousemove', this.onDrag); window.addEventListener('mouseup', this.onDragStop); }; @@ -74,6 +75,8 @@ export class Sticker { window.removeEventListener('mousemove', this.onDrag); window.removeEventListener('mouseup', this.onDragStop); + + this.lockMapClicks(false); }; onDrag = e => { @@ -82,7 +85,6 @@ export class Sticker { }; estimateAngle = e => { - console.log('est'); const { x, y } = this.element.getBoundingClientRect(); const { pageX, pageY } = e; this.angle = Math.atan2((y - pageY), (x - pageX)); diff --git a/src/modules/Stickers.js b/src/modules/Stickers.js index 2edf735..e846bd8 100644 --- a/src/modules/Stickers.js +++ b/src/modules/Stickers.js @@ -2,10 +2,11 @@ import L from 'leaflet'; import { Sticker } from '$modules/Sticker'; export class Stickers { - constructor({ map }) { + constructor({ map, lockMapClicks }) { this.map = map; this.layer = L.layerGroup(); + this.lockMapClicks = lockMapClicks; this.stickers = []; this.layer.addTo(this.map); @@ -23,6 +24,7 @@ export class Stickers { latlng, deleteSticker: this.deleteStickerByReference, map: this.map, + lockMapClicks: this.lockMapClicks, }); this.stickers.push(sticker); diff --git a/src/styles/mapScreen.js b/src/styles/mapScreen.js index 14a91c2..5f0cfb4 100644 --- a/src/styles/mapScreen.js +++ b/src/styles/mapScreen.js @@ -28,6 +28,32 @@ const vertexMixin = css` } `; +const routerMixin = css` + .leaflet-control-container .leaflet-routing-container-hide { + display: none; + } + + .router-waypoint { + width: 40px; + height: 40px; + margin-left: -20px; + margin-top: -20px; + outline: none; + z-index: 10001; + + ::after { + content: ' '; + display: block; + width: 20px; + height: 20px; + border-radius: 10px; + box-shadow: 0 0 0 2px #4597d0; + margin-left: 10px; + margin-top: 10px; + } + } +`; + const stickers = css` .sticker-container { outline: none; @@ -132,4 +158,5 @@ export const MapScreen = styled.div.attrs({ id: 'map' })` ${vertexMixin} ${stickers} + ${routerMixin} `; diff --git a/src/utils/leafletDomMarkers.js b/src/utils/DomMarker.js similarity index 100% rename from src/utils/leafletDomMarkers.js rename to src/utils/DomMarker.js