nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit a6dc4a31d9498dd633f860ad5576e33d87069ce6
parent 7ae72a8901dc9ecb001359e4b005684291472605
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Fri, 27 Apr 2018 17:09:12 +0300

replaced geo locating plugin with own one #59

Diffstat:
Mpackage.json | 1-
Msrc/App.js | 5++---
Asrc/lib/leaflet.control.locate/index.js | 348+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/lib/leaflet.control.mylocation/location-arrow-active.svg -> src/lib/leaflet.control.locate/location-arrow-active.svg | 0
Rsrc/lib/leaflet.control.mylocation/location-arrow.svg -> src/lib/leaflet.control.locate/location-arrow.svg | 0
Asrc/lib/leaflet.control.locate/style.css | 33+++++++++++++++++++++++++++++++++
Dsrc/lib/leaflet.control.mylocation/index.js | 138-------------------------------------------------------------------------------
Dsrc/lib/leaflet.control.mylocation/style.css | 37-------------------------------------
8 files changed, 383 insertions(+), 179 deletions(-)

diff --git a/package.json b/package.json @@ -61,7 +61,6 @@ "image-promise": "^4.0.1", "knockout": "^3.4.0", "leaflet": "1.0.3", - "leaflet.locatecontrol": "^0.62.0", "load-script": "^1.0.0", "mapillary-js": "2.5.2", "pbf": "^3.0.5", diff --git a/src/App.js b/src/App.js @@ -26,7 +26,7 @@ import 'lib/leaflet.control.jnx'; import 'lib/leaflet.control.jnx/hash-state'; import 'lib/leaflet.control.azimuth'; import {hashState, bindHashStateReadOnly} from 'lib/leaflet.hashState/hashState'; -import {MyLocate} from 'lib/leaflet.control.mylocation'; +import {LocateControl} from 'lib/leaflet.control.locate'; function setUp() { @@ -67,8 +67,7 @@ function setUp() { const azimuthControl = new L.Control.Azimuth({position: 'topleft'}).addTo(map); - const location = new MyLocate(); - location.addTo(map); + new LocateControl({position: 'topleft'}).addTo(map); /////////// controls top-right corner diff --git a/src/lib/leaflet.control.locate/index.js b/src/lib/leaflet.control.locate/index.js @@ -0,0 +1,348 @@ +import L from 'leaflet'; +import 'lib/leaflet.control.commons' +import './style.css'; + +const STATE_DISABLED = 'disabled'; +const STATE_LOCATING = 'locating'; +const STATE_ENABLED = 'enabled'; +const STATE_ENABLED_FOLLOWING = 'enabled_following'; +const STATE_MOVING_TO_FOLLOWING = 'moving_to_following'; +const STATE_MOVING_TO_FOLLOWING_FIRST = 'moving_to_following_first'; +const STATE_UPDATING_FOLLOWING = 'updating_following'; + +const EVENT_INIT = 'init'; +const EVENT_BUTTON_CLICK = 'button_click'; +const EVENT_LOCATION_RECEIVED = 'location_received'; +const EVENT_LOCATION_ERROR = 'location_error'; +const EVENT_MAP_MOVE = 'user_move'; +const EVENT_MAP_MOVE_END = 'map_move_end'; + + +const PositionMarker = L.LayerGroup.extend({ + initialize: function(options) { + L.LayerGroup.prototype.initialize.call(this, options); + this._locationSet = false; + this._elements = { + accuracyCircle: L.circle([0, 0], { + radius: 1, + interactive: false, + fillColor: '#4271a8', + color: '#2ba3f7', + weight: 2 + }), + markerCircle: L.circleMarker([0, 0], { + interactive: false, + radius: 10, + color: '#2ba3f7', + weight: 2.5, + fill: null, + opacity: 0.8 + }), + markerPoint: L.circleMarker([0, 0], { + interactive: false, + radius: 2, + weight: 0, + color: '#2ba3f7', + fillOpacity: 0.8 + }), + }; + this.addLayer(this._elements.accuracyCircle); + + }, + + onAdd: function(map) { + L.LayerGroup.prototype.onAdd.call(this, map); + map.on('zoom', this._onZoom, this); + this._updatePrecisionState(); + }, + + onRemove: function(map) { + map.off('zoom', this._onZoom, this); + L.LayerGroup.prototype.onRemove.call(this, map); + }, + + _updatePrecisionState: function() { + if (!this._map || !this._locationSet) { + return; + } + const precise = this._elements.accuracyCircle._radius <= this._elements.markerCircle.options.radius * 0.8; + if (precise !== this._precise) { + if (precise) { + this._elements.accuracyCircle.setStyle({opacity: 0, fillOpacity: 0}); + this.addLayer(this._elements.markerPoint); + this.addLayer(this._elements.markerCircle); + } else { + this._elements.accuracyCircle.setStyle({opacity: 0.8, fillOpacity: 0.4}); + this.removeLayer(this._elements.markerPoint); + this.removeLayer(this._elements.markerCircle); + + } + this._precise = precise; + } + }, + + setLocation: function(latlng, accuracy) { + this._elements.accuracyCircle.setLatLng(latlng); + this._elements.accuracyCircle.setRadius(accuracy); + this._elements.markerCircle.setLatLng(latlng); + this._elements.markerPoint.setLatLng(latlng); + this._locationSet = true; + this._updatePrecisionState(); + }, + + _onZoom: function(e) { + this._updatePrecisionState(); + } + +}); + +const LocateControl = L.Control.extend({ + // button click behavior: + // if button turned off -- turn on, maps follows marker + // if button turned on + // if map is following marker -- turn off + // if map not following marker -- center map at marker, start following + + options: { + locationAcquireTimeoutMS: 10000, + showError: ({message}) => { + alert(message); + }, + maxAutoZoom: 17, + minAutoZoomDeltaForAuto: 4, + minDistForAutoZoom: 2 // in average screen sizes + }, + + initialize: function(options) { + L.Control.prototype.initialize.call(this, options); + this._events = []; + }, + + onAdd: function(map) { + this._map = map; + const container = this._container = L.DomUtil.create( + 'div', 'leaflet-bar leaflet-control leaflet-control-locate' + ); + this._stopContainerEvents(); + const link = L.DomUtil.create('a', '', container); + L.DomUtil.create('div', 'icon-position', link); + L.DomEvent.on(container, 'click', () => this._handleEvent(EVENT_BUTTON_CLICK)); + this._marker = new PositionMarker(); + this._handleEvent(EVENT_INIT); + return container; + }, + + _startLocating: function() { + if (!('geolocation' in navigator)) { + const error = {code: 0, message: 'Geolocation not supported'}; + setTimeout(() => { + this._onLocationError(error); + }, 0 + ) + } + this._watchID = navigator.geolocation.watchPosition( + (pos) => this._handleEvent(EVENT_LOCATION_RECEIVED, pos), + (e) => this._handleEvent(EVENT_LOCATION_ERROR, e), + { + enableHighAccuracy: true, + timeout: this.options.locationAcquireTimeoutMS, + } + ); + }, + + _stopLocating: function() { + if (this._watchID && navigator.geolocation) { + navigator.geolocation.clearWatch(this._watchID); + } + }, + + _storeLocation: function(position) { + this._latlng = L.latLng(position.coords.latitude, position.coords.longitude); + this._accuracy = position.coords.accuracy; + }, + + _updateMarkerLocation: function() { + this._marker.setLocation(this._latlng, this._accuracy); + }, + + _updateMapPosition: function() { + this._map.panTo(this._latlng, {noMoveStart: true}); + }, + + _setViewToLocation: function(preferAutoZoom) { + if (!this._map || !this._latlng) { + return; + } + + // autoZoom -- to fit accuracy cirlce on screen, but not more then options.maxAutoZoom (17) + // if current zoom more then options.minAutoZoomDeltaForAuto less then autoZoom, set autoZoom + // if map center far from geolocation, set autoZoom + // if map center not far from geolocation + // if accuracy circle does not fit at current zoom, zoom out to fit + // if current zoom is less then minAutoZoomDeltaForAuto less then autoZoom or >= autoZoom and circle fits screen, keep current zoom + + const currentZoom = this._map.getZoom(); + let zoomFitAccuracy = this._map.getBoundsZoom(this._latlng.toBounds(this._accuracy * 2)); + let autoZoom = zoomFitAccuracy; + let newZoom; + autoZoom = Math.min(autoZoom, this.options.maxAutoZoom); + + if (preferAutoZoom || autoZoom - currentZoom >= this.options.minAutoZoomDeltaForAuto) { + newZoom = autoZoom; + } else { + const p1 = this._map.project(this._map.getCenter()); + const p2 = this._map.project(this._latlng); + const screenSize = this._map.getSize(); + const averageScreenSize = (screenSize.x + screenSize.y) / 2; + if (p1.distanceTo(p2) > averageScreenSize * this.options.minDistForAutoZoom) { + newZoom = autoZoom + } else { + newZoom = currentZoom > zoomFitAccuracy ? zoomFitAccuracy : currentZoom; + } + } + this._map.setView(this._latlng, newZoom); + }, + + _onMapMove: function() { + this._handleEvent(EVENT_MAP_MOVE) + }, + + _onMapMoveEnd: function() { + const ll = this._map.getCenter(); + setTimeout(() => { + if (this._map.getCenter().equals(ll)) { + this._handleEvent(EVENT_MAP_MOVE_END) + } + }, 100); + }, + + _isMapCenteredAtLocation: function() { + if (!this._latlng || !this._map) { + return false; + } + let p1 = this._map.project(this._latlng); + let p2 = this._map.project(this._map.getCenter()); + return p1.distanceTo(p2) < 5; + }, + + _updateButtonClasses: function(add, remove) { + for (let cls of add) { + L.DomUtil.addClass(this._container, cls); + } + for (let cls of remove) { + L.DomUtil.removeClass(this._container, cls); + } + }, + + _setEvents: function(on) { + const f = on ? 'on' : 'off'; + this._map[f]('move', this._onMapMove, this); + this._map[f]('moveend', this._onMapMoveEnd, this); + }, + + _handleEvent: function(event, data) { + this._events.push({event, data}); + if (!this._processingEvent) { + this._processingEvent = true; + while (this._events.length) { + this._processEvent(this._events.shift()); + } + this._processingEvent = false; + } + }, + + _processEvent: function({event, data}) { + // console.log('PROCESS EVENT', event); + const state = this._state; + switch (event) { + case EVENT_INIT: + this._setState(STATE_DISABLED); + break; + case EVENT_BUTTON_CLICK: + if (state === STATE_DISABLED) { + this._setState(STATE_LOCATING); + } else if (state === STATE_ENABLED) { + this._setState(STATE_MOVING_TO_FOLLOWING); + this._setViewToLocation(); + } else { + this._setState(STATE_DISABLED); + } + break; + case EVENT_LOCATION_RECEIVED: + if (state === STATE_DISABLED) { + return; + } + this._storeLocation(data); + this._updateMarkerLocation(); + if (state === STATE_LOCATING || state === STATE_MOVING_TO_FOLLOWING_FIRST) { + this._setViewToLocation(true); + this._setState(STATE_MOVING_TO_FOLLOWING_FIRST); + } else if (state === STATE_MOVING_TO_FOLLOWING) { + this._setViewToLocation(); + } else if (this._state === STATE_ENABLED_FOLLOWING || state === STATE_UPDATING_FOLLOWING) { + this._updateMapPosition(); + this._setState(STATE_UPDATING_FOLLOWING) + } + break; + case EVENT_LOCATION_ERROR: + if (state === STATE_DISABLED) { + return + } + this.options.showError(data); + this._setState(STATE_DISABLED); + break; + case EVENT_MAP_MOVE: + if (state === STATE_ENABLED_FOLLOWING) { + if (!this._isMapCenteredAtLocation()) { + this._setState(STATE_ENABLED); + } + } + break; + case EVENT_MAP_MOVE_END: + if (state === STATE_MOVING_TO_FOLLOWING || state === STATE_UPDATING_FOLLOWING || state === STATE_MOVING_TO_FOLLOWING_FIRST) { + if (this._isMapCenteredAtLocation()) { + this._setState(STATE_ENABLED_FOLLOWING); + } else { + this._setState(STATE_ENABLED); + } + } + break; + default: + } + }, + + _setState: function(newState) { + const oldState = this._state; + if (oldState === newState) { + return; + } + console.log(`STATE: ${oldState} -> ${newState}`); + switch (newState) { + case STATE_LOCATING: + this._startLocating(); + this._updateButtonClasses(['requesting'], ['active', 'following']); + this._setEvents(true); + break; + case STATE_DISABLED: + this._stopLocating(); + this._marker.removeFrom(this._map); + this._setEvents(false); + this._updateButtonClasses([], ['active', 'following', 'requesting']); + break; + case STATE_ENABLED: + this._updateButtonClasses(['active'], ['following', 'requesting']); + break; + case STATE_MOVING_TO_FOLLOWING_FIRST: + this._marker.addTo(this._map); + break; + case STATE_ENABLED_FOLLOWING: + this._updateButtonClasses(['active', 'following'], ['requesting']); + break; + default: + } + this._state = newState; + }, + } +); + +export {LocateControl}; diff --git a/src/lib/leaflet.control.mylocation/location-arrow-active.svg b/src/lib/leaflet.control.locate/location-arrow-active.svg diff --git a/src/lib/leaflet.control.mylocation/location-arrow.svg b/src/lib/leaflet.control.locate/location-arrow.svg diff --git a/src/lib/leaflet.control.locate/style.css b/src/lib/leaflet.control.locate/style.css @@ -0,0 +1,33 @@ +.icon-position { + background-image: url("location-arrow.svg"); + background-size: 61%; + background-repeat: no-repeat; + background-position: 50% 50%; + width: 100%; + height: 100%; +} + +.following .icon-position { + background-image: url("location-arrow-active.svg"); +} + +.requesting .icon-position { + animation-name: spin; + animation-duration: 500ms; + animation-iteration-count: infinite; + animation-timing-function: linear; + animation-delay: 100ms; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.leaflet-control-locate.active a { + background-color: #cce8ff; +} diff --git a/src/lib/leaflet.control.mylocation/index.js b/src/lib/leaflet.control.mylocation/index.js @@ -1,137 +0,0 @@ -import L from 'leaflet'; -import 'leaflet.locatecontrol'; -import './style.css'; - - -const MyLocate = L.Control.Locate.extend({ - options: { - icon: 'icon-position', - iconLoading: 'icon-position', - setView: 'untilPan', - flyTo: true, - cacheLocation: false, - showPopup: false, - locateOptions: { - enableHighAccuracy: true, - watch: true, - setView: false - }, - maxZoom: 16, - - circleStyle: { - interactive: false, - color: '#4271a8', - fillOpacity: 0.3, - weight: 0, - }, - markerStyle: { - color: '#2a85d4', - weight: 2.5, - opacity: 0.8, - fillOpacity: 0.4, - radius: 8 - }, - minCirclePixelRadius: 50 - - }, - - start: function() { - this.options.keepCurrentZoomLevel = false; - L.Control.Locate.prototype.start.call(this); - }, - - _onDrag: function() { - if (this._settingView) { - return; - } - L.Control.Locate.prototype._onDrag.call(this); - }, - - _activate: function() { - if (!this._active) { - this._map.on('movestart', this._onDrag, this); - this._map.on('zoom', this._onZoom, this); - } - L.Control.Locate.prototype._activate.call(this); - }, - - _deactivate: function() { - L.Control.Locate.prototype._deactivate.call(this); - this._map.off('movestart', this._onDrag, this); - this._map.off('zoom', this._onZoom, this); - }, - - _onZoom: function() { - if (!this._circle || !this.options.minCirclePixelRadius) { - return; - } - if (typeof this._circle._origFillOpacity === 'undefined') { - this._circle._origFillOpacity = this._circle.options.fillOpacity; - } - L.Util.requestAnimFrame(() => { - const opacity = this._circle._radius < this.options.minCirclePixelRadius ? - 0 : this._circle._origFillOpacity; - console.log(this._circle._radius, this.options.minCirclePixelRadius, this._circle._origFillOpacity); - this._circle.setStyle({fillOpacity: opacity}); - }); - }, - - _onClick: function() { - this.options.keepCurrentZoomLevel = false; - L.Control.Locate.prototype._onClick.call(this); - }, - - _onMarkerClick: function() { - this._userPanned = false; - this._updateContainerStyle(); - this.options.keepCurrentZoomLevel = false; - this.setView(); - }, - - _drawMarker: function() { - var newMarker = !this._marker; - L.Control.Locate.prototype._drawMarker.call(this); - if (newMarker) { - this._marker.on('click', this._onMarkerClick.bind(this)); - } - }, - - setView: function() { - this._drawMarker(); - if (this._isOutsideMapBounds()) { - this._event = undefined; // clear the current location so we can get back into the bounds - this.options.onLocationOutsideMapBounds(this); - } else { - var coords = this._event, - lat = coords.latitude, - lng = coords.longitude, - latlng = new L.LatLng(lat, lng), - zoom; - if (!this.options.keepCurrentZoomLevel) { - // fix for leaflet issue #6139 - var bounds = latlng.toBounds(coords.accuracy * 2); - zoom = this._map.getBoundsZoom(bounds); - zoom = this.options.maxZoom ? Math.min(zoom, this.options.maxZoom) : zoom; - } - this._settingView = true; - this._map.once('moveend', () => { - this._settingView = false; - }); - var f = this.options.flyTo ? this._map.flyTo : this._map.setView; - f.call(this._map, latlng, zoom); - - this.options.keepCurrentZoomLevel = true; - } - }, - - } -); - -export {MyLocate}; - - -// + при включение зумить, но не ближе ~16 уровня и не ближе круга точности. -// + зум можно уменьшать только если круг точности большой и на 16 уровень не помещается -// + при обновлении позиции зум не менять -// + при плавном приближении маркер сильно зумится -// + а что с сохранением состоямия в localStorage? -- ничего -\ No newline at end of file diff --git a/src/lib/leaflet.control.mylocation/style.css b/src/lib/leaflet.control.mylocation/style.css @@ -1,37 +0,0 @@ -.icon-position { - display: block; - position: absolute; - width: 60%; - height: 60%; - top: 20%; - left: 20%; - background-image: url("location-arrow.svg"); - background-size: 100%; - background-repeat: no-repeat; - background-position: 50% 50%; -} - -.following .icon-position { - background-image: url("location-arrow-active.svg"); -} - -.requesting .icon-position { - animation-name: spin; - animation-duration: 500ms; - animation-iteration-count: infinite; - animation-timing-function: linear; - animation-delay: 100ms; -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -.leaflet-control-locate.active a { - background-color: #cce8ff; -}