From 1820aef3fb5c926664de1d4d484f64a5c9ba7099 Mon Sep 17 00:00:00 2001 From: YANGDL <114714267@qq.com> Date: 星期二, 05 一月 2021 17:06:08 +0800 Subject: [PATCH] 优化逻辑 --- src/components/plugin/wmts_plugins.js | 172 src/components/plugin/Editable.js | 3845 +++++++++++----------- src/components/plugin/PathDashFlow.js | 54 src/components/plugin/MagicMarker.js | 78 src/components/plugin/CanvasMarkers.js | 744 ++-- src/components/plugin/CustomPopup.js | 126 src/components/plugin/cluster-layer/leaflet.markercluster-src.js | 4503 +++++++++++++------------- src/utils/tools.js | 27 src/components/plugin/PathDrag.js | 262 9 files changed, 4,906 insertions(+), 4,905 deletions(-) diff --git a/src/components/plugin/CanvasMarkers.js b/src/components/plugin/CanvasMarkers.js index b2693df..28ab01a 100644 --- a/src/components/plugin/CanvasMarkers.js +++ b/src/components/plugin/CanvasMarkers.js @@ -1,386 +1,386 @@ 'use strict' import Rbush from 'rbush' -function init(L) { - var CanvasIconLayer = (L.Layer ? L.Layer : L.Class).extend({ +function init (L) { + var CanvasIconLayer = (L.Layer ? L.Layer : L.Class).extend({ - // Add event listeners to initialized section. - initialize: function(options) { - L.setOptions(this, options) - this._onClickListeners = [] - this._onHoverListeners = [] - }, + // Add event listeners to initialized section. + initialize: function (options) { + L.setOptions(this, options) + this._onClickListeners = [] + this._onHoverListeners = [] + }, - setOptions: function(options) { - L.setOptions(this, options) - return this.redraw() - }, - redraw: function() { - this._redraw(true) - }, + setOptions: function (options) { + L.setOptions(this, options) + return this.redraw() + }, + redraw: function () { + this._redraw(true) + }, - // Multiple layers at a time for rBush performance - addMarkers: function(markers) { - var self = this - var tmpMark = [] - var tmpLatLng = [] + // Multiple layers at a time for rBush performance + addMarkers: function (markers) { + var self = this + var tmpMark = [] + var tmpLatLng = [] - markers.forEach(function(marker) { - if (!((marker.options.pane === 'markerPane') && marker.options.icon)) { - console.error('Layer isn\'t a marker') - return - } - - var latlng = marker.getLatLng() - var isDisplaying = self._map.getBounds().contains(latlng) - var s = self._addMarker(marker, latlng, isDisplaying) - - // Only add to Point Lookup if we are on map - if (isDisplaying === true) tmpMark.push(s[0]) - - tmpLatLng.push(s[1]) - }) - - self._markers.load(tmpMark) - self._latlngMarkers.load(tmpLatLng) - }, - - // Adds single layer at a time. Less efficient for rBush - addMarker: function(marker) { - var self = this - var latlng = marker.getLatLng() - var isDisplaying = self._map.getBounds().contains(latlng) - var dat = self._addMarker(marker, latlng, isDisplaying) - - // Only add to Point Lookup if we are on map - if (isDisplaying === true) self._markers.insert(dat[0]) - - self._latlngMarkers.insert(dat[1]) - }, - - addLayer: function(layer) { - if ((layer.options.pane === 'markerPane') && layer.options.icon) this.addMarker(layer) - else console.error('Layer isn\'t a marker') - }, - - addLayers: function(layers) { - this.addMarkers(layers) - }, - - removeLayer: function(layer) { - this.removeMarker(layer, true) - }, - - removeMarker: function(marker, redraw) { - var self = this - - // If we are removed point - if (marker['minX']) marker = marker.data - - var latlng = marker.getLatLng() - var isDisplaying = self._map.getBounds().contains(latlng) - - var markerData = { - - minX: latlng.lng, - minY: latlng.lat, - maxX: latlng.lng, - maxY: latlng.lat, - data: marker - } - - self._latlngMarkers.remove(markerData, function(a, b) { - return a.data._leaflet_id === b.data._leaflet_id - }) - - self._latlngMarkers.total-- - self._latlngMarkers.dirty++ - - if (isDisplaying === true && redraw === true) { - self._redraw(true) - } - }, - - onAdd: function(map) { - this._map = map - - if (!this._canvas) this._initCanvas() - - if (this.options.pane) this.getPane().appendChild(this._canvas) - else map._panes.overlayPane.appendChild(this._canvas) - - map.on('moveend', this._reset, this) - map.on('resize', this._reset, this) - - map.on('click', this._executeListeners, this) - map.on('mousemove', this._executeListeners, this) - map.on('zoomstart', this._canvasHide, this) - map.on('zoomend', this._canvasShow, this) - }, - - onRemove: function(map) { - if (this.options.pane) this.getPane().removeChild(this._canvas) - else map.getPanes().overlayPane.removeChild(this._canvas) - - map.off('click', this._executeListeners, this) - map.off('mousemove', this._executeListeners, this) - - map.off('moveend', this._reset, this) - map.off('resize', this._reset, this) - map.off('zoomstart', this._canvasHide, this) - map.off('zoomend', this._canvasShow, this) - }, - - addTo: function(map) { - map.addLayer(this) - return this - }, - - clearLayers: function() { - this._latlngMarkers = null - this._markers = null - this._redraw(true) - }, - _canvasHide: function() { - this._canvas.style.visibility = 'hidden' - }, - _canvasShow: function() { - this._canvas.style.visibility = 'visible' - }, - _addMarker: function(marker, latlng, isDisplaying) { - var self = this - // Needed for pop-up & tooltip to work. - marker._map = self._map - - // _markers contains Points of markers currently displaying on map - if (!self._markers) self._markers = new Rbush() - - // _latlngMarkers contains Lat\Long coordinates of all markers in layer. - if (!self._latlngMarkers) { - self._latlngMarkers = new Rbush() - self._latlngMarkers.dirty = 0 - self._latlngMarkers.total = 0 - } - - L.Util.stamp(marker) - - var pointPos = self._map.latLngToContainerPoint(latlng) - var iconSize = marker.options.icon.options.iconSize - - var adjX = iconSize[0] / 2 - var adjY = iconSize[1] / 2 - var ret = [({ - minX: (pointPos.x - adjX), - minY: (pointPos.y - adjY), - maxX: (pointPos.x + adjX), - maxY: (pointPos.y + adjY), - data: marker - }), ({ - minX: latlng.lng, - minY: latlng.lat, - maxX: latlng.lng, - maxY: latlng.lat, - data: marker - })] - - self._latlngMarkers.dirty++ - self._latlngMarkers.total++ - - // Only draw if we are on map - if (isDisplaying === true) self._drawMarker(marker, pointPos) - - return ret - }, - - _drawMarker: function(marker, pointPos) { - var self = this - - if (!this._imageLookup) this._imageLookup = {} - if (!pointPos) { - pointPos = self._map.latLngToContainerPoint(marker.getLatLng()) - } - - var iconUrl = marker.options.icon.options.iconUrl - - if (marker.canvas_img) { - self._drawImage(marker, pointPos) - } else { - if (self._imageLookup[iconUrl]) { - marker.canvas_img = self._imageLookup[iconUrl][0] - - if (self._imageLookup[iconUrl][1] === false) { - self._imageLookup[iconUrl][2].push([marker, pointPos]) - } else { - self._drawImage(marker, pointPos) - } - } else { - var i = new Image() - i.src = iconUrl - marker.canvas_img = i - - // Image,isLoaded,marker\pointPos ref - self._imageLookup[iconUrl] = [i, false, [[marker, pointPos]]] - - i.onload = function() { - self._imageLookup[iconUrl][1] = true - self._imageLookup[iconUrl][2].forEach(function(e) { - self._drawImage(e[0], e[1]) - }) - } - } - } - }, - - _drawImage: function(marker, pointPos) { - var options = marker.options.icon.options - - this._context.drawImage( - marker.canvas_img, - pointPos.x - options.iconAnchor[0], - pointPos.y - options.iconAnchor[1], - options.iconSize[0], - options.iconSize[1] - ) - }, - - _reset: function() { - var topLeft = this._map.containerPointToLayerPoint([0, 0]) - L.DomUtil.setPosition(this._canvas, topLeft) - - var size = this._map.getSize() - - this._canvas.width = size.x - this._canvas.height = size.y - - this._redraw() - }, - - _redraw: function(clear) { - var self = this - - if (clear) this._context.clearRect(0, 0, this._canvas.width, this._canvas.height) - if (!this._map || !this._latlngMarkers) return - - var tmp = [] - - // If we are 10% individual inserts\removals, reconstruct lookup for efficiency - if (self._latlngMarkers.dirty / self._latlngMarkers.total >= 0.1) { - self._latlngMarkers.all().forEach(function(e) { - tmp.push(e) - }) - - self._latlngMarkers.clear() - self._latlngMarkers.load(tmp) - self._latlngMarkers.dirty = 0 - tmp = [] - } - - var mapBounds = self._map.getBounds() - - // Only re-draw what we are showing on the map. - - var mapBoxCoords = { - - minX: mapBounds.getWest(), - minY: mapBounds.getSouth(), - maxX: mapBounds.getEast(), - maxY: mapBounds.getNorth() - } - - self._latlngMarkers.search(mapBoxCoords).forEach(function(e) { - // Readjust Point Map - var pointPos = self._map.latLngToContainerPoint(e.data.getLatLng()) - - var iconSize = e.data.options.icon.options.iconSize - var adjX = iconSize[0] / 2 - var adjY = iconSize[1] / 2 - - var newCoords = { - minX: (pointPos.x - adjX), - minY: (pointPos.y - adjY), - maxX: (pointPos.x + adjX), - maxY: (pointPos.y + adjY), - data: e.data - } - - tmp.push(newCoords) - - // Redraw points - self._drawMarker(e.data, pointPos) - }) - - // Clear rBush & Bulk Load for performance - this._markers.clear() - this._markers.load(tmp) - }, - - _initCanvas: function() { - this._canvas = L.DomUtil.create('canvas', 'leaflet-canvas-icon-layer leaflet-layer') - var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']) - this._canvas.style[originProp] = '50% 50%' - - var size = this._map.getSize() - this._canvas.width = size.x - this._canvas.height = size.y - - this._context = this._canvas.getContext('2d') - - var animated = this._map.options.zoomAnimation && L.Browser.any3d - L.DomUtil.addClass(this._canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide')) - }, - - addOnClickListener: function(listener) { - this._onClickListeners.push(listener) - }, - - addOnHoverListener: function(listener) { - this._onHoverListeners.push(listener) - }, - - _executeListeners: function(event) { - if (!this._markers) return - - var me = this - var x = event.containerPoint.x - var y = event.containerPoint.y - - if (me._openToolTip) { - me._openToolTip.closeTooltip() - delete me._openToolTip - } - - var ret = this._markers.search({ minX: x, minY: y, maxX: x, maxY: y }) - - if (ret && ret.length > 0) { - me._map._container.style.cursor = 'pointer' - - if (event.type === 'click') { - var hasPopup = ret[0].data.getPopup() - if (hasPopup) ret[0].data.openPopup() - - me._onClickListeners.forEach(function(listener) { listener(event, ret) }) - } - - if (event.type === 'mousemove') { - var hasTooltip = ret[0].data.getTooltip() - if (hasTooltip) { - me._openToolTip = ret[0].data - ret[0].data.openTooltip() - } - - me._onHoverListeners.forEach(function(listener) { listener(event, ret) }) - } - } else { - me._map._container.style.cursor = '' - } + markers.forEach(function (marker) { + if (!((marker.options.pane === 'markerPane') && marker.options.icon)) { + console.error('Layer isn\'t a marker') + return } - }) - L.canvasIconLayer = function(options) { - return new CanvasIconLayer(options) + var latlng = marker.getLatLng() + var isDisplaying = self._map.getBounds().contains(latlng) + var s = self._addMarker(marker, latlng, isDisplaying) + + // Only add to Point Lookup if we are on map + if (isDisplaying === true) tmpMark.push(s[0]) + + tmpLatLng.push(s[1]) + }) + + self._markers.load(tmpMark) + self._latlngMarkers.load(tmpLatLng) + }, + + // Adds single layer at a time. Less efficient for rBush + addMarker: function (marker) { + var self = this + var latlng = marker.getLatLng() + var isDisplaying = self._map.getBounds().contains(latlng) + var dat = self._addMarker(marker, latlng, isDisplaying) + + // Only add to Point Lookup if we are on map + if (isDisplaying === true) self._markers.insert(dat[0]) + + self._latlngMarkers.insert(dat[1]) + }, + + addLayer: function (layer) { + if ((layer.options.pane === 'markerPane') && layer.options.icon) this.addMarker(layer) + else console.error('Layer isn\'t a marker') + }, + + addLayers: function (layers) { + this.addMarkers(layers) + }, + + removeLayer: function (layer) { + this.removeMarker(layer, true) + }, + + removeMarker: function (marker, redraw) { + var self = this + + // If we are removed point + if (marker.minX) marker = marker.data + + var latlng = marker.getLatLng() + var isDisplaying = self._map.getBounds().contains(latlng) + + var markerData = { + + minX: latlng.lng, + minY: latlng.lat, + maxX: latlng.lng, + maxY: latlng.lat, + data: marker + } + + self._latlngMarkers.remove(markerData, function (a, b) { + return a.data._leaflet_id === b.data._leaflet_id + }) + + self._latlngMarkers.total-- + self._latlngMarkers.dirty++ + + if (isDisplaying === true && redraw === true) { + self._redraw(true) + } + }, + + onAdd: function (map) { + this._map = map + + if (!this._canvas) this._initCanvas() + + if (this.options.pane) this.getPane().appendChild(this._canvas) + else map._panes.overlayPane.appendChild(this._canvas) + + map.on('moveend', this._reset, this) + map.on('resize', this._reset, this) + + map.on('click', this._executeListeners, this) + map.on('mousemove', this._executeListeners, this) + map.on('zoomstart', this._canvasHide, this) + map.on('zoomend', this._canvasShow, this) + }, + + onRemove: function (map) { + if (this.options.pane) this.getPane().removeChild(this._canvas) + else map.getPanes().overlayPane.removeChild(this._canvas) + + map.off('click', this._executeListeners, this) + map.off('mousemove', this._executeListeners, this) + + map.off('moveend', this._reset, this) + map.off('resize', this._reset, this) + map.off('zoomstart', this._canvasHide, this) + map.off('zoomend', this._canvasShow, this) + }, + + addTo: function (map) { + map.addLayer(this) + return this + }, + + clearLayers: function () { + this._latlngMarkers = null + this._markers = null + this._redraw(true) + }, + _canvasHide: function () { + this._canvas.style.visibility = 'hidden' + }, + _canvasShow: function () { + this._canvas.style.visibility = 'visible' + }, + _addMarker: function (marker, latlng, isDisplaying) { + var self = this + // Needed for pop-up & tooltip to work. + marker._map = self._map + + // _markers contains Points of markers currently displaying on map + if (!self._markers) self._markers = new Rbush() + + // _latlngMarkers contains Lat\Long coordinates of all markers in layer. + if (!self._latlngMarkers) { + self._latlngMarkers = new Rbush() + self._latlngMarkers.dirty = 0 + self._latlngMarkers.total = 0 + } + + L.Util.stamp(marker) + + var pointPos = self._map.latLngToContainerPoint(latlng) + var iconSize = marker.options.icon.options.iconSize + + var adjX = iconSize[0] / 2 + var adjY = iconSize[1] / 2 + var ret = [({ + minX: (pointPos.x - adjX), + minY: (pointPos.y - adjY), + maxX: (pointPos.x + adjX), + maxY: (pointPos.y + adjY), + data: marker + }), ({ + minX: latlng.lng, + minY: latlng.lat, + maxX: latlng.lng, + maxY: latlng.lat, + data: marker + })] + + self._latlngMarkers.dirty++ + self._latlngMarkers.total++ + + // Only draw if we are on map + if (isDisplaying === true) self._drawMarker(marker, pointPos) + + return ret + }, + + _drawMarker: function (marker, pointPos) { + var self = this + + if (!this._imageLookup) this._imageLookup = {} + if (!pointPos) { + pointPos = self._map.latLngToContainerPoint(marker.getLatLng()) + } + + var iconUrl = marker.options.icon.options.iconUrl + + if (marker.canvas_img) { + self._drawImage(marker, pointPos) + } else { + if (self._imageLookup[iconUrl]) { + marker.canvas_img = self._imageLookup[iconUrl][0] + + if (self._imageLookup[iconUrl][1] === false) { + self._imageLookup[iconUrl][2].push([marker, pointPos]) + } else { + self._drawImage(marker, pointPos) + } + } else { + var i = new Image() + i.src = iconUrl + marker.canvas_img = i + + // Image,isLoaded,marker\pointPos ref + self._imageLookup[iconUrl] = [i, false, [[marker, pointPos]]] + + i.onload = function () { + self._imageLookup[iconUrl][1] = true + self._imageLookup[iconUrl][2].forEach(function (e) { + self._drawImage(e[0], e[1]) + }) + } + } + } + }, + + _drawImage: function (marker, pointPos) { + var options = marker.options.icon.options + + this._context.drawImage( + marker.canvas_img, + pointPos.x - options.iconAnchor[0], + pointPos.y - options.iconAnchor[1], + options.iconSize[0], + options.iconSize[1] + ) + }, + + _reset: function () { + var topLeft = this._map.containerPointToLayerPoint([0, 0]) + L.DomUtil.setPosition(this._canvas, topLeft) + + var size = this._map.getSize() + + this._canvas.width = size.x + this._canvas.height = size.y + + this._redraw() + }, + + _redraw: function (clear) { + var self = this + + if (clear) this._context.clearRect(0, 0, this._canvas.width, this._canvas.height) + if (!this._map || !this._latlngMarkers) return + + var tmp = [] + + // If we are 10% individual inserts\removals, reconstruct lookup for efficiency + if (self._latlngMarkers.dirty / self._latlngMarkers.total >= 0.1) { + self._latlngMarkers.all().forEach(function (e) { + tmp.push(e) + }) + + self._latlngMarkers.clear() + self._latlngMarkers.load(tmp) + self._latlngMarkers.dirty = 0 + tmp = [] + } + + var mapBounds = self._map.getBounds() + + // Only re-draw what we are showing on the map. + + var mapBoxCoords = { + + minX: mapBounds.getWest(), + minY: mapBounds.getSouth(), + maxX: mapBounds.getEast(), + maxY: mapBounds.getNorth() + } + + self._latlngMarkers.search(mapBoxCoords).forEach(function (e) { + // Readjust Point Map + var pointPos = self._map.latLngToContainerPoint(e.data.getLatLng()) + + var iconSize = e.data.options.icon.options.iconSize + var adjX = iconSize[0] / 2 + var adjY = iconSize[1] / 2 + + var newCoords = { + minX: (pointPos.x - adjX), + minY: (pointPos.y - adjY), + maxX: (pointPos.x + adjX), + maxY: (pointPos.y + adjY), + data: e.data + } + + tmp.push(newCoords) + + // Redraw points + self._drawMarker(e.data, pointPos) + }) + + // Clear rBush & Bulk Load for performance + this._markers.clear() + this._markers.load(tmp) + }, + + _initCanvas: function () { + this._canvas = L.DomUtil.create('canvas', 'leaflet-canvas-icon-layer leaflet-layer') + var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']) + this._canvas.style[originProp] = '50% 50%' + + var size = this._map.getSize() + this._canvas.width = size.x + this._canvas.height = size.y + + this._context = this._canvas.getContext('2d') + + var animated = this._map.options.zoomAnimation && L.Browser.any3d + L.DomUtil.addClass(this._canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide')) + }, + + addOnClickListener: function (listener) { + this._onClickListeners.push(listener) + }, + + addOnHoverListener: function (listener) { + this._onHoverListeners.push(listener) + }, + + _executeListeners: function (event) { + if (!this._markers) return + + var me = this + var x = event.containerPoint.x + var y = event.containerPoint.y + + if (me._openToolTip) { + me._openToolTip.closeTooltip() + delete me._openToolTip + } + + var ret = this._markers.search({ minX: x, minY: y, maxX: x, maxY: y }) + + if (ret && ret.length > 0) { + me._map._container.style.cursor = 'pointer' + + if (event.type === 'click') { + var hasPopup = ret[0].data.getPopup() + if (hasPopup) ret[0].data.openPopup() + + me._onClickListeners.forEach(function (listener) { listener(event, ret) }) + } + + if (event.type === 'mousemove') { + var hasTooltip = ret[0].data.getTooltip() + if (hasTooltip) { + me._openToolTip = ret[0].data + ret[0].data.openTooltip() + } + + me._onHoverListeners.forEach(function (listener) { listener(event, ret) }) + } + } else { + me._map._container.style.cursor = '' + } } + }) + + L.canvasIconLayer = function (options) { + return new CanvasIconLayer(options) + } } export default { - init + init } diff --git a/src/components/plugin/CustomPopup.js b/src/components/plugin/CustomPopup.js index 1ea6b84..a35e848 100644 --- a/src/components/plugin/CustomPopup.js +++ b/src/components/plugin/CustomPopup.js @@ -1,74 +1,74 @@ 'use strict' const init = (L) => { - L.CustomPopup = L.Popup.extend({ - _initLayout: () => { - // 姝ゅ鐢熸垚鐨勫鍣紝Leaflet浼氭牴鎹叾绫诲悕鑷姩閫傞厤Transform锛屽尮閰嶆牱寮忥紝鎵�浠ュ鏋滆鑷畾涔夌殑璇濓紝璇ラ儴鍒嗘牱寮忚鑷繁閲嶅啓锛屾垜杩欓噷鍙В鍐充簡鑷�傚簲瀹瑰櫒鐨勯棶棰橈紝浠ヤ笅閲囩敤鍘熺敓瀹瑰櫒锛屾墍浠ュ悗闈㈡垜浼氬姞涓婃牱寮忚鐩栥�� - let prefix = 'leaflet-popup' - let container = (this._container = L.DomUtil.create( - 'div', - prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-animated' - )) - let wrapper = container - this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper) - L.DomEvent.disableClickPropagation(wrapper) - .disableScrollPropagation(this._contentNode) - .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation) - this._tipContainer = L.DomUtil.create( - 'div', - prefix + '-tip-container', - container - ) - this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer) - }, - // 浣嶇疆鏇存柊 - _updatePosition: () => { - if (!this._map) { - return - } - let pos = this._map.latLngToLayerPoint(this._latlng) - let offset = L.point(this.options.offset) - let anchor = [0, 0] - if (this._zoomAnimated) { - setPosition(this._container, pos.add(anchor)) - } else { - offset = offset.add(pos).add(anchor) - } - let bottom = (this._containerBottom = -offset.y) - let left = (this._containerLeft = + L.CustomPopup = L.Popup.extend({ + _initLayout: () => { + // 姝ゅ鐢熸垚鐨勫鍣紝Leaflet浼氭牴鎹叾绫诲悕鑷姩閫傞厤Transform锛屽尮閰嶆牱寮忥紝鎵�浠ュ鏋滆鑷畾涔夌殑璇濓紝璇ラ儴鍒嗘牱寮忚鑷繁閲嶅啓锛屾垜杩欓噷鍙В鍐充簡鑷�傚簲瀹瑰櫒鐨勯棶棰橈紝浠ヤ笅閲囩敤鍘熺敓瀹瑰櫒锛屾墍浠ュ悗闈㈡垜浼氬姞涓婃牱寮忚鐩栥�� + const prefix = 'leaflet-popup' + const container = (this._container = L.DomUtil.create( + 'div', + prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-animated' + )) + const wrapper = container + this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper) + L.DomEvent.disableClickPropagation(wrapper) + .disableScrollPropagation(this._contentNode) + .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation) + this._tipContainer = L.DomUtil.create( + 'div', + prefix + '-tip-container', + container + ) + this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer) + }, + // 浣嶇疆鏇存柊 + _updatePosition: () => { + if (!this._map) { + return + } + const pos = this._map.latLngToLayerPoint(this._latlng) + let offset = L.point(this.options.offset) + const anchor = [0, 0] + if (this._zoomAnimated) { + setPosition(this._container, pos.add(anchor)) + } else { + offset = offset.add(pos).add(anchor) + } + const bottom = (this._containerBottom = -offset.y) + const left = (this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x) - // bottom position the popup in case the height of the popup changes (images loading etc) - this._container.style.bottom = bottom + 'px' - this._container.style.left = left + 'px' - }, - // 閲嶅啓灞傜骇鍙樺寲瑙﹀彂鏇存柊 - _animateZoom: (e) => { - let pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center) - let anchor = [0, 0] - setPosition(this._container, pos.add(anchor)) - } - }) + // bottom position the popup in case the height of the popup changes (images loading etc) + this._container.style.bottom = bottom + 'px' + this._container.style.left = left + 'px' + }, + // 閲嶅啓灞傜骇鍙樺寲瑙﹀彂鏇存柊 + _animateZoom: (e) => { + const pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center) + const anchor = [0, 0] + setPosition(this._container, pos.add(anchor)) + } + }) -// 閲嶅啓setTransform锛岀敱浜庝笉鍐嶅浐瀹氬搴︼紝鎵�浠ュ鍔爐ranslateX(-50%)姘村钩灞呬腑 - const setTransform = (el, offset, scale) => { - let pos = offset || new L.Point(0, 0) - el.style[L.DomUtil.TRANSFORM] = + // 閲嶅啓setTransform锛岀敱浜庝笉鍐嶅浐瀹氬搴︼紝鎵�浠ュ鍔爐ranslateX(-50%)姘村钩灞呬腑 + const setTransform = (el, offset, scale) => { + const pos = offset || new L.Point(0, 0) + el.style[L.DomUtil.TRANSFORM] = (L.Browser.ie3d - ? 'translate(' + pos.x + 'px,' + pos.y + 'px,0) translateX(-50%)' - : 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0) translateX(-50%)') + + ? 'translate(' + pos.x + 'px,' + pos.y + 'px,0) translateX(-50%)' + : 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0) translateX(-50%)') + (scale ? ' scale(' + scale + ')' : '') + } + // 鍥犱负閲嶅啓浜唖etTransform锛屾墍浠etPosition涔熻閲嶆柊鎸囧悜鏂规硶 + const setPosition = (el, point) => { + el._leaflet_pos = point + if (L.Browser.any3d) { + setTransform(el, point) + } else { + el.style.left = point.x + 'px' + el.style.top = point.y + 'px' } -// 鍥犱负閲嶅啓浜唖etTransform锛屾墍浠etPosition涔熻閲嶆柊鎸囧悜鏂规硶 - const setPosition = (el, point) => { - el._leaflet_pos = point - if (L.Browser.any3d) { - setTransform(el, point) - } else { - el.style.left = point.x + 'px' - el.style.top = point.y + 'px' - } - } + } } export default { - init + init } diff --git a/src/components/plugin/Editable.js b/src/components/plugin/Editable.js index c2ae865..d86dbc0 100644 --- a/src/components/plugin/Editable.js +++ b/src/components/plugin/Editable.js @@ -1,1963 +1,1962 @@ 'use strict' const init = (L) => { - (function(factory, window) { - // define an AMD module that relies on 'leaflet' - if (typeof define === 'function' && window.define.amd) { - window.define(['leaflet'], factory) + (function (factory, window) { + // define an AMD module that relies on 'leaflet' + if (typeof define === 'function' && window.define.amd) { + window.define(['leaflet'], factory) - // define a Common JS module that relies on 'leaflet' - } else if (typeof exports === 'object') { - module.exports = factory(require('leaflet')) + // define a Common JS module that relies on 'leaflet' + } else if (typeof exports === 'object') { + module.exports = factory(require('leaflet')) + } + + // attach your plugin to the global 'L' variable + if (typeof window !== 'undefined' && L) { + factory(L) + } + }(function (L) { + // 馃崅miniclass CancelableEvent (Event objects) + // 馃崅method cancel() + // Cancel any subsequent action. + + // 馃崅miniclass VertexEvent (Event objects) + // 馃崅property vertex: VertexMarker + // The vertex that fires the event. + + // 馃崅miniclass ShapeEvent (Event objects) + // 馃崅property shape: Array + // The shape (LatLngs array) subject of the action. + + // 馃崅miniclass CancelableVertexEvent (Event objects) + // 馃崅inherits VertexEvent + // 馃崅inherits CancelableEvent + + // 馃崅miniclass CancelableShapeEvent (Event objects) + // 馃崅inherits ShapeEvent + // 馃崅inherits CancelableEvent + + // 馃崅miniclass LayerEvent (Event objects) + // 馃崅property layer: object + // The Layer (Marker, Polyline鈥�) subject of the action. + + // 馃崅namespace Editable; 馃崅class Editable; 馃崅aka L.Editable + // Main edition handler. By default, it is attached to the map + // as `map.editTools` property. + // Leaflet.Editable is made to be fully extendable. You have three ways to customize + // the behaviour: using options, listening to events, or extending. + L.Editable = L.Evented.extend({ + + statics: { + FORWARD: 1, + BACKWARD: -1 + }, + + options: { + + // You can pass them when creating a map using the `editOptions` key. + // 馃崅option zIndex: int = 1000 + // The default zIndex of the editing tools. + zIndex: 1000, + + // 馃崅option polygonClass: class = L.Polygon + // Class to be used when creating a new Polygon. + polygonClass: L.Polygon, + + // 馃崅option polylineClass: class = L.Polyline + // Class to be used when creating a new Polyline. + polylineClass: L.Polyline, + + // 馃崅option markerClass: class = L.Marker + // Class to be used when creating a new Marker. + markerClass: L.Marker, + + // 馃崅option rectangleClass: class = L.Rectangle + // Class to be used when creating a new Rectangle. + rectangleClass: L.Rectangle, + + // 馃崅option circleClass: class = L.Circle + // Class to be used when creating a new Circle. + circleClass: L.Circle, + + // 馃崅option drawingCSSClass: string = 'leaflet-editable-drawing' + // CSS class to be added to the map container while drawing. + drawingCSSClass: 'leaflet-editable-drawing', + + // 馃崅option drawingCursor: const = 'crosshair' + // Cursor mode set to the map while drawing. + drawingCursor: 'crosshair', + + // 馃崅option editLayer: Layer = new L.LayerGroup() + // Layer used to store edit tools (vertex, line guide鈥�). + editLayer: undefined, + + // 馃崅option featuresLayer: Layer = new L.LayerGroup() + // Default layer used to store drawn features (Marker, Polyline鈥�). + featuresLayer: undefined, + + // 馃崅option polylineEditorClass: class = PolylineEditor + // Class to be used as Polyline editor. + polylineEditorClass: undefined, + + // 馃崅option polygonEditorClass: class = PolygonEditor + // Class to be used as Polygon editor. + polygonEditorClass: undefined, + + // 馃崅option markerEditorClass: class = MarkerEditor + // Class to be used as Marker editor. + markerEditorClass: undefined, + + // 馃崅option rectangleEditorClass: class = RectangleEditor + // Class to be used as Rectangle editor. + rectangleEditorClass: undefined, + + // 馃崅option circleEditorClass: class = CircleEditor + // Class to be used as Circle editor. + circleEditorClass: undefined, + + // 馃崅option lineGuideOptions: hash = {} + // Options to be passed to the line guides. + lineGuideOptions: {}, + + // 馃崅option skipMiddleMarkers: boolean = false + // Set this to true if you don't want middle markers. + skipMiddleMarkers: false + + }, + + initialize: function (map, options) { + L.setOptions(this, options) + this._lastZIndex = this.options.zIndex + this.map = map + this.editLayer = this.createEditLayer() + this.featuresLayer = this.createFeaturesLayer() + this.forwardLineGuide = this.createLineGuide() + this.backwardLineGuide = this.createLineGuide() + }, + + fireAndForward: function (type, e) { + e = e || {} + e.editTools = this + this.fire(type, e) + this.map.fire(type, e) + }, + + createLineGuide: function () { + const options = L.extend({ + dashArray: '5,10', + weight: 1, + interactive: false + }, this.options.lineGuideOptions) + return L.polyline([], options) + }, + + createVertexIcon: function (options) { + return L.Browser.mobile && L.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options) + }, + + createEditLayer: function () { + return this.options.editLayer || new L.LayerGroup().addTo(this.map) + }, + + createFeaturesLayer: function () { + return this.options.featuresLayer || new L.LayerGroup().addTo(this.map) + }, + + moveForwardLineGuide: function (latlng) { + if (this.forwardLineGuide._latlngs.length) { + this.forwardLineGuide._latlngs[1] = latlng + this.forwardLineGuide._bounds.extend(latlng) + this.forwardLineGuide.redraw() } + }, - // attach your plugin to the global 'L' variable - if (typeof window !== 'undefined' && L) { - factory(L) + moveBackwardLineGuide: function (latlng) { + if (this.backwardLineGuide._latlngs.length) { + this.backwardLineGuide._latlngs[1] = latlng + this.backwardLineGuide._bounds.extend(latlng) + this.backwardLineGuide.redraw() } - }(function(L) { - // 馃崅miniclass CancelableEvent (Event objects) - // 馃崅method cancel() - // Cancel any subsequent action. + }, - // 馃崅miniclass VertexEvent (Event objects) - // 馃崅property vertex: VertexMarker - // The vertex that fires the event. + anchorForwardLineGuide: function (latlng) { + this.forwardLineGuide._latlngs[0] = latlng + this.forwardLineGuide._bounds.extend(latlng) + this.forwardLineGuide.redraw() + }, - // 馃崅miniclass ShapeEvent (Event objects) - // 馃崅property shape: Array - // The shape (LatLngs array) subject of the action. + anchorBackwardLineGuide: function (latlng) { + this.backwardLineGuide._latlngs[0] = latlng + this.backwardLineGuide._bounds.extend(latlng) + this.backwardLineGuide.redraw() + }, - // 馃崅miniclass CancelableVertexEvent (Event objects) - // 馃崅inherits VertexEvent - // 馃崅inherits CancelableEvent + attachForwardLineGuide: function () { + this.editLayer.addLayer(this.forwardLineGuide) + }, - // 馃崅miniclass CancelableShapeEvent (Event objects) - // 馃崅inherits ShapeEvent - // 馃崅inherits CancelableEvent + attachBackwardLineGuide: function () { + this.editLayer.addLayer(this.backwardLineGuide) + }, - // 馃崅miniclass LayerEvent (Event objects) - // 馃崅property layer: object - // The Layer (Marker, Polyline鈥�) subject of the action. + detachForwardLineGuide: function () { + this.forwardLineGuide.setLatLngs([]) + this.editLayer.removeLayer(this.forwardLineGuide) + }, - // 馃崅namespace Editable; 馃崅class Editable; 馃崅aka L.Editable - // Main edition handler. By default, it is attached to the map - // as `map.editTools` property. - // Leaflet.Editable is made to be fully extendable. You have three ways to customize - // the behaviour: using options, listening to events, or extending. - L.Editable = L.Evented.extend({ + detachBackwardLineGuide: function () { + this.backwardLineGuide.setLatLngs([]) + this.editLayer.removeLayer(this.backwardLineGuide) + }, - statics: { - FORWARD: 1, - BACKWARD: -1 - }, - - options: { - - // You can pass them when creating a map using the `editOptions` key. - // 馃崅option zIndex: int = 1000 - // The default zIndex of the editing tools. - zIndex: 1000, - - // 馃崅option polygonClass: class = L.Polygon - // Class to be used when creating a new Polygon. - polygonClass: L.Polygon, - - // 馃崅option polylineClass: class = L.Polyline - // Class to be used when creating a new Polyline. - polylineClass: L.Polyline, - - // 馃崅option markerClass: class = L.Marker - // Class to be used when creating a new Marker. - markerClass: L.Marker, - - // 馃崅option rectangleClass: class = L.Rectangle - // Class to be used when creating a new Rectangle. - rectangleClass: L.Rectangle, - - // 馃崅option circleClass: class = L.Circle - // Class to be used when creating a new Circle. - circleClass: L.Circle, - - // 馃崅option drawingCSSClass: string = 'leaflet-editable-drawing' - // CSS class to be added to the map container while drawing. - drawingCSSClass: 'leaflet-editable-drawing', - - // 馃崅option drawingCursor: const = 'crosshair' - // Cursor mode set to the map while drawing. - drawingCursor: 'crosshair', - - // 馃崅option editLayer: Layer = new L.LayerGroup() - // Layer used to store edit tools (vertex, line guide鈥�). - editLayer: undefined, - - // 馃崅option featuresLayer: Layer = new L.LayerGroup() - // Default layer used to store drawn features (Marker, Polyline鈥�). - featuresLayer: undefined, - - // 馃崅option polylineEditorClass: class = PolylineEditor - // Class to be used as Polyline editor. - polylineEditorClass: undefined, - - // 馃崅option polygonEditorClass: class = PolygonEditor - // Class to be used as Polygon editor. - polygonEditorClass: undefined, - - // 馃崅option markerEditorClass: class = MarkerEditor - // Class to be used as Marker editor. - markerEditorClass: undefined, - - // 馃崅option rectangleEditorClass: class = RectangleEditor - // Class to be used as Rectangle editor. - rectangleEditorClass: undefined, - - // 馃崅option circleEditorClass: class = CircleEditor - // Class to be used as Circle editor. - circleEditorClass: undefined, - - // 馃崅option lineGuideOptions: hash = {} - // Options to be passed to the line guides. - lineGuideOptions: {}, - - // 馃崅option skipMiddleMarkers: boolean = false - // Set this to true if you don't want middle markers. - skipMiddleMarkers: false - - }, - - initialize: function(map, options) { - L.setOptions(this, options) - this._lastZIndex = this.options.zIndex - this.map = map - this.editLayer = this.createEditLayer() - this.featuresLayer = this.createFeaturesLayer() - this.forwardLineGuide = this.createLineGuide() - this.backwardLineGuide = this.createLineGuide() - }, - - fireAndForward: function(type, e) { - e = e || {} - e.editTools = this - this.fire(type, e) - this.map.fire(type, e) - }, - - createLineGuide: function() { - let options = L.extend({ - dashArray: '5,10', - weight: 1, - interactive: false - }, this.options.lineGuideOptions) - return L.polyline([], options) - }, - - createVertexIcon: function(options) { - return L.Browser.mobile && L.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options) - }, - - createEditLayer: function() { - return this.options.editLayer || new L.LayerGroup().addTo(this.map) - }, - - createFeaturesLayer: function() { - return this.options.featuresLayer || new L.LayerGroup().addTo(this.map) - }, - - moveForwardLineGuide: function(latlng) { - if (this.forwardLineGuide._latlngs.length) { - this.forwardLineGuide._latlngs[1] = latlng - this.forwardLineGuide._bounds.extend(latlng) - this.forwardLineGuide.redraw() - } - }, - - moveBackwardLineGuide: function(latlng) { - if (this.backwardLineGuide._latlngs.length) { - this.backwardLineGuide._latlngs[1] = latlng - this.backwardLineGuide._bounds.extend(latlng) - this.backwardLineGuide.redraw() - } - }, - - anchorForwardLineGuide: function(latlng) { - this.forwardLineGuide._latlngs[0] = latlng - this.forwardLineGuide._bounds.extend(latlng) - this.forwardLineGuide.redraw() - }, - - anchorBackwardLineGuide: function(latlng) { - this.backwardLineGuide._latlngs[0] = latlng - this.backwardLineGuide._bounds.extend(latlng) - this.backwardLineGuide.redraw() - }, - - attachForwardLineGuide: function() { - this.editLayer.addLayer(this.forwardLineGuide) - }, - - attachBackwardLineGuide: function() { - this.editLayer.addLayer(this.backwardLineGuide) - }, - - detachForwardLineGuide: function() { - this.forwardLineGuide.setLatLngs([]) - this.editLayer.removeLayer(this.forwardLineGuide) - }, - - detachBackwardLineGuide: function() { - this.backwardLineGuide.setLatLngs([]) - this.editLayer.removeLayer(this.backwardLineGuide) - }, - - blockEvents: function() { - // Hack: force map not to listen to other layers events while drawing. - if (!this._oldTargets) { - this._oldTargets = this.map._targets - this.map._targets = {} - } - }, - - unblockEvents: function() { - if (this._oldTargets) { - // Reset, but keep targets created while drawing. - this.map._targets = L.extend(this.map._targets, this._oldTargets) - delete this._oldTargets - } - }, - - registerForDrawing: function(editor) { - if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor) - this.blockEvents() - editor.reset() // Make sure editor tools still receive events. - this._drawingEditor = editor - this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor) - this.map.on('mousedown', this.onMousedown, this) - this.map.on('mouseup', this.onMouseup, this) - L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass) - this.defaultMapCursor = this.map._container.style.cursor - this.map._container.style.cursor = this.options.drawingCursor - }, - - unregisterForDrawing: function(editor) { - this.unblockEvents() - L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass) - this.map._container.style.cursor = this.defaultMapCursor - editor = editor || this._drawingEditor - if (!editor) return - this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor) - this.map.off('mousedown', this.onMousedown, this) - this.map.off('mouseup', this.onMouseup, this) - if (editor !== this._drawingEditor) return - delete this._drawingEditor - if (editor._drawing) editor.cancelDrawing() - }, - - onMousedown: function(e) { - if (e.originalEvent.which !== 1) return - this._mouseDown = e - this._drawingEditor.onDrawingMouseDown(e) - }, - - onMouseup: function(e) { - if (this._mouseDown) { - let editor = this._drawingEditor - let mouseDown = this._mouseDown - this._mouseDown = null - editor.onDrawingMouseUp(e) - if (this._drawingEditor !== editor) return // onDrawingMouseUp may call unregisterFromDrawing. - let origin = L.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY) - let distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin) - if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e) - } - }, - - // 馃崅section Public methods - // You will generally access them by the `map.editTools` - // instance: - // - // `map.editTools.startPolyline();` - - // 馃崅method drawing(): boolean - // Return true if any drawing action is ongoing. - drawing: function() { - return this._drawingEditor && this._drawingEditor.drawing() - }, - - // 馃崅method stopDrawing() - // When you need to stop any ongoing drawing, without needing to know which editor is active. - stopDrawing: function() { - this.unregisterForDrawing() - }, - - // 馃崅method commitDrawing() - // When you need to commit any ongoing drawing, without needing to know which editor is active. - commitDrawing: function(e) { - if (!this._drawingEditor) return - this._drawingEditor.commitDrawing(e) - }, - - connectCreatedToMap: function(layer) { - return this.featuresLayer.addLayer(layer) - }, - - // 馃崅method startPolyline(latlng: L.LatLng, options: hash): L.Polyline - // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click. - // If `options` is given, it will be passed to the Polyline class constructor. - startPolyline: function(latlng, options) { - let line = this.createPolyline([], options) - line.enableEdit(this.map).newShape(latlng) - return line - }, - - // 馃崅method startPolygon(latlng: L.LatLng, options: hash): L.Polygon - // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click. - // If `options` is given, it will be passed to the Polygon class constructor. - startPolygon: function(latlng, options) { - let polygon = this.createPolygon([], options) - polygon.enableEdit(this.map).newShape(latlng) - return polygon - }, - - // 馃崅method startMarker(latlng: L.LatLng, options: hash): L.Marker - // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point. - // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch). - // If `options` is given, it will be passed to the Marker class constructor. - startMarker: function(latlng, options) { - latlng = latlng || this.map.getCenter().clone() - let marker = this.createMarker(latlng, options) - marker.enableEdit(this.map).startDrawing() - return marker - }, - - // 馃崅method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle - // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag. - // If `options` is given, it will be passed to the Rectangle class constructor. - startRectangle: function(latlng, options) { - let corner = latlng || L.latLng([0, 0]) - let bounds = new L.LatLngBounds(corner, corner) - let rectangle = this.createRectangle(bounds, options) - rectangle.enableEdit(this.map).startDrawing() - return rectangle - }, - - // 馃崅method startCircle(latlng: L.LatLng, options: hash): L.Circle - // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag. - // If `options` is given, it will be passed to the Circle class constructor. - startCircle: function(latlng, options) { - latlng = latlng || this.map.getCenter().clone() - let circle = this.createCircle(latlng, options) - circle.enableEdit(this.map).startDrawing() - return circle - }, - - startHole: function(editor, latlng) { - editor.newHole(latlng) - }, - - createLayer: function(Klass, latlngs, options) { - options = L.Util.extend({ editOptions: { editTools: this } }, options) - let layer = new Klass(latlngs, options) - // 馃崅namespace Editable - // 馃崅event editable:created: LayerEvent - // Fired when a new feature (Marker, Polyline鈥�) is created. - this.fireAndForward('editable:created', { layer: layer }) - return layer - }, - - createPolyline: function(latlngs, options) { - return this.createLayer((options && options.polylineClass) || this.options.polylineClass, latlngs, options) - }, - - createPolygon: function(latlngs, options) { - return this.createLayer((options && options.polygonClass) || this.options.polygonClass, latlngs, options) - }, - - createMarker: function(latlng, options) { - return this.createLayer((options && options.markerClass) || this.options.markerClass, latlng, options) - }, - - createRectangle: function(bounds, options) { - return this.createLayer((options && options.rectangleClass) || this.options.rectangleClass, bounds, options) - }, - - createCircle: function(latlng, options) { - return this.createLayer((options && options.circleClass) || this.options.circleClass, latlng, options) - } - - }) - - L.extend(L.Editable, { - - makeCancellable: function(e) { - e.cancel = function() { - e._cancelled = true - } - } - - }) - - // 馃崅namespace Map; 馃崅class Map - // Leaflet.Editable add options and events to the `L.Map` object. - // See `Editable` events for the list of events fired on the Map. - // 馃崅example - // - // ```js - // let map = L.map('map', { - // editable: true, - // editOptions: { - // 鈥� - // } - // }); - // ``` - // 馃崅section Editable Map Options - L.Map.mergeOptions({ - - // 馃崅namespace Map - // 馃崅section Map Options - // 馃崅option EditToolsClass: class = L.Editable - // Class to be used as vertex, for path editing. - EditToolsClass: L.Editable, - - // 馃崅option editable: boolean = false - // Whether to create a L.Editable instance at map init. - editable: false, - - // 馃崅option editOptions: hash = {} - // Options to pass to L.Editable when instantiating. - editOptions: {} - - }) - - L.Map.addInitHook(function() { - this.whenReady(function() { - if (this.options.editable) { - this.editTools = new this.options.EditToolsClass(this, this.options.editOptions) - } - }) - }) - - L.Editable.VertexIcon = L.DivIcon.extend({ - - options: { - iconSize: new L.Point(8, 8) - } - - }) - - L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({ - - options: { - iconSize: new L.Point(20, 20) - } - - }) - - // 馃崅namespace Editable; 馃崅class VertexMarker; Handler for dragging path vertices. - L.Editable.VertexMarker = L.Marker.extend({ - - options: { - draggable: true, - className: 'leaflet-vertex-icon leaflet-custom-icon' - }, - - // 馃崅section Public methods - // The marker used to handle path vertex. You will usually interact with a `VertexMarker` - // instance when listening for events like `editable:vertex:ctrlclick`. - - initialize: function(latlng, latlngs, editor, options) { - // We don't use this._latlng, because on drag Leaflet replace it while - // we want to keep reference. - this.latlng = latlng - this.latlngs = latlngs - this.editor = editor - L.Marker.prototype.initialize.call(this, latlng, options) - this.options.icon = this.editor.tools.createVertexIcon({ className: this.options.className }) - this.latlng.__vertex = this - this.editor.editLayer.addLayer(this) - this.setZIndexOffset(editor.tools._lastZIndex + 1) - }, - - onAdd: function(map) { - L.Marker.prototype.onAdd.call(this, map) - this.on('drag', this.onDrag) - this.on('dragstart', this.onDragStart) - this.on('dragend', this.onDragEnd) - this.on('mouseup', this.onMouseup) - this.on('click', this.onClick) - this.on('contextmenu', this.onContextMenu) - this.on('mousedown touchstart', this.onMouseDown) - this.on('mouseover', this.onMouseOver) - this.on('mouseout', this.onMouseOut) - this.addMiddleMarkers() - }, - - onRemove: function(map) { - if (this.middleMarker) this.middleMarker.delete() - delete this.latlng.__vertex - this.off('drag', this.onDrag) - this.off('dragstart', this.onDragStart) - this.off('dragend', this.onDragEnd) - this.off('mouseup', this.onMouseup) - this.off('click', this.onClick) - this.off('contextmenu', this.onContextMenu) - this.off('mousedown touchstart', this.onMouseDown) - this.off('mouseover', this.onMouseOver) - this.off('mouseout', this.onMouseOut) - L.Marker.prototype.onRemove.call(this, map) - }, - - onDrag: function(e) { - e.vertex = this - this.editor.onVertexMarkerDrag(e) - let iconPos = L.DomUtil.getPosition(this._icon) - let latlng = this._map.layerPointToLatLng(iconPos) - this.latlng.update(latlng) - this._latlng = this.latlng // Push back to Leaflet our reference. - this.editor.refresh() - if (this.middleMarker) this.middleMarker.updateLatLng() - let next = this.getNext() - if (next && next.middleMarker) next.middleMarker.updateLatLng() - }, - - onDragStart: function(e) { - e.vertex = this - this.editor.onVertexMarkerDragStart(e) - }, - - onDragEnd: function(e) { - e.vertex = this - this.editor.onVertexMarkerDragEnd(e) - }, - - onClick: function(e) { - e.vertex = this - this.editor.onVertexMarkerClick(e) - }, - - onMouseup: function(e) { - L.DomEvent.stop(e) - e.vertex = this - this.editor.map.fire('mouseup', e) - }, - - onContextMenu: function(e) { - e.vertex = this - this.editor.onVertexMarkerContextMenu(e) - }, - - onMouseDown: function(e) { - e.vertex = this - this.editor.onVertexMarkerMouseDown(e) - }, - - onMouseOver: function(e) { - e.vertex = this - this.editor.onVertexMarkerMouseOver(e) - }, - - onMouseOut: function(e) { - e.vertex = this - this.editor.onVertexMarkerMouseOut(e) - }, - - // 馃崅method delete() - // Delete a vertex and the related LatLng. - delete: function() { - let next = this.getNext() // Compute before changing latlng - this.latlngs.splice(this.getIndex(), 1) - this.editor.editLayer.removeLayer(this) - this.editor.onVertexDeleted({ latlng: this.latlng, vertex: this }) - if (!this.latlngs.length) this.editor.deleteShape(this.latlngs) - if (next) next.resetMiddleMarker() - this.editor.refresh() - }, - - // 馃崅method getIndex(): int - // Get the index of the current vertex among others of the same LatLngs group. - getIndex: function() { - return this.latlngs.indexOf(this.latlng) - }, - - // 馃崅method getLastIndex(): int - // Get last vertex index of the LatLngs group of the current vertex. - getLastIndex: function() { - return this.latlngs.length - 1 - }, - - // 馃崅method getPrevious(): VertexMarker - // Get the previous VertexMarker in the same LatLngs group. - getPrevious: function() { - if (this.latlngs.length < 2) return - let index = this.getIndex() - let previousIndex = index - 1 - if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex() - let previous = this.latlngs[previousIndex] - if (previous) return previous.__vertex - }, - - // 馃崅method getNext(): VertexMarker - // Get the next VertexMarker in the same LatLngs group. - getNext: function() { - if (this.latlngs.length < 2) return - let index = this.getIndex() - let nextIndex = index + 1 - if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0 - let next = this.latlngs[nextIndex] - if (next) return next.__vertex - }, - - addMiddleMarker: function(previous) { - if (!this.editor.hasMiddleMarkers()) return - previous = previous || this.getPrevious() - if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor) - }, - - addMiddleMarkers: function() { - if (!this.editor.hasMiddleMarkers()) return - let previous = this.getPrevious() - if (previous) this.addMiddleMarker(previous) - let next = this.getNext() - if (next) next.resetMiddleMarker() - }, - - resetMiddleMarker: function() { - if (this.middleMarker) this.middleMarker.delete() - this.addMiddleMarker() - }, - - // 馃崅method split() - // Split the vertex LatLngs group at its index, if possible. - split: function() { - if (!this.editor.splitShape) return // Only for PolylineEditor - this.editor.splitShape(this.latlngs, this.getIndex()) - }, - - // 馃崅method continue() - // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline. - continue: function() { - if (!this.editor.continueBackward) return // Only for PolylineEditor - let index = this.getIndex() - if (index === 0) this.editor.continueBackward(this.latlngs) - else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs) - } - - }) - - L.Editable.mergeOptions({ - - // 馃崅namespace Editable - // 馃崅option VertexMarkerClass: class = VertexMarker - // Class to be used as vertex, for path editing. - VertexMarkerClass: L.Editable.VertexMarker - - }) - - L.Editable.MiddleMarker = L.Marker.extend({ - - options: { - opacity: 0.5, - className: 'leaflet-div-icon leaflet-middle-icon', - draggable: true - }, - - initialize: function(left, right, latlngs, editor, options) { - this.left = left - this.right = right - this.editor = editor - this.latlngs = latlngs - L.Marker.prototype.initialize.call(this, this.computeLatLng(), options) - this._opacity = this.options.opacity - this.options.icon = this.editor.tools.createVertexIcon({ className: this.options.className }) - this.editor.editLayer.addLayer(this) - this.setVisibility() - }, - - setVisibility: function() { - let leftPoint = this._map.latLngToContainerPoint(this.left.latlng) - let rightPoint = this._map.latLngToContainerPoint(this.right.latlng) - let size = L.point(this.options.icon.options.iconSize) - if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide() - else this.show() - }, - - show: function() { - this.setOpacity(this._opacity) - }, - - hide: function() { - this.setOpacity(0) - }, - - updateLatLng: function() { - this.setLatLng(this.computeLatLng()) - this.setVisibility() - }, - - computeLatLng: function() { - let leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng) - let rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng) - let y = (leftPoint.y + rightPoint.y) / 2 - let x = (leftPoint.x + rightPoint.x) / 2 - return this.editor.map.containerPointToLatLng([x, y]) - }, - - onAdd: function(map) { - L.Marker.prototype.onAdd.call(this, map) - L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this) - map.on('zoomend', this.setVisibility, this) - }, - - onRemove: function(map) { - delete this.right.middleMarker - L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this) - map.off('zoomend', this.setVisibility, this) - L.Marker.prototype.onRemove.call(this, map) - }, - - onMouseDown: function(e) { - let iconPos = L.DomUtil.getPosition(this._icon) - let latlng = this.editor.map.layerPointToLatLng(iconPos) - e = { - originalEvent: e, - latlng: latlng - } - if (this.options.opacity === 0) return - L.Editable.makeCancellable(e) - this.editor.onMiddleMarkerMouseDown(e) - if (e._cancelled) return - this.latlngs.splice(this.index(), 0, e.latlng) - this.editor.refresh() - let icon = this._icon - let marker = this.editor.addVertexMarker(e.latlng, this.latlngs) - this.editor.onNewVertex(marker) - /* Hack to workaround browser not firing touchend when element is no more on DOM */ - let parent = marker._icon.parentNode - parent.removeChild(marker._icon) - marker._icon = icon - parent.appendChild(marker._icon) - marker._initIcon() - marker._initInteraction() - marker.setOpacity(1) - /* End hack */ - // Transfer ongoing dragging to real marker - L.Draggable._dragging = false - marker.dragging._draggable._onDown(e.originalEvent) - this.delete() - }, - - delete: function() { - this.editor.editLayer.removeLayer(this) - }, - - index: function() { - return this.latlngs.indexOf(this.right.latlng) - } - - }) - - L.Editable.mergeOptions({ - - // 馃崅namespace Editable - // 馃崅option MiddleMarkerClass: class = VertexMarker - // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path. - MiddleMarkerClass: L.Editable.MiddleMarker - - }) - - // 馃崅namespace Editable; 馃崅class BaseEditor; 馃崅aka L.Editable.BaseEditor - // When editing a feature (Marker, Polyline鈥�), an editor is attached to it. This - // editor basically knows how to handle the edition. - L.Editable.BaseEditor = L.Handler.extend({ - - initialize: function(map, feature, options) { - L.setOptions(this, options) - this.map = map - this.feature = feature - this.feature.editor = this - this.editLayer = new L.LayerGroup() - this.tools = this.options.editTools || map.editTools - }, - - // 馃崅method enable(): this - // Set up the drawing tools for the feature to be editable. - addHooks: function() { - if (this.isConnected()) this.onFeatureAdd() - else this.feature.once('add', this.onFeatureAdd, this) - this.onEnable() - this.feature.on(this._getEvents(), this) - }, - - // 馃崅method disable(): this - // Remove the drawing tools for the feature. - removeHooks: function() { - this.feature.off(this._getEvents(), this) - if (this.feature.dragging) this.feature.dragging.disable() - this.editLayer.clearLayers() - this.tools.editLayer.removeLayer(this.editLayer) - this.onDisable() - if (this._drawing) this.cancelDrawing() - }, - - // 馃崅method drawing(): boolean - // Return true if any drawing action is ongoing with this editor. - drawing: function() { - return !!this._drawing - }, - - reset: function() { - }, - - onFeatureAdd: function() { - this.tools.editLayer.addLayer(this.editLayer) - if (this.feature.dragging) this.feature.dragging.enable() - }, - - hasMiddleMarkers: function() { - return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers - }, - - fireAndForward: function(type, e) { - e = e || {} - e.layer = this.feature - this.feature.fire(type, e) - this.tools.fireAndForward(type, e) - }, - - onEnable: function() { - // 馃崅namespace Editable - // 馃崅event editable:enable: Event - // Fired when an existing feature is ready to be edited. - this.fireAndForward('editable:enable') - }, - - onDisable: function() { - // 馃崅namespace Editable - // 馃崅event editable:disable: Event - // Fired when an existing feature is not ready anymore to be edited. - this.fireAndForward('editable:disable') - }, - - onEditing: function() { - // 馃崅namespace Editable - // 馃崅event editable:editing: Event - // Fired as soon as any change is made to the feature geometry. - this.fireAndForward('editable:editing') - }, - - onStartDrawing: function() { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:start: Event - // Fired when a feature is to be drawn. - this.fireAndForward('editable:drawing:start') - }, - - onEndDrawing: function() { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:end: Event - // Fired when a feature is not drawn anymore. - this.fireAndForward('editable:drawing:end') - }, - - onCancelDrawing: function() { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:cancel: Event - // Fired when user cancel drawing while a feature is being drawn. - this.fireAndForward('editable:drawing:cancel') - }, - - onCommitDrawing: function(e) { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:commit: Event - // Fired when user finish drawing a feature. - this.fireAndForward('editable:drawing:commit', e) - }, - - onDrawingMouseDown: function(e) { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:mousedown: Event - // Fired when user `mousedown` while drawing. - this.fireAndForward('editable:drawing:mousedown', e) - }, - - onDrawingMouseUp: function(e) { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:mouseup: Event - // Fired when user `mouseup` while drawing. - this.fireAndForward('editable:drawing:mouseup', e) - }, - - startDrawing: function() { - if (!this._drawing) this._drawing = L.Editable.FORWARD - this.tools.registerForDrawing(this) - this.onStartDrawing() - }, - - commitDrawing: function(e) { - this.onCommitDrawing(e) - this.endDrawing() - }, - - cancelDrawing: function() { - // If called during a vertex drag, the vertex will be removed before - // the mouseup fires on it. This is a workaround. Maybe better fix is - // To have L.Draggable reset it's status on disable (Leaflet side). - L.Draggable._dragging = false - this.onCancelDrawing() - this.endDrawing() - }, - - endDrawing: function() { - this._drawing = false - this.tools.unregisterForDrawing(this) - this.onEndDrawing() - }, - - onDrawingClick: function(e) { - if (!this.drawing()) return - L.Editable.makeCancellable(e) - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:click: CancelableEvent - // Fired when user `click` while drawing, before any internal action is being processed. - this.fireAndForward('editable:drawing:click', e) - if (e._cancelled) return - if (!this.isConnected()) this.connect(e) - this.processDrawingClick(e) - }, - - isConnected: function() { - return this.map.hasLayer(this.feature) - }, - - connect: function() { - this.tools.connectCreatedToMap(this.feature) - this.tools.editLayer.addLayer(this.editLayer) - }, - - onMove: function(e) { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:move: Event - // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex. - this.fireAndForward('editable:drawing:move', e) - }, - - onDrawingMouseMove: function(e) { - this.onMove(e) - }, - - _getEvents: function() { - return { - dragstart: this.onDragStart, - drag: this.onDrag, - dragend: this.onDragEnd, - remove: this.disable - } - }, - - onDragStart: function(e) { - this.onEditing() - // 馃崅namespace Editable - // 馃崅event editable:dragstart: Event - // Fired before a path feature is dragged. - this.fireAndForward('editable:dragstart', e) - }, - - onDrag: function(e) { - this.onMove(e) - // 馃崅namespace Editable - // 馃崅event editable:drag: Event - // Fired when a path feature is being dragged. - this.fireAndForward('editable:drag', e) - }, - - onDragEnd: function(e) { - // 馃崅namespace Editable - // 馃崅event editable:dragend: Event - // Fired after a path feature has been dragged. - this.fireAndForward('editable:dragend', e) - } - - }) - - // 馃崅namespace Editable; 馃崅class MarkerEditor; 馃崅aka L.Editable.MarkerEditor - // 馃崅inherits BaseEditor - // Editor for Marker. - L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({ - - onDrawingMouseMove: function(e) { - L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) - if (this._drawing) this.feature.setLatLng(e.latlng) - }, - - processDrawingClick: function(e) { - // 馃崅namespace Editable - // 馃崅section Drawing events - // 馃崅event editable:drawing:clicked: Event - // Fired when user `click` while drawing, after all internal actions. - this.fireAndForward('editable:drawing:clicked', e) - this.commitDrawing(e) - }, - - connect: function(e) { - // On touch, the latlng has not been updated because there is - // no mousemove. - if (e) this.feature._latlng = e.latlng - L.Editable.BaseEditor.prototype.connect.call(this, e) - } - - }) - - // 馃崅namespace Editable; 馃崅class PathEditor; 馃崅aka L.Editable.PathEditor - // 馃崅inherits BaseEditor - // Base class for all path editors. - L.Editable.PathEditor = L.Editable.BaseEditor.extend({ - - CLOSED: false, - MIN_VERTEX: 2, - - addHooks: function() { - L.Editable.BaseEditor.prototype.addHooks.call(this) - if (this.feature) this.initVertexMarkers() - return this - }, - - initVertexMarkers: function(latlngs) { - if (!this.enabled()) return - latlngs = latlngs || this.getLatLngs() - if (isFlat(latlngs)) this.addVertexMarkers(latlngs) - else for (let i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]) - }, - - getLatLngs: function() { - return this.feature.getLatLngs() - }, - - // 馃崅method reset() - // Rebuild edit elements (Vertex, MiddleMarker, etc.). - reset: function() { - this.editLayer.clearLayers() - this.initVertexMarkers() - }, - - addVertexMarker: function(latlng, latlngs) { - return new this.tools.options.VertexMarkerClass(latlng, latlngs, this) - }, - - onNewVertex: function(vertex) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:new: VertexEvent - // Fired when a new vertex is created. - this.fireAndForward('editable:vertex:new', { latlng: vertex.latlng, vertex: vertex }) - }, - - addVertexMarkers: function(latlngs) { - for (let i = 0; i < latlngs.length; i++) { - this.addVertexMarker(latlngs[i], latlngs) - } - }, - - refreshVertexMarkers: function(latlngs) { - latlngs = latlngs || this.getDefaultLatLngs() - for (let i = 0; i < latlngs.length; i++) { - latlngs[i].__vertex.update() - } - }, - - addMiddleMarker: function(left, right, latlngs) { - return new this.tools.options.MiddleMarkerClass(left, right, latlngs, this) - }, - - onVertexMarkerClick: function(e) { - L.Editable.makeCancellable(e) - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:click: CancelableVertexEvent - // Fired when a `click` is issued on a vertex, before any internal action is being processed. - this.fireAndForward('editable:vertex:click', e) - if (e._cancelled) return - if (this.tools.drawing() && this.tools._drawingEditor !== this) return - let index = e.vertex.getIndex() - let commit - if (e.originalEvent.ctrlKey) { - this.onVertexMarkerCtrlClick(e) - } else if (e.originalEvent.altKey) { - this.onVertexMarkerAltClick(e) - } else if (e.originalEvent.shiftKey) { - this.onVertexMarkerShiftClick(e) - } else if (e.originalEvent.metaKey) { - this.onVertexMarkerMetaKeyClick(e) - } else if (index === e.vertex.getLastIndex() && this._drawing === L.Editable.FORWARD) { - if (index >= this.MIN_VERTEX - 1) commit = true - } else if (index === 0 && this._drawing === L.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) { - commit = true - } else if (index === 0 && this._drawing === L.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) { - commit = true // Allow to close on first point also for polygons - } else { - this.onVertexRawMarkerClick(e) - } - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:clicked: VertexEvent - // Fired when a `click` is issued on a vertex, after all internal actions. - this.fireAndForward('editable:vertex:clicked', e) - if (commit) this.commitDrawing(e) - }, - - onVertexRawMarkerClick: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:rawclick: CancelableVertexEvent - // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode. - this.fireAndForward('editable:vertex:rawclick', e) - if (e._cancelled) return - if (!this.vertexCanBeDeleted(e.vertex)) return - e.vertex.delete() - }, - - vertexCanBeDeleted: function(vertex) { - return vertex.latlngs.length > this.MIN_VERTEX - }, - - onVertexDeleted: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:deleted: VertexEvent - // Fired after a vertex has been deleted by user. - this.fireAndForward('editable:vertex:deleted', e) - }, - - onVertexMarkerCtrlClick: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:ctrlclick: VertexEvent - // Fired when a `click` with `ctrlKey` is issued on a vertex. - this.fireAndForward('editable:vertex:ctrlclick', e) - }, - - onVertexMarkerShiftClick: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:shiftclick: VertexEvent - // Fired when a `click` with `shiftKey` is issued on a vertex. - this.fireAndForward('editable:vertex:shiftclick', e) - }, - - onVertexMarkerMetaKeyClick: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:metakeyclick: VertexEvent - // Fired when a `click` with `metaKey` is issued on a vertex. - this.fireAndForward('editable:vertex:metakeyclick', e) - }, - - onVertexMarkerAltClick: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:altclick: VertexEvent - // Fired when a `click` with `altKey` is issued on a vertex. - this.fireAndForward('editable:vertex:altclick', e) - }, - - onVertexMarkerContextMenu: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:contextmenu: VertexEvent - // Fired when a `contextmenu` is issued on a vertex. - this.fireAndForward('editable:vertex:contextmenu', e) - }, - - onVertexMarkerMouseDown: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:mousedown: VertexEvent - // Fired when user `mousedown` a vertex. - this.fireAndForward('editable:vertex:mousedown', e) - }, - - onVertexMarkerMouseOver: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:mouseover: VertexEvent - // Fired when a user's mouse enters the vertex - this.fireAndForward('editable:vertex:mouseover', e) - }, - - onVertexMarkerMouseOut: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:mouseout: VertexEvent - // Fired when a user's mouse leaves the vertex - this.fireAndForward('editable:vertex:mouseout', e) - }, - - onMiddleMarkerMouseDown: function(e) { - // 馃崅namespace Editable - // 馃崅section MiddleMarker events - // 馃崅event editable:middlemarker:mousedown: VertexEvent - // Fired when user `mousedown` a middle marker. - this.fireAndForward('editable:middlemarker:mousedown', e) - }, - - onVertexMarkerDrag: function(e) { - this.onMove(e) - if (this.feature._bounds) this.extendBounds(e) - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:drag: VertexEvent - // Fired when a vertex is dragged by user. - this.fireAndForward('editable:vertex:drag', e) - }, - - onVertexMarkerDragStart: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:dragstart: VertexEvent - // Fired before a vertex is dragged by user. - this.fireAndForward('editable:vertex:dragstart', e) - }, - - onVertexMarkerDragEnd: function(e) { - // 馃崅namespace Editable - // 馃崅section Vertex events - // 馃崅event editable:vertex:dragend: VertexEvent - // Fired after a vertex is dragged by user. - this.fireAndForward('editable:vertex:dragend', e) - }, - - setDrawnLatLngs: function(latlngs) { - this._drawnLatLngs = latlngs || this.getDefaultLatLngs() - }, - - startDrawing: function() { - if (!this._drawnLatLngs) this.setDrawnLatLngs() - L.Editable.BaseEditor.prototype.startDrawing.call(this) - }, - - startDrawingForward: function() { - this.startDrawing() - }, - - endDrawing: function() { - this.tools.detachForwardLineGuide() - this.tools.detachBackwardLineGuide() - if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs) - L.Editable.BaseEditor.prototype.endDrawing.call(this) - delete this._drawnLatLngs - }, - - addLatLng: function(latlng) { - if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng) - else this._drawnLatLngs.unshift(latlng) - this.feature._bounds.extend(latlng) - let vertex = this.addVertexMarker(latlng, this._drawnLatLngs) - this.onNewVertex(vertex) - this.refresh() - }, - - newPointForward: function(latlng) { - this.addLatLng(latlng) - this.tools.attachForwardLineGuide() - this.tools.anchorForwardLineGuide(latlng) - }, - - newPointBackward: function(latlng) { - this.addLatLng(latlng) - this.tools.anchorBackwardLineGuide(latlng) - }, - - // 馃崅namespace PathEditor - // 馃崅method push() - // Programmatically add a point while drawing. - push: function(latlng) { - if (!latlng) return console.error('L.Editable.PathEditor.push expect a valid latlng as parameter') - if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng) - else this.newPointBackward(latlng) - }, - - removeLatLng: function(latlng) { - latlng.__vertex.delete() - this.refresh() - }, - - // 馃崅method pop(): L.LatLng or null - // Programmatically remove last point (if any) while drawing. - pop: function() { - if (this._drawnLatLngs.length <= 1) return - let latlng - if (this._drawing === L.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1] - else latlng = this._drawnLatLngs[0] - this.removeLatLng(latlng) - if (this._drawing === L.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]) - else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]) - return latlng - }, - - processDrawingClick: function(e) { - if (e.vertex && e.vertex.editor === this) return - if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng) - else this.newPointBackward(e.latlng) - this.fireAndForward('editable:drawing:clicked', e) - }, - - onDrawingMouseMove: function(e) { - L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) - if (this._drawing) { - this.tools.moveForwardLineGuide(e.latlng) - this.tools.moveBackwardLineGuide(e.latlng) - } - }, - - refresh: function() { - this.feature.redraw() - this.onEditing() - }, - - // 馃崅namespace PathEditor - // 馃崅method newShape(latlng?: L.LatLng) - // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it; - // if optional `latlng` is given, start a path at this point. - newShape: function(latlng) { - let shape = this.addNewEmptyShape() - if (!shape) return - this.setDrawnLatLngs(shape[0] || shape) // Polygon or polyline - this.startDrawingForward() - // 馃崅namespace Editable - // 馃崅section Shape events - // 馃崅event editable:shape:new: ShapeEvent - // Fired when a new shape is created in a multi (Polygon or Polyline). - this.fireAndForward('editable:shape:new', { shape: shape }) - if (latlng) this.newPointForward(latlng) - }, - - deleteShape: function(shape, latlngs) { - let e = { shape: shape } - L.Editable.makeCancellable(e) - // 馃崅namespace Editable - // 馃崅section Shape events - // 馃崅event editable:shape:delete: CancelableShapeEvent - // Fired before a new shape is deleted in a multi (Polygon or Polyline). - this.fireAndForward('editable:shape:delete', e) - if (e._cancelled) return - shape = this._deleteShape(shape, latlngs) - if (this.ensureNotFlat) this.ensureNotFlat() // Polygon. - this.feature.setLatLngs(this.getLatLngs()) // Force bounds reset. - this.refresh() - this.reset() - // 馃崅namespace Editable - // 馃崅section Shape events - // 馃崅event editable:shape:deleted: ShapeEvent - // Fired after a new shape is deleted in a multi (Polygon or Polyline). - this.fireAndForward('editable:shape:deleted', { shape: shape }) - return shape - }, - - _deleteShape: function(shape, latlngs) { - latlngs = latlngs || this.getLatLngs() - if (!latlngs.length) return - let self = this - let inplaceDelete = function(latlngs, shape) { - // Called when deleting a flat latlngs - shape = latlngs.splice(0, Number.MAX_VALUE) - return shape - } - let spliceDelete = function(latlngs, shape) { - // Called when removing a latlngs inside an array - latlngs.splice(latlngs.indexOf(shape), 1) - if (!latlngs.length) self._deleteShape(latlngs) - return shape - } - if (latlngs === shape) return inplaceDelete(latlngs, shape) - for (let i = 0; i < latlngs.length; i++) { - if (latlngs[i] === shape) return spliceDelete(latlngs, shape) - else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape) - } - }, - - // 馃崅namespace PathEditor - // 馃崅method deleteShapeAt(latlng: L.LatLng): Array - // Remove a path shape at the given `latlng`. - deleteShapeAt: function(latlng) { - let shape = this.feature.shapeAt(latlng) - if (shape) return this.deleteShape(shape) - }, - - // 馃崅method appendShape(shape: Array) - // Append a new shape to the Polygon or Polyline. - appendShape: function(shape) { - this.insertShape(shape) - }, - - // 馃崅method prependShape(shape: Array) - // Prepend a new shape to the Polygon or Polyline. - prependShape: function(shape) { - this.insertShape(shape, 0) - }, - - // 馃崅method insertShape(shape: Array, index: int) - // Insert a new shape to the Polygon or Polyline at given index (default is to append). - insertShape: function(shape, index) { - this.ensureMulti() - shape = this.formatShape(shape) - if (typeof index === 'undefined') index = this.feature._latlngs.length - this.feature._latlngs.splice(index, 0, shape) - this.feature.redraw() - if (this._enabled) this.reset() - }, - - extendBounds: function(e) { - this.feature._bounds.extend(e.vertex.latlng) - }, - - onDragStart: function(e) { - this.editLayer.clearLayers() - L.Editable.BaseEditor.prototype.onDragStart.call(this, e) - }, - - onDragEnd: function(e) { - this.initVertexMarkers() - L.Editable.BaseEditor.prototype.onDragEnd.call(this, e) - } - - }) - - // 馃崅namespace Editable; 馃崅class PolylineEditor; 馃崅aka L.Editable.PolylineEditor - // 馃崅inherits PathEditor - L.Editable.PolylineEditor = L.Editable.PathEditor.extend({ - - startDrawingBackward: function() { - this._drawing = L.Editable.BACKWARD - this.startDrawing() - }, - - // 馃崅method continueBackward(latlngs?: Array) - // Set up drawing tools to continue the line backward. - continueBackward: function(latlngs) { - if (this.drawing()) return - latlngs = latlngs || this.getDefaultLatLngs() - this.setDrawnLatLngs(latlngs) - if (latlngs.length > 0) { - this.tools.attachBackwardLineGuide() - this.tools.anchorBackwardLineGuide(latlngs[0]) - } - this.startDrawingBackward() - }, - - // 馃崅method continueForward(latlngs?: Array) - // Set up drawing tools to continue the line forward. - continueForward: function(latlngs) { - if (this.drawing()) return - latlngs = latlngs || this.getDefaultLatLngs() - this.setDrawnLatLngs(latlngs) - if (latlngs.length > 0) { - this.tools.attachForwardLineGuide() - this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]) - } - this.startDrawingForward() - }, - - getDefaultLatLngs: function(latlngs) { - latlngs = latlngs || this.feature._latlngs - if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs - else return this.getDefaultLatLngs(latlngs[0]) - }, - - ensureMulti: function() { - if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) { - this.feature._latlngs = [this.feature._latlngs] - } - }, - - addNewEmptyShape: function() { - if (this.feature._latlngs.length) { - let shape = [] - this.appendShape(shape) - return shape - } else { - return this.feature._latlngs - } - }, - - formatShape: function(shape) { - if (isFlat(shape)) return shape - else if (shape[0]) return this.formatShape(shape[0]) - }, - - // 馃崅method splitShape(latlngs?: Array, index: int) - // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`. - splitShape: function(shape, index) { - if (!index || index >= shape.length - 1) return - this.ensureMulti() - let shapeIndex = this.feature._latlngs.indexOf(shape) - if (shapeIndex === -1) return - let first = shape.slice(0, index + 1) - let second = shape.slice(index) - // We deal with reference, we don't want twice the same latlng around. - second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt) - this.feature._latlngs.splice(shapeIndex, 1, first, second) - this.refresh() - this.reset() - } - - }) - - // 馃崅namespace Editable; 馃崅class PolygonEditor; 馃崅aka L.Editable.PolygonEditor - // 馃崅inherits PathEditor - L.Editable.PolygonEditor = L.Editable.PathEditor.extend({ - - CLOSED: true, - MIN_VERTEX: 3, - - newPointForward: function(latlng) { - L.Editable.PathEditor.prototype.newPointForward.call(this, latlng) - if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng) - if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide() - }, - - addNewEmptyHole: function(latlng) { - this.ensureNotFlat() - let latlngs = this.feature.shapeAt(latlng) - if (!latlngs) return - let holes = [] - latlngs.push(holes) - return holes - }, - - // 馃崅method newHole(latlng?: L.LatLng, index: int) - // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created. - newHole: function(latlng) { - let holes = this.addNewEmptyHole(latlng) - if (!holes) return - this.setDrawnLatLngs(holes) - this.startDrawingForward() - if (latlng) this.newPointForward(latlng) - }, - - addNewEmptyShape: function() { - if (this.feature._latlngs.length && this.feature._latlngs[0].length) { - let shape = [] - this.appendShape(shape) - return shape - } else { - return this.feature._latlngs - } - }, - - ensureMulti: function() { - if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) { - this.feature._latlngs = [this.feature._latlngs] - } - }, - - ensureNotFlat: function() { - if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs] - }, - - vertexCanBeDeleted: function(vertex) { - let parent = this.feature.parentShape(vertex.latlngs) - let idx = L.Util.indexOf(parent, vertex.latlngs) - if (idx > 0) return true // Holes can be totally deleted without removing the layer itself. - return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex) - }, - - getDefaultLatLngs: function() { - if (!this.feature._latlngs.length) this.feature._latlngs.push([]) - return this.feature._latlngs[0] - }, - - formatShape: function(shape) { - // [[1, 2], [3, 4]] => must be nested - // [] => must be nested - // [[]] => is already nested - if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape] - else return shape - } - - }) - - // 馃崅namespace Editable; 馃崅class RectangleEditor; 馃崅aka L.Editable.RectangleEditor - // 馃崅inherits PathEditor - L.Editable.RectangleEditor = L.Editable.PathEditor.extend({ - - CLOSED: true, - MIN_VERTEX: 4, - - options: { - skipMiddleMarkers: true - }, - - extendBounds: function(e) { - let index = e.vertex.getIndex() - let next = e.vertex.getNext() - let previous = e.vertex.getPrevious() - let oppositeIndex = (index + 2) % 4 - let opposite = e.vertex.latlngs[oppositeIndex] - let bounds = new L.LatLngBounds(e.latlng, opposite) - // Update latlngs by hand to preserve order. - previous.latlng.update([e.latlng.lat, opposite.lng]) - next.latlng.update([opposite.lat, e.latlng.lng]) - this.updateBounds(bounds) - this.refreshVertexMarkers() - }, - - onDrawingMouseDown: function(e) { - L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e) - this.connect() - let latlngs = this.getDefaultLatLngs() - // L.Polygon._convertLatLngs removes last latlng if it equals first point, - // which is the case here as all latlngs are [0, 0] - if (latlngs.length === 3) latlngs.push(e.latlng) - let bounds = new L.LatLngBounds(e.latlng, e.latlng) - this.updateBounds(bounds) - this.updateLatLngs(bounds) - this.refresh() - this.reset() - // Stop dragging map. - // L.Draggable has two workflows: - // - mousedown => mousemove => mouseup - // - touchstart => touchmove => touchend - // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only - // can deal with mousedown, but then when in a touch device, we are dealing with - // simulated events (actually simulated by L.Map.Tap), which are no more taken - // into account by L.Draggable. - // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103 - e.originalEvent._simulated = false - this.map.dragging._draggable._onUp(e.originalEvent) - // Now transfer ongoing drag action to the bottom right corner. - // Should we refine which corner will handle the drag according to - // drag direction? - latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent) - }, - - onDrawingMouseUp: function(e) { - this.commitDrawing(e) - e.originalEvent._simulated = false - L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e) - }, - - onDrawingMouseMove: function(e) { - e.originalEvent._simulated = false - L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e) - }, - - getDefaultLatLngs: function(latlngs) { - return latlngs || this.feature._latlngs[0] - }, - - updateBounds: function(bounds) { - this.feature._bounds = bounds - }, - - updateLatLngs: function(bounds) { - let latlngs = this.getDefaultLatLngs() - let newLatlngs = this.feature._boundsToLatLngs(bounds) - // Keep references. - for (let i = 0; i < latlngs.length; i++) { - latlngs[i].update(newLatlngs[i]) - } - } - - }) - - // 馃崅namespace Editable; 馃崅class CircleEditor; 馃崅aka L.Editable.CircleEditor - // 馃崅inherits PathEditor - L.Editable.CircleEditor = L.Editable.PathEditor.extend({ - - MIN_VERTEX: 2, - - options: { - skipMiddleMarkers: true - }, - - initialize: function(map, feature, options) { - L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options) - // C.Y.B Modify - - let latlng = this.computeResizeLatLng() - - // latlng.lat = latlng.lat-0.007855; - // latlng.lng= latlng.lng-0.1; - this._resizeLatLng = latlng - // 鍘熷鏂规硶 - // this._resizeLatLng = this.computeResizeLatLng(); - }, - - computeResizeLatLng: function() { - // While circle is not added to the map, _radius is not set. - // let delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4) - let delta = (this.feature._radius || this.feature._mRadius) - let point = this.map.project(this.feature._latlng) - // return this.map.unproject([point.x + delta, point.y - delta]) - return this.map.unproject([point.x + delta, point.y]) - }, - - updateResizeLatLng: function() { - this._resizeLatLng.update(this.computeResizeLatLng()) - this._resizeLatLng.__vertex.update() - }, - - getLatLngs: function() { - return [this.feature._latlng, this._resizeLatLng] - }, - - getDefaultLatLngs: function() { - return this.getLatLngs() - }, - - onVertexMarkerDrag: function(e) { - if (e.vertex.getIndex() === 1) this.resize(e) - else this.updateResizeLatLng(e) - L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e) - }, - - resize: function(e) { - let radius = this.feature._latlng.distanceTo(e.latlng) - this.feature.setRadius(radius) - }, - - onDrawingMouseDown: function(e) { - L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e) - this._resizeLatLng.update(e.latlng) - this.feature._latlng.update(e.latlng) - this.connect() - // Stop dragging map. - e.originalEvent._simulated = false - this.map.dragging._draggable._onUp(e.originalEvent) - // Now transfer ongoing drag action to the radius handler. - this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent) - }, - - onDrawingMouseUp: function(e) { - this.commitDrawing(e) - e.originalEvent._simulated = false - L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e) - }, - - onDrawingMouseMove: function(e) { - e.originalEvent._simulated = false - L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e) - }, - - onDrag: function(e) { - L.Editable.PathEditor.prototype.onDrag.call(this, e) - this.feature.dragging.updateLatLng(this._resizeLatLng) - } - - }) - - // 馃崅namespace Editable; 馃崅class EditableMixin - // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle` - // and `L.Marker`. It adds some methods to them. - // *When editing is enabled, the editor is accessible on the instance with the - // `editor` property.* - let EditableMixin = { - - createEditor: function(map) { - map = map || this._map - let tools = (this.options.editOptions || {}).editTools || map.editTools - if (!tools) throw Error('Unable to detect Editable instance.') - let Klass = this.options.editorClass || this.getEditorClass(tools) - return new Klass(map, this, this.options.editOptions) - }, - - // 馃崅method enableEdit(map?: L.Map): this.editor - // Enable editing, by creating an editor if not existing, and then calling `enable` on it. - enableEdit: function(map) { - if (!this.editor) this.createEditor(map) - this.editor.enable() - return this.editor - }, - - // 馃崅method editEnabled(): boolean - // Return true if current instance has an editor attached, and this editor is enabled. - editEnabled: function() { - return this.editor && this.editor.enabled() - }, - - // 馃崅method disableEdit() - // Disable editing, also remove the editor property reference. - disableEdit: function() { - if (this.editor) { - this.editor.disable() - delete this.editor - } - }, - - // 馃崅method toggleEdit() - // Enable or disable editing, according to current status. - toggleEdit: function() { - if (this.editEnabled()) this.disableEdit() - else this.enableEdit() - }, - - _onEditableAdd: function() { - if (this.editor) this.enableEdit() - } - + blockEvents: function () { + // Hack: force map not to listen to other layers events while drawing. + if (!this._oldTargets) { + this._oldTargets = this.map._targets + this.map._targets = {} } + }, - let PolylineMixin = { - - getEditorClass: function(tools) { - return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L.Editable.PolylineEditor - }, - - shapeAt: function(latlng, latlngs) { - // We can have those cases: - // - latlngs are just a flat array of latlngs, use this - // - latlngs is an array of arrays of latlngs, loop over - let shape = null - latlngs = latlngs || this._latlngs - if (!latlngs.length) return shape - else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs - else for (let i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i] - return shape - }, - - isInLatLngs: function(l, latlngs) { - if (!latlngs) return false - let i, k, len - let part = [] - let p - let w = this._clickTolerance() - this._projectLatlngs(latlngs, part, this._pxBounds) - part = part[0] - p = this._map.latLngToLayerPoint(l) - - if (!this._pxBounds.contains(p)) { - return false - } - for (i = 1, len = part.length, k = 0; i < len; k = i++) { - if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) { - return true - } - } - return false - } - + unblockEvents: function () { + if (this._oldTargets) { + // Reset, but keep targets created while drawing. + this.map._targets = L.extend(this.map._targets, this._oldTargets) + delete this._oldTargets } + }, - let PolygonMixin = { + registerForDrawing: function (editor) { + if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor) + this.blockEvents() + editor.reset() // Make sure editor tools still receive events. + this._drawingEditor = editor + this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor) + this.map.on('mousedown', this.onMousedown, this) + this.map.on('mouseup', this.onMouseup, this) + L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass) + this.defaultMapCursor = this.map._container.style.cursor + this.map._container.style.cursor = this.options.drawingCursor + }, - getEditorClass: function(tools) { - return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L.Editable.PolygonEditor - }, + unregisterForDrawing: function (editor) { + this.unblockEvents() + L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass) + this.map._container.style.cursor = this.defaultMapCursor + editor = editor || this._drawingEditor + if (!editor) return + this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor) + this.map.off('mousedown', this.onMousedown, this) + this.map.off('mouseup', this.onMouseup, this) + if (editor !== this._drawingEditor) return + delete this._drawingEditor + if (editor._drawing) editor.cancelDrawing() + }, - shapeAt: function(latlng, latlngs) { - // We can have those cases: - // - latlngs are just a flat array of latlngs, use this - // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first - // - latlngs is an array of arrays of arrays, this is a multi, loop over - let shape = null - latlngs = latlngs || this._latlngs - if (!latlngs.length) return shape - else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs - else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs - else for (let i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i] - return shape - }, + onMousedown: function (e) { + if (e.originalEvent.which !== 1) return + this._mouseDown = e + this._drawingEditor.onDrawingMouseDown(e) + }, - isInLatLngs: function(l, latlngs) { - let inside = false - let l1 - let l2 - let j - let k - let len2 - for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) { - l1 = latlngs[j] - l2 = latlngs[k] + onMouseup: function (e) { + if (this._mouseDown) { + const editor = this._drawingEditor + const mouseDown = this._mouseDown + this._mouseDown = null + editor.onDrawingMouseUp(e) + if (this._drawingEditor !== editor) return // onDrawingMouseUp may call unregisterFromDrawing. + const origin = L.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY) + const distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin) + if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e) + } + }, - if (((l1.lat > l.lat) !== (l2.lat > l.lat)) && + // 馃崅section Public methods + // You will generally access them by the `map.editTools` + // instance: + // + // `map.editTools.startPolyline();` + + // 馃崅method drawing(): boolean + // Return true if any drawing action is ongoing. + drawing: function () { + return this._drawingEditor && this._drawingEditor.drawing() + }, + + // 馃崅method stopDrawing() + // When you need to stop any ongoing drawing, without needing to know which editor is active. + stopDrawing: function () { + this.unregisterForDrawing() + }, + + // 馃崅method commitDrawing() + // When you need to commit any ongoing drawing, without needing to know which editor is active. + commitDrawing: function (e) { + if (!this._drawingEditor) return + this._drawingEditor.commitDrawing(e) + }, + + connectCreatedToMap: function (layer) { + return this.featuresLayer.addLayer(layer) + }, + + // 馃崅method startPolyline(latlng: L.LatLng, options: hash): L.Polyline + // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click. + // If `options` is given, it will be passed to the Polyline class constructor. + startPolyline: function (latlng, options) { + const line = this.createPolyline([], options) + line.enableEdit(this.map).newShape(latlng) + return line + }, + + // 馃崅method startPolygon(latlng: L.LatLng, options: hash): L.Polygon + // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click. + // If `options` is given, it will be passed to the Polygon class constructor. + startPolygon: function (latlng, options) { + const polygon = this.createPolygon([], options) + polygon.enableEdit(this.map).newShape(latlng) + return polygon + }, + + // 馃崅method startMarker(latlng: L.LatLng, options: hash): L.Marker + // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point. + // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch). + // If `options` is given, it will be passed to the Marker class constructor. + startMarker: function (latlng, options) { + latlng = latlng || this.map.getCenter().clone() + const marker = this.createMarker(latlng, options) + marker.enableEdit(this.map).startDrawing() + return marker + }, + + // 馃崅method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle + // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag. + // If `options` is given, it will be passed to the Rectangle class constructor. + startRectangle: function (latlng, options) { + const corner = latlng || L.latLng([0, 0]) + const bounds = new L.LatLngBounds(corner, corner) + const rectangle = this.createRectangle(bounds, options) + rectangle.enableEdit(this.map).startDrawing() + return rectangle + }, + + // 馃崅method startCircle(latlng: L.LatLng, options: hash): L.Circle + // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag. + // If `options` is given, it will be passed to the Circle class constructor. + startCircle: function (latlng, options) { + latlng = latlng || this.map.getCenter().clone() + const circle = this.createCircle(latlng, options) + circle.enableEdit(this.map).startDrawing() + return circle + }, + + startHole: function (editor, latlng) { + editor.newHole(latlng) + }, + + createLayer: function (Klass, latlngs, options) { + options = L.Util.extend({ editOptions: { editTools: this } }, options) + const layer = new Klass(latlngs, options) + // 馃崅namespace Editable + // 馃崅event editable:created: LayerEvent + // Fired when a new feature (Marker, Polyline鈥�) is created. + this.fireAndForward('editable:created', { layer: layer }) + return layer + }, + + createPolyline: function (latlngs, options) { + return this.createLayer((options && options.polylineClass) || this.options.polylineClass, latlngs, options) + }, + + createPolygon: function (latlngs, options) { + return this.createLayer((options && options.polygonClass) || this.options.polygonClass, latlngs, options) + }, + + createMarker: function (latlng, options) { + return this.createLayer((options && options.markerClass) || this.options.markerClass, latlng, options) + }, + + createRectangle: function (bounds, options) { + return this.createLayer((options && options.rectangleClass) || this.options.rectangleClass, bounds, options) + }, + + createCircle: function (latlng, options) { + return this.createLayer((options && options.circleClass) || this.options.circleClass, latlng, options) + } + + }) + + L.extend(L.Editable, { + + makeCancellable: function (e) { + e.cancel = function () { + e._cancelled = true + } + } + + }) + + // 馃崅namespace Map; 馃崅class Map + // Leaflet.Editable add options and events to the `L.Map` object. + // See `Editable` events for the list of events fired on the Map. + // 馃崅example + // + // ```js + // let map = L.map('map', { + // editable: true, + // editOptions: { + // 鈥� + // } + // }); + // ``` + // 馃崅section Editable Map Options + L.Map.mergeOptions({ + + // 馃崅namespace Map + // 馃崅section Map Options + // 馃崅option EditToolsClass: class = L.Editable + // Class to be used as vertex, for path editing. + EditToolsClass: L.Editable, + + // 馃崅option editable: boolean = false + // Whether to create a L.Editable instance at map init. + editable: false, + + // 馃崅option editOptions: hash = {} + // Options to pass to L.Editable when instantiating. + editOptions: {} + + }) + + L.Map.addInitHook(function () { + this.whenReady(function () { + if (this.options.editable) { + this.editTools = new this.options.EditToolsClass(this, this.options.editOptions) + } + }) + }) + + L.Editable.VertexIcon = L.DivIcon.extend({ + + options: { + iconSize: new L.Point(8, 8) + } + + }) + + L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({ + + options: { + iconSize: new L.Point(20, 20) + } + + }) + + // 馃崅namespace Editable; 馃崅class VertexMarker; Handler for dragging path vertices. + L.Editable.VertexMarker = L.Marker.extend({ + + options: { + draggable: true, + className: 'leaflet-vertex-icon leaflet-custom-icon' + }, + + // 馃崅section Public methods + // The marker used to handle path vertex. You will usually interact with a `VertexMarker` + // instance when listening for events like `editable:vertex:ctrlclick`. + + initialize: function (latlng, latlngs, editor, options) { + // We don't use this._latlng, because on drag Leaflet replace it while + // we want to keep reference. + this.latlng = latlng + this.latlngs = latlngs + this.editor = editor + L.Marker.prototype.initialize.call(this, latlng, options) + this.options.icon = this.editor.tools.createVertexIcon({ className: this.options.className }) + this.latlng.__vertex = this + this.editor.editLayer.addLayer(this) + this.setZIndexOffset(editor.tools._lastZIndex + 1) + }, + + onAdd: function (map) { + L.Marker.prototype.onAdd.call(this, map) + this.on('drag', this.onDrag) + this.on('dragstart', this.onDragStart) + this.on('dragend', this.onDragEnd) + this.on('mouseup', this.onMouseup) + this.on('click', this.onClick) + this.on('contextmenu', this.onContextMenu) + this.on('mousedown touchstart', this.onMouseDown) + this.on('mouseover', this.onMouseOver) + this.on('mouseout', this.onMouseOut) + this.addMiddleMarkers() + }, + + onRemove: function (map) { + if (this.middleMarker) this.middleMarker.delete() + delete this.latlng.__vertex + this.off('drag', this.onDrag) + this.off('dragstart', this.onDragStart) + this.off('dragend', this.onDragEnd) + this.off('mouseup', this.onMouseup) + this.off('click', this.onClick) + this.off('contextmenu', this.onContextMenu) + this.off('mousedown touchstart', this.onMouseDown) + this.off('mouseover', this.onMouseOver) + this.off('mouseout', this.onMouseOut) + L.Marker.prototype.onRemove.call(this, map) + }, + + onDrag: function (e) { + e.vertex = this + this.editor.onVertexMarkerDrag(e) + const iconPos = L.DomUtil.getPosition(this._icon) + const latlng = this._map.layerPointToLatLng(iconPos) + this.latlng.update(latlng) + this._latlng = this.latlng // Push back to Leaflet our reference. + this.editor.refresh() + if (this.middleMarker) this.middleMarker.updateLatLng() + const next = this.getNext() + if (next && next.middleMarker) next.middleMarker.updateLatLng() + }, + + onDragStart: function (e) { + e.vertex = this + this.editor.onVertexMarkerDragStart(e) + }, + + onDragEnd: function (e) { + e.vertex = this + this.editor.onVertexMarkerDragEnd(e) + }, + + onClick: function (e) { + e.vertex = this + this.editor.onVertexMarkerClick(e) + }, + + onMouseup: function (e) { + L.DomEvent.stop(e) + e.vertex = this + this.editor.map.fire('mouseup', e) + }, + + onContextMenu: function (e) { + e.vertex = this + this.editor.onVertexMarkerContextMenu(e) + }, + + onMouseDown: function (e) { + e.vertex = this + this.editor.onVertexMarkerMouseDown(e) + }, + + onMouseOver: function (e) { + e.vertex = this + this.editor.onVertexMarkerMouseOver(e) + }, + + onMouseOut: function (e) { + e.vertex = this + this.editor.onVertexMarkerMouseOut(e) + }, + + // 馃崅method delete() + // Delete a vertex and the related LatLng. + delete: function () { + const next = this.getNext() // Compute before changing latlng + this.latlngs.splice(this.getIndex(), 1) + this.editor.editLayer.removeLayer(this) + this.editor.onVertexDeleted({ latlng: this.latlng, vertex: this }) + if (!this.latlngs.length) this.editor.deleteShape(this.latlngs) + if (next) next.resetMiddleMarker() + this.editor.refresh() + }, + + // 馃崅method getIndex(): int + // Get the index of the current vertex among others of the same LatLngs group. + getIndex: function () { + return this.latlngs.indexOf(this.latlng) + }, + + // 馃崅method getLastIndex(): int + // Get last vertex index of the LatLngs group of the current vertex. + getLastIndex: function () { + return this.latlngs.length - 1 + }, + + // 馃崅method getPrevious(): VertexMarker + // Get the previous VertexMarker in the same LatLngs group. + getPrevious: function () { + if (this.latlngs.length < 2) return + const index = this.getIndex() + let previousIndex = index - 1 + if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex() + const previous = this.latlngs[previousIndex] + if (previous) return previous.__vertex + }, + + // 馃崅method getNext(): VertexMarker + // Get the next VertexMarker in the same LatLngs group. + getNext: function () { + if (this.latlngs.length < 2) return + const index = this.getIndex() + let nextIndex = index + 1 + if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0 + const next = this.latlngs[nextIndex] + if (next) return next.__vertex + }, + + addMiddleMarker: function (previous) { + if (!this.editor.hasMiddleMarkers()) return + previous = previous || this.getPrevious() + if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor) + }, + + addMiddleMarkers: function () { + if (!this.editor.hasMiddleMarkers()) return + const previous = this.getPrevious() + if (previous) this.addMiddleMarker(previous) + const next = this.getNext() + if (next) next.resetMiddleMarker() + }, + + resetMiddleMarker: function () { + if (this.middleMarker) this.middleMarker.delete() + this.addMiddleMarker() + }, + + // 馃崅method split() + // Split the vertex LatLngs group at its index, if possible. + split: function () { + if (!this.editor.splitShape) return // Only for PolylineEditor + this.editor.splitShape(this.latlngs, this.getIndex()) + }, + + // 馃崅method continue() + // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline. + continue: function () { + if (!this.editor.continueBackward) return // Only for PolylineEditor + const index = this.getIndex() + if (index === 0) this.editor.continueBackward(this.latlngs) + else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs) + } + + }) + + L.Editable.mergeOptions({ + + // 馃崅namespace Editable + // 馃崅option VertexMarkerClass: class = VertexMarker + // Class to be used as vertex, for path editing. + VertexMarkerClass: L.Editable.VertexMarker + + }) + + L.Editable.MiddleMarker = L.Marker.extend({ + + options: { + opacity: 0.5, + className: 'leaflet-div-icon leaflet-middle-icon', + draggable: true + }, + + initialize: function (left, right, latlngs, editor, options) { + this.left = left + this.right = right + this.editor = editor + this.latlngs = latlngs + L.Marker.prototype.initialize.call(this, this.computeLatLng(), options) + this._opacity = this.options.opacity + this.options.icon = this.editor.tools.createVertexIcon({ className: this.options.className }) + this.editor.editLayer.addLayer(this) + this.setVisibility() + }, + + setVisibility: function () { + const leftPoint = this._map.latLngToContainerPoint(this.left.latlng) + const rightPoint = this._map.latLngToContainerPoint(this.right.latlng) + const size = L.point(this.options.icon.options.iconSize) + if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide() + else this.show() + }, + + show: function () { + this.setOpacity(this._opacity) + }, + + hide: function () { + this.setOpacity(0) + }, + + updateLatLng: function () { + this.setLatLng(this.computeLatLng()) + this.setVisibility() + }, + + computeLatLng: function () { + const leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng) + const rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng) + const y = (leftPoint.y + rightPoint.y) / 2 + const x = (leftPoint.x + rightPoint.x) / 2 + return this.editor.map.containerPointToLatLng([x, y]) + }, + + onAdd: function (map) { + L.Marker.prototype.onAdd.call(this, map) + L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this) + map.on('zoomend', this.setVisibility, this) + }, + + onRemove: function (map) { + delete this.right.middleMarker + L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this) + map.off('zoomend', this.setVisibility, this) + L.Marker.prototype.onRemove.call(this, map) + }, + + onMouseDown: function (e) { + const iconPos = L.DomUtil.getPosition(this._icon) + const latlng = this.editor.map.layerPointToLatLng(iconPos) + e = { + originalEvent: e, + latlng: latlng + } + if (this.options.opacity === 0) return + L.Editable.makeCancellable(e) + this.editor.onMiddleMarkerMouseDown(e) + if (e._cancelled) return + this.latlngs.splice(this.index(), 0, e.latlng) + this.editor.refresh() + const icon = this._icon + const marker = this.editor.addVertexMarker(e.latlng, this.latlngs) + this.editor.onNewVertex(marker) + /* Hack to workaround browser not firing touchend when element is no more on DOM */ + const parent = marker._icon.parentNode + parent.removeChild(marker._icon) + marker._icon = icon + parent.appendChild(marker._icon) + marker._initIcon() + marker._initInteraction() + marker.setOpacity(1) + /* End hack */ + // Transfer ongoing dragging to real marker + L.Draggable._dragging = false + marker.dragging._draggable._onDown(e.originalEvent) + this.delete() + }, + + delete: function () { + this.editor.editLayer.removeLayer(this) + }, + + index: function () { + return this.latlngs.indexOf(this.right.latlng) + } + + }) + + L.Editable.mergeOptions({ + + // 馃崅namespace Editable + // 馃崅option MiddleMarkerClass: class = VertexMarker + // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path. + MiddleMarkerClass: L.Editable.MiddleMarker + + }) + + // 馃崅namespace Editable; 馃崅class BaseEditor; 馃崅aka L.Editable.BaseEditor + // When editing a feature (Marker, Polyline鈥�), an editor is attached to it. This + // editor basically knows how to handle the edition. + L.Editable.BaseEditor = L.Handler.extend({ + + initialize: function (map, feature, options) { + L.setOptions(this, options) + this.map = map + this.feature = feature + this.feature.editor = this + this.editLayer = new L.LayerGroup() + this.tools = this.options.editTools || map.editTools + }, + + // 馃崅method enable(): this + // Set up the drawing tools for the feature to be editable. + addHooks: function () { + if (this.isConnected()) this.onFeatureAdd() + else this.feature.once('add', this.onFeatureAdd, this) + this.onEnable() + this.feature.on(this._getEvents(), this) + }, + + // 馃崅method disable(): this + // Remove the drawing tools for the feature. + removeHooks: function () { + this.feature.off(this._getEvents(), this) + if (this.feature.dragging) this.feature.dragging.disable() + this.editLayer.clearLayers() + this.tools.editLayer.removeLayer(this.editLayer) + this.onDisable() + if (this._drawing) this.cancelDrawing() + }, + + // 馃崅method drawing(): boolean + // Return true if any drawing action is ongoing with this editor. + drawing: function () { + return !!this._drawing + }, + + reset: function () { + }, + + onFeatureAdd: function () { + this.tools.editLayer.addLayer(this.editLayer) + if (this.feature.dragging) this.feature.dragging.enable() + }, + + hasMiddleMarkers: function () { + return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers + }, + + fireAndForward: function (type, e) { + e = e || {} + e.layer = this.feature + this.feature.fire(type, e) + this.tools.fireAndForward(type, e) + }, + + onEnable: function () { + // 馃崅namespace Editable + // 馃崅event editable:enable: Event + // Fired when an existing feature is ready to be edited. + this.fireAndForward('editable:enable') + }, + + onDisable: function () { + // 馃崅namespace Editable + // 馃崅event editable:disable: Event + // Fired when an existing feature is not ready anymore to be edited. + this.fireAndForward('editable:disable') + }, + + onEditing: function () { + // 馃崅namespace Editable + // 馃崅event editable:editing: Event + // Fired as soon as any change is made to the feature geometry. + this.fireAndForward('editable:editing') + }, + + onStartDrawing: function () { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:start: Event + // Fired when a feature is to be drawn. + this.fireAndForward('editable:drawing:start') + }, + + onEndDrawing: function () { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:end: Event + // Fired when a feature is not drawn anymore. + this.fireAndForward('editable:drawing:end') + }, + + onCancelDrawing: function () { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:cancel: Event + // Fired when user cancel drawing while a feature is being drawn. + this.fireAndForward('editable:drawing:cancel') + }, + + onCommitDrawing: function (e) { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:commit: Event + // Fired when user finish drawing a feature. + this.fireAndForward('editable:drawing:commit', e) + }, + + onDrawingMouseDown: function (e) { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:mousedown: Event + // Fired when user `mousedown` while drawing. + this.fireAndForward('editable:drawing:mousedown', e) + }, + + onDrawingMouseUp: function (e) { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:mouseup: Event + // Fired when user `mouseup` while drawing. + this.fireAndForward('editable:drawing:mouseup', e) + }, + + startDrawing: function () { + if (!this._drawing) this._drawing = L.Editable.FORWARD + this.tools.registerForDrawing(this) + this.onStartDrawing() + }, + + commitDrawing: function (e) { + this.onCommitDrawing(e) + this.endDrawing() + }, + + cancelDrawing: function () { + // If called during a vertex drag, the vertex will be removed before + // the mouseup fires on it. This is a workaround. Maybe better fix is + // To have L.Draggable reset it's status on disable (Leaflet side). + L.Draggable._dragging = false + this.onCancelDrawing() + this.endDrawing() + }, + + endDrawing: function () { + this._drawing = false + this.tools.unregisterForDrawing(this) + this.onEndDrawing() + }, + + onDrawingClick: function (e) { + if (!this.drawing()) return + L.Editable.makeCancellable(e) + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:click: CancelableEvent + // Fired when user `click` while drawing, before any internal action is being processed. + this.fireAndForward('editable:drawing:click', e) + if (e._cancelled) return + if (!this.isConnected()) this.connect(e) + this.processDrawingClick(e) + }, + + isConnected: function () { + return this.map.hasLayer(this.feature) + }, + + connect: function () { + this.tools.connectCreatedToMap(this.feature) + this.tools.editLayer.addLayer(this.editLayer) + }, + + onMove: function (e) { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:move: Event + // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex. + this.fireAndForward('editable:drawing:move', e) + }, + + onDrawingMouseMove: function (e) { + this.onMove(e) + }, + + _getEvents: function () { + return { + dragstart: this.onDragStart, + drag: this.onDrag, + dragend: this.onDragEnd, + remove: this.disable + } + }, + + onDragStart: function (e) { + this.onEditing() + // 馃崅namespace Editable + // 馃崅event editable:dragstart: Event + // Fired before a path feature is dragged. + this.fireAndForward('editable:dragstart', e) + }, + + onDrag: function (e) { + this.onMove(e) + // 馃崅namespace Editable + // 馃崅event editable:drag: Event + // Fired when a path feature is being dragged. + this.fireAndForward('editable:drag', e) + }, + + onDragEnd: function (e) { + // 馃崅namespace Editable + // 馃崅event editable:dragend: Event + // Fired after a path feature has been dragged. + this.fireAndForward('editable:dragend', e) + } + + }) + + // 馃崅namespace Editable; 馃崅class MarkerEditor; 馃崅aka L.Editable.MarkerEditor + // 馃崅inherits BaseEditor + // Editor for Marker. + L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({ + + onDrawingMouseMove: function (e) { + L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) + if (this._drawing) this.feature.setLatLng(e.latlng) + }, + + processDrawingClick: function (e) { + // 馃崅namespace Editable + // 馃崅section Drawing events + // 馃崅event editable:drawing:clicked: Event + // Fired when user `click` while drawing, after all internal actions. + this.fireAndForward('editable:drawing:clicked', e) + this.commitDrawing(e) + }, + + connect: function (e) { + // On touch, the latlng has not been updated because there is + // no mousemove. + if (e) this.feature._latlng = e.latlng + L.Editable.BaseEditor.prototype.connect.call(this, e) + } + + }) + + // 馃崅namespace Editable; 馃崅class PathEditor; 馃崅aka L.Editable.PathEditor + // 馃崅inherits BaseEditor + // Base class for all path editors. + L.Editable.PathEditor = L.Editable.BaseEditor.extend({ + + CLOSED: false, + MIN_VERTEX: 2, + + addHooks: function () { + L.Editable.BaseEditor.prototype.addHooks.call(this) + if (this.feature) this.initVertexMarkers() + return this + }, + + initVertexMarkers: function (latlngs) { + if (!this.enabled()) return + latlngs = latlngs || this.getLatLngs() + if (isFlat(latlngs)) this.addVertexMarkers(latlngs) + else for (let i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]) + }, + + getLatLngs: function () { + return this.feature.getLatLngs() + }, + + // 馃崅method reset() + // Rebuild edit elements (Vertex, MiddleMarker, etc.). + reset: function () { + this.editLayer.clearLayers() + this.initVertexMarkers() + }, + + addVertexMarker: function (latlng, latlngs) { + return new this.tools.options.VertexMarkerClass(latlng, latlngs, this) + }, + + onNewVertex: function (vertex) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:new: VertexEvent + // Fired when a new vertex is created. + this.fireAndForward('editable:vertex:new', { latlng: vertex.latlng, vertex: vertex }) + }, + + addVertexMarkers: function (latlngs) { + for (let i = 0; i < latlngs.length; i++) { + this.addVertexMarker(latlngs[i], latlngs) + } + }, + + refreshVertexMarkers: function (latlngs) { + latlngs = latlngs || this.getDefaultLatLngs() + for (let i = 0; i < latlngs.length; i++) { + latlngs[i].__vertex.update() + } + }, + + addMiddleMarker: function (left, right, latlngs) { + return new this.tools.options.MiddleMarkerClass(left, right, latlngs, this) + }, + + onVertexMarkerClick: function (e) { + L.Editable.makeCancellable(e) + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:click: CancelableVertexEvent + // Fired when a `click` is issued on a vertex, before any internal action is being processed. + this.fireAndForward('editable:vertex:click', e) + if (e._cancelled) return + if (this.tools.drawing() && this.tools._drawingEditor !== this) return + const index = e.vertex.getIndex() + let commit + if (e.originalEvent.ctrlKey) { + this.onVertexMarkerCtrlClick(e) + } else if (e.originalEvent.altKey) { + this.onVertexMarkerAltClick(e) + } else if (e.originalEvent.shiftKey) { + this.onVertexMarkerShiftClick(e) + } else if (e.originalEvent.metaKey) { + this.onVertexMarkerMetaKeyClick(e) + } else if (index === e.vertex.getLastIndex() && this._drawing === L.Editable.FORWARD) { + if (index >= this.MIN_VERTEX - 1) commit = true + } else if (index === 0 && this._drawing === L.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) { + commit = true + } else if (index === 0 && this._drawing === L.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) { + commit = true // Allow to close on first point also for polygons + } else { + this.onVertexRawMarkerClick(e) + } + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:clicked: VertexEvent + // Fired when a `click` is issued on a vertex, after all internal actions. + this.fireAndForward('editable:vertex:clicked', e) + if (commit) this.commitDrawing(e) + }, + + onVertexRawMarkerClick: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:rawclick: CancelableVertexEvent + // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode. + this.fireAndForward('editable:vertex:rawclick', e) + if (e._cancelled) return + if (!this.vertexCanBeDeleted(e.vertex)) return + e.vertex.delete() + }, + + vertexCanBeDeleted: function (vertex) { + return vertex.latlngs.length > this.MIN_VERTEX + }, + + onVertexDeleted: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:deleted: VertexEvent + // Fired after a vertex has been deleted by user. + this.fireAndForward('editable:vertex:deleted', e) + }, + + onVertexMarkerCtrlClick: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:ctrlclick: VertexEvent + // Fired when a `click` with `ctrlKey` is issued on a vertex. + this.fireAndForward('editable:vertex:ctrlclick', e) + }, + + onVertexMarkerShiftClick: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:shiftclick: VertexEvent + // Fired when a `click` with `shiftKey` is issued on a vertex. + this.fireAndForward('editable:vertex:shiftclick', e) + }, + + onVertexMarkerMetaKeyClick: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:metakeyclick: VertexEvent + // Fired when a `click` with `metaKey` is issued on a vertex. + this.fireAndForward('editable:vertex:metakeyclick', e) + }, + + onVertexMarkerAltClick: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:altclick: VertexEvent + // Fired when a `click` with `altKey` is issued on a vertex. + this.fireAndForward('editable:vertex:altclick', e) + }, + + onVertexMarkerContextMenu: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:contextmenu: VertexEvent + // Fired when a `contextmenu` is issued on a vertex. + this.fireAndForward('editable:vertex:contextmenu', e) + }, + + onVertexMarkerMouseDown: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:mousedown: VertexEvent + // Fired when user `mousedown` a vertex. + this.fireAndForward('editable:vertex:mousedown', e) + }, + + onVertexMarkerMouseOver: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:mouseover: VertexEvent + // Fired when a user's mouse enters the vertex + this.fireAndForward('editable:vertex:mouseover', e) + }, + + onVertexMarkerMouseOut: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:mouseout: VertexEvent + // Fired when a user's mouse leaves the vertex + this.fireAndForward('editable:vertex:mouseout', e) + }, + + onMiddleMarkerMouseDown: function (e) { + // 馃崅namespace Editable + // 馃崅section MiddleMarker events + // 馃崅event editable:middlemarker:mousedown: VertexEvent + // Fired when user `mousedown` a middle marker. + this.fireAndForward('editable:middlemarker:mousedown', e) + }, + + onVertexMarkerDrag: function (e) { + this.onMove(e) + if (this.feature._bounds) this.extendBounds(e) + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:drag: VertexEvent + // Fired when a vertex is dragged by user. + this.fireAndForward('editable:vertex:drag', e) + }, + + onVertexMarkerDragStart: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:dragstart: VertexEvent + // Fired before a vertex is dragged by user. + this.fireAndForward('editable:vertex:dragstart', e) + }, + + onVertexMarkerDragEnd: function (e) { + // 馃崅namespace Editable + // 馃崅section Vertex events + // 馃崅event editable:vertex:dragend: VertexEvent + // Fired after a vertex is dragged by user. + this.fireAndForward('editable:vertex:dragend', e) + }, + + setDrawnLatLngs: function (latlngs) { + this._drawnLatLngs = latlngs || this.getDefaultLatLngs() + }, + + startDrawing: function () { + if (!this._drawnLatLngs) this.setDrawnLatLngs() + L.Editable.BaseEditor.prototype.startDrawing.call(this) + }, + + startDrawingForward: function () { + this.startDrawing() + }, + + endDrawing: function () { + this.tools.detachForwardLineGuide() + this.tools.detachBackwardLineGuide() + if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs) + L.Editable.BaseEditor.prototype.endDrawing.call(this) + delete this._drawnLatLngs + }, + + addLatLng: function (latlng) { + if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng) + else this._drawnLatLngs.unshift(latlng) + this.feature._bounds.extend(latlng) + const vertex = this.addVertexMarker(latlng, this._drawnLatLngs) + this.onNewVertex(vertex) + this.refresh() + }, + + newPointForward: function (latlng) { + this.addLatLng(latlng) + this.tools.attachForwardLineGuide() + this.tools.anchorForwardLineGuide(latlng) + }, + + newPointBackward: function (latlng) { + this.addLatLng(latlng) + this.tools.anchorBackwardLineGuide(latlng) + }, + + // 馃崅namespace PathEditor + // 馃崅method push() + // Programmatically add a point while drawing. + push: function (latlng) { + if (!latlng) return console.error('L.Editable.PathEditor.push expect a valid latlng as parameter') + if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng) + else this.newPointBackward(latlng) + }, + + removeLatLng: function (latlng) { + latlng.__vertex.delete() + this.refresh() + }, + + // 馃崅method pop(): L.LatLng or null + // Programmatically remove last point (if any) while drawing. + pop: function () { + if (this._drawnLatLngs.length <= 1) return + let latlng + if (this._drawing === L.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1] + else latlng = this._drawnLatLngs[0] + this.removeLatLng(latlng) + if (this._drawing === L.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]) + else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]) + return latlng + }, + + processDrawingClick: function (e) { + if (e.vertex && e.vertex.editor === this) return + if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng) + else this.newPointBackward(e.latlng) + this.fireAndForward('editable:drawing:clicked', e) + }, + + onDrawingMouseMove: function (e) { + L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) + if (this._drawing) { + this.tools.moveForwardLineGuide(e.latlng) + this.tools.moveBackwardLineGuide(e.latlng) + } + }, + + refresh: function () { + this.feature.redraw() + this.onEditing() + }, + + // 馃崅namespace PathEditor + // 馃崅method newShape(latlng?: L.LatLng) + // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it; + // if optional `latlng` is given, start a path at this point. + newShape: function (latlng) { + const shape = this.addNewEmptyShape() + if (!shape) return + this.setDrawnLatLngs(shape[0] || shape) // Polygon or polyline + this.startDrawingForward() + // 馃崅namespace Editable + // 馃崅section Shape events + // 馃崅event editable:shape:new: ShapeEvent + // Fired when a new shape is created in a multi (Polygon or Polyline). + this.fireAndForward('editable:shape:new', { shape: shape }) + if (latlng) this.newPointForward(latlng) + }, + + deleteShape: function (shape, latlngs) { + const e = { shape: shape } + L.Editable.makeCancellable(e) + // 馃崅namespace Editable + // 馃崅section Shape events + // 馃崅event editable:shape:delete: CancelableShapeEvent + // Fired before a new shape is deleted in a multi (Polygon or Polyline). + this.fireAndForward('editable:shape:delete', e) + if (e._cancelled) return + shape = this._deleteShape(shape, latlngs) + if (this.ensureNotFlat) this.ensureNotFlat() // Polygon. + this.feature.setLatLngs(this.getLatLngs()) // Force bounds reset. + this.refresh() + this.reset() + // 馃崅namespace Editable + // 馃崅section Shape events + // 馃崅event editable:shape:deleted: ShapeEvent + // Fired after a new shape is deleted in a multi (Polygon or Polyline). + this.fireAndForward('editable:shape:deleted', { shape: shape }) + return shape + }, + + _deleteShape: function (shape, latlngs) { + latlngs = latlngs || this.getLatLngs() + if (!latlngs.length) return + const self = this + const inplaceDelete = function (latlngs, shape) { + // Called when deleting a flat latlngs + shape = latlngs.splice(0, Number.MAX_VALUE) + return shape + } + const spliceDelete = function (latlngs, shape) { + // Called when removing a latlngs inside an array + latlngs.splice(latlngs.indexOf(shape), 1) + if (!latlngs.length) self._deleteShape(latlngs) + return shape + } + if (latlngs === shape) return inplaceDelete(latlngs, shape) + for (let i = 0; i < latlngs.length; i++) { + if (latlngs[i] === shape) return spliceDelete(latlngs, shape) + else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape) + } + }, + + // 馃崅namespace PathEditor + // 馃崅method deleteShapeAt(latlng: L.LatLng): Array + // Remove a path shape at the given `latlng`. + deleteShapeAt: function (latlng) { + const shape = this.feature.shapeAt(latlng) + if (shape) return this.deleteShape(shape) + }, + + // 馃崅method appendShape(shape: Array) + // Append a new shape to the Polygon or Polyline. + appendShape: function (shape) { + this.insertShape(shape) + }, + + // 馃崅method prependShape(shape: Array) + // Prepend a new shape to the Polygon or Polyline. + prependShape: function (shape) { + this.insertShape(shape, 0) + }, + + // 馃崅method insertShape(shape: Array, index: int) + // Insert a new shape to the Polygon or Polyline at given index (default is to append). + insertShape: function (shape, index) { + this.ensureMulti() + shape = this.formatShape(shape) + if (typeof index === 'undefined') index = this.feature._latlngs.length + this.feature._latlngs.splice(index, 0, shape) + this.feature.redraw() + if (this._enabled) this.reset() + }, + + extendBounds: function (e) { + this.feature._bounds.extend(e.vertex.latlng) + }, + + onDragStart: function (e) { + this.editLayer.clearLayers() + L.Editable.BaseEditor.prototype.onDragStart.call(this, e) + }, + + onDragEnd: function (e) { + this.initVertexMarkers() + L.Editable.BaseEditor.prototype.onDragEnd.call(this, e) + } + + }) + + // 馃崅namespace Editable; 馃崅class PolylineEditor; 馃崅aka L.Editable.PolylineEditor + // 馃崅inherits PathEditor + L.Editable.PolylineEditor = L.Editable.PathEditor.extend({ + + startDrawingBackward: function () { + this._drawing = L.Editable.BACKWARD + this.startDrawing() + }, + + // 馃崅method continueBackward(latlngs?: Array) + // Set up drawing tools to continue the line backward. + continueBackward: function (latlngs) { + if (this.drawing()) return + latlngs = latlngs || this.getDefaultLatLngs() + this.setDrawnLatLngs(latlngs) + if (latlngs.length > 0) { + this.tools.attachBackwardLineGuide() + this.tools.anchorBackwardLineGuide(latlngs[0]) + } + this.startDrawingBackward() + }, + + // 馃崅method continueForward(latlngs?: Array) + // Set up drawing tools to continue the line forward. + continueForward: function (latlngs) { + if (this.drawing()) return + latlngs = latlngs || this.getDefaultLatLngs() + this.setDrawnLatLngs(latlngs) + if (latlngs.length > 0) { + this.tools.attachForwardLineGuide() + this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]) + } + this.startDrawingForward() + }, + + getDefaultLatLngs: function (latlngs) { + latlngs = latlngs || this.feature._latlngs + if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs + else return this.getDefaultLatLngs(latlngs[0]) + }, + + ensureMulti: function () { + if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) { + this.feature._latlngs = [this.feature._latlngs] + } + }, + + addNewEmptyShape: function () { + if (this.feature._latlngs.length) { + const shape = [] + this.appendShape(shape) + return shape + } else { + return this.feature._latlngs + } + }, + + formatShape: function (shape) { + if (isFlat(shape)) return shape + else if (shape[0]) return this.formatShape(shape[0]) + }, + + // 馃崅method splitShape(latlngs?: Array, index: int) + // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`. + splitShape: function (shape, index) { + if (!index || index >= shape.length - 1) return + this.ensureMulti() + const shapeIndex = this.feature._latlngs.indexOf(shape) + if (shapeIndex === -1) return + const first = shape.slice(0, index + 1) + const second = shape.slice(index) + // We deal with reference, we don't want twice the same latlng around. + second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt) + this.feature._latlngs.splice(shapeIndex, 1, first, second) + this.refresh() + this.reset() + } + + }) + + // 馃崅namespace Editable; 馃崅class PolygonEditor; 馃崅aka L.Editable.PolygonEditor + // 馃崅inherits PathEditor + L.Editable.PolygonEditor = L.Editable.PathEditor.extend({ + + CLOSED: true, + MIN_VERTEX: 3, + + newPointForward: function (latlng) { + L.Editable.PathEditor.prototype.newPointForward.call(this, latlng) + if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng) + if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide() + }, + + addNewEmptyHole: function (latlng) { + this.ensureNotFlat() + const latlngs = this.feature.shapeAt(latlng) + if (!latlngs) return + const holes = [] + latlngs.push(holes) + return holes + }, + + // 馃崅method newHole(latlng?: L.LatLng, index: int) + // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created. + newHole: function (latlng) { + const holes = this.addNewEmptyHole(latlng) + if (!holes) return + this.setDrawnLatLngs(holes) + this.startDrawingForward() + if (latlng) this.newPointForward(latlng) + }, + + addNewEmptyShape: function () { + if (this.feature._latlngs.length && this.feature._latlngs[0].length) { + const shape = [] + this.appendShape(shape) + return shape + } else { + return this.feature._latlngs + } + }, + + ensureMulti: function () { + if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) { + this.feature._latlngs = [this.feature._latlngs] + } + }, + + ensureNotFlat: function () { + if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs] + }, + + vertexCanBeDeleted: function (vertex) { + const parent = this.feature.parentShape(vertex.latlngs) + const idx = L.Util.indexOf(parent, vertex.latlngs) + if (idx > 0) return true // Holes can be totally deleted without removing the layer itself. + return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex) + }, + + getDefaultLatLngs: function () { + if (!this.feature._latlngs.length) this.feature._latlngs.push([]) + return this.feature._latlngs[0] + }, + + formatShape: function (shape) { + // [[1, 2], [3, 4]] => must be nested + // [] => must be nested + // [[]] => is already nested + if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape] + else return shape + } + + }) + + // 馃崅namespace Editable; 馃崅class RectangleEditor; 馃崅aka L.Editable.RectangleEditor + // 馃崅inherits PathEditor + L.Editable.RectangleEditor = L.Editable.PathEditor.extend({ + + CLOSED: true, + MIN_VERTEX: 4, + + options: { + skipMiddleMarkers: true + }, + + extendBounds: function (e) { + const index = e.vertex.getIndex() + const next = e.vertex.getNext() + const previous = e.vertex.getPrevious() + const oppositeIndex = (index + 2) % 4 + const opposite = e.vertex.latlngs[oppositeIndex] + const bounds = new L.LatLngBounds(e.latlng, opposite) + // Update latlngs by hand to preserve order. + previous.latlng.update([e.latlng.lat, opposite.lng]) + next.latlng.update([opposite.lat, e.latlng.lng]) + this.updateBounds(bounds) + this.refreshVertexMarkers() + }, + + onDrawingMouseDown: function (e) { + L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e) + this.connect() + const latlngs = this.getDefaultLatLngs() + // L.Polygon._convertLatLngs removes last latlng if it equals first point, + // which is the case here as all latlngs are [0, 0] + if (latlngs.length === 3) latlngs.push(e.latlng) + const bounds = new L.LatLngBounds(e.latlng, e.latlng) + this.updateBounds(bounds) + this.updateLatLngs(bounds) + this.refresh() + this.reset() + // Stop dragging map. + // L.Draggable has two workflows: + // - mousedown => mousemove => mouseup + // - touchstart => touchmove => touchend + // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only + // can deal with mousedown, but then when in a touch device, we are dealing with + // simulated events (actually simulated by L.Map.Tap), which are no more taken + // into account by L.Draggable. + // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103 + e.originalEvent._simulated = false + this.map.dragging._draggable._onUp(e.originalEvent) + // Now transfer ongoing drag action to the bottom right corner. + // Should we refine which corner will handle the drag according to + // drag direction? + latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent) + }, + + onDrawingMouseUp: function (e) { + this.commitDrawing(e) + e.originalEvent._simulated = false + L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e) + }, + + onDrawingMouseMove: function (e) { + e.originalEvent._simulated = false + L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e) + }, + + getDefaultLatLngs: function (latlngs) { + return latlngs || this.feature._latlngs[0] + }, + + updateBounds: function (bounds) { + this.feature._bounds = bounds + }, + + updateLatLngs: function (bounds) { + const latlngs = this.getDefaultLatLngs() + const newLatlngs = this.feature._boundsToLatLngs(bounds) + // Keep references. + for (let i = 0; i < latlngs.length; i++) { + latlngs[i].update(newLatlngs[i]) + } + } + + }) + + // 馃崅namespace Editable; 馃崅class CircleEditor; 馃崅aka L.Editable.CircleEditor + // 馃崅inherits PathEditor + L.Editable.CircleEditor = L.Editable.PathEditor.extend({ + + MIN_VERTEX: 2, + + options: { + skipMiddleMarkers: true + }, + + initialize: function (map, feature, options) { + L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options) + // C.Y.B Modify + + const latlng = this.computeResizeLatLng() + + // latlng.lat = latlng.lat-0.007855; + // latlng.lng= latlng.lng-0.1; + this._resizeLatLng = latlng + // 鍘熷鏂规硶 + // this._resizeLatLng = this.computeResizeLatLng(); + }, + + computeResizeLatLng: function () { + // While circle is not added to the map, _radius is not set. + // let delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4) + const delta = (this.feature._radius || this.feature._mRadius) + const point = this.map.project(this.feature._latlng) + // return this.map.unproject([point.x + delta, point.y - delta]) + return this.map.unproject([point.x + delta, point.y]) + }, + + updateResizeLatLng: function () { + this._resizeLatLng.update(this.computeResizeLatLng()) + this._resizeLatLng.__vertex.update() + }, + + getLatLngs: function () { + return [this.feature._latlng, this._resizeLatLng] + }, + + getDefaultLatLngs: function () { + return this.getLatLngs() + }, + + onVertexMarkerDrag: function (e) { + if (e.vertex.getIndex() === 1) this.resize(e) + else this.updateResizeLatLng(e) + L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e) + }, + + resize: function (e) { + const radius = this.feature._latlng.distanceTo(e.latlng) + this.feature.setRadius(radius) + }, + + onDrawingMouseDown: function (e) { + L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e) + this._resizeLatLng.update(e.latlng) + this.feature._latlng.update(e.latlng) + this.connect() + // Stop dragging map. + e.originalEvent._simulated = false + this.map.dragging._draggable._onUp(e.originalEvent) + // Now transfer ongoing drag action to the radius handler. + this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent) + }, + + onDrawingMouseUp: function (e) { + this.commitDrawing(e) + e.originalEvent._simulated = false + L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e) + }, + + onDrawingMouseMove: function (e) { + e.originalEvent._simulated = false + L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e) + }, + + onDrag: function (e) { + L.Editable.PathEditor.prototype.onDrag.call(this, e) + this.feature.dragging.updateLatLng(this._resizeLatLng) + } + + }) + + // 馃崅namespace Editable; 馃崅class EditableMixin + // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle` + // and `L.Marker`. It adds some methods to them. + // *When editing is enabled, the editor is accessible on the instance with the + // `editor` property.* + const EditableMixin = { + + createEditor: function (map) { + map = map || this._map + const tools = (this.options.editOptions || {}).editTools || map.editTools + if (!tools) throw Error('Unable to detect Editable instance.') + const Klass = this.options.editorClass || this.getEditorClass(tools) + return new Klass(map, this, this.options.editOptions) + }, + + // 馃崅method enableEdit(map?: L.Map): this.editor + // Enable editing, by creating an editor if not existing, and then calling `enable` on it. + enableEdit: function (map) { + if (!this.editor) this.createEditor(map) + this.editor.enable() + return this.editor + }, + + // 馃崅method editEnabled(): boolean + // Return true if current instance has an editor attached, and this editor is enabled. + editEnabled: function () { + return this.editor && this.editor.enabled() + }, + + // 馃崅method disableEdit() + // Disable editing, also remove the editor property reference. + disableEdit: function () { + if (this.editor) { + this.editor.disable() + delete this.editor + } + }, + + // 馃崅method toggleEdit() + // Enable or disable editing, according to current status. + toggleEdit: function () { + if (this.editEnabled()) this.disableEdit() + else this.enableEdit() + }, + + _onEditableAdd: function () { + if (this.editor) this.enableEdit() + } + + } + + const PolylineMixin = { + + getEditorClass: function (tools) { + return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L.Editable.PolylineEditor + }, + + shapeAt: function (latlng, latlngs) { + // We can have those cases: + // - latlngs are just a flat array of latlngs, use this + // - latlngs is an array of arrays of latlngs, loop over + let shape = null + latlngs = latlngs || this._latlngs + if (!latlngs.length) return shape + else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs + else for (let i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i] + return shape + }, + + isInLatLngs: function (l, latlngs) { + if (!latlngs) return false + let i, k, len + let part = [] + const w = this._clickTolerance() + this._projectLatlngs(latlngs, part, this._pxBounds) + part = part[0] + const p = this._map.latLngToLayerPoint(l) + + if (!this._pxBounds.contains(p)) { + return false + } + for (i = 1, len = part.length, k = 0; i < len; k = i++) { + if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) { + return true + } + } + return false + } + + } + + const PolygonMixin = { + + getEditorClass: function (tools) { + return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L.Editable.PolygonEditor + }, + + shapeAt: function (latlng, latlngs) { + // We can have those cases: + // - latlngs are just a flat array of latlngs, use this + // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first + // - latlngs is an array of arrays of arrays, this is a multi, loop over + let shape = null + latlngs = latlngs || this._latlngs + if (!latlngs.length) return shape + else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs + else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs + else for (let i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i] + return shape + }, + + isInLatLngs: function (l, latlngs) { + let inside = false + let l1 + let l2 + let j + let k + let len2 + for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) { + l1 = latlngs[j] + l2 = latlngs[k] + + if (((l1.lat > l.lat) !== (l2.lat > l.lat)) && (l.lng < (l2.lng - l1.lng) * (l.lat - l1.lat) / (l2.lat - l1.lat) + l1.lng)) { - inside = !inside - } - } - - return inside - }, - - parentShape: function(shape, latlngs) { - latlngs = latlngs || this._latlngs - if (!latlngs) return - let idx = L.Util.indexOf(latlngs, shape) - if (idx !== -1) return latlngs - for (let i = 0; i < latlngs.length; i++) { - idx = L.Util.indexOf(latlngs[i], shape) - if (idx !== -1) return latlngs[i] - } - } - + inside = !inside + } } - let MarkerMixin = { + return inside + }, - getEditorClass: function(tools) { - return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L.Editable.MarkerEditor - } - + parentShape: function (shape, latlngs) { + latlngs = latlngs || this._latlngs + if (!latlngs) return + let idx = L.Util.indexOf(latlngs, shape) + if (idx !== -1) return latlngs + for (let i = 0; i < latlngs.length; i++) { + idx = L.Util.indexOf(latlngs[i], shape) + if (idx !== -1) return latlngs[i] } + } - let RectangleMixin = { + } - getEditorClass: function(tools) { - return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L.Editable.RectangleEditor - } + const MarkerMixin = { - } + getEditorClass: function (tools) { + return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L.Editable.MarkerEditor + } - let CircleMixin = { + } - getEditorClass: function(tools) { - return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L.Editable.CircleEditor - } + const RectangleMixin = { - } + getEditorClass: function (tools) { + return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L.Editable.RectangleEditor + } - let keepEditable = function() { - // Make sure you can remove/readd an editable layer. - this.on('add', this._onEditableAdd) - } + } - let isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat // <=> 1.1 compat. + const CircleMixin = { - if (L.Polyline) { - L.Polyline.include(EditableMixin) - L.Polyline.include(PolylineMixin) - L.Polyline.addInitHook(keepEditable) - } - if (L.Polygon) { - L.Polygon.include(EditableMixin) - L.Polygon.include(PolygonMixin) - } - if (L.Marker) { - L.Marker.include(EditableMixin) - L.Marker.include(MarkerMixin) - L.Marker.addInitHook(keepEditable) - } - if (L.Rectangle) { - L.Rectangle.include(EditableMixin) - L.Rectangle.include(RectangleMixin) - } - if (L.Circle) { - L.Circle.include(EditableMixin) - L.Circle.include(CircleMixin) - } + getEditorClass: function (tools) { + return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L.Editable.CircleEditor + } - L.LatLng.prototype.update = function(latlng) { - latlng = L.latLng(latlng) - this.lat = latlng.lat - this.lng = latlng.lng - } - }, window)) + } + + const keepEditable = function () { + // Make sure you can remove/readd an editable layer. + this.on('add', this._onEditableAdd) + } + + const isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat // <=> 1.1 compat. + + if (L.Polyline) { + L.Polyline.include(EditableMixin) + L.Polyline.include(PolylineMixin) + L.Polyline.addInitHook(keepEditable) + } + if (L.Polygon) { + L.Polygon.include(EditableMixin) + L.Polygon.include(PolygonMixin) + } + if (L.Marker) { + L.Marker.include(EditableMixin) + L.Marker.include(MarkerMixin) + L.Marker.addInitHook(keepEditable) + } + if (L.Rectangle) { + L.Rectangle.include(EditableMixin) + L.Rectangle.include(RectangleMixin) + } + if (L.Circle) { + L.Circle.include(EditableMixin) + L.Circle.include(CircleMixin) + } + + L.LatLng.prototype.update = function (latlng) { + latlng = L.latLng(latlng) + this.lat = latlng.lat + this.lng = latlng.lng + } + }, window)) } export default { - init + init } diff --git a/src/components/plugin/MagicMarker.js b/src/components/plugin/MagicMarker.js index 5060402..f74a2a4 100644 --- a/src/components/plugin/MagicMarker.js +++ b/src/components/plugin/MagicMarker.js @@ -1,49 +1,49 @@ const init = (L) => { - (function(window) { - console.log(window) - let setOptions = function(obj, options) { - for (let i in options) { - obj[i] = options[i] - } - return obj + (function (window) { + console.log(window) + const setOptions = function (obj, options) { + for (const i in options) { + obj[i] = options[i] + } + return obj + } + L.Icon.Magic = function (options) { + let opts + if (options.iconUrl) { + opts = { + html: "<div class='magicDiv'><div class='magictime " + options.magic + "'><img id='migic' src='" + options.iconUrl + "'/></div></div>" + // className: 'magicDiv', } - L.Icon.Magic = function(options) { - let opts - if (options.iconUrl) { - opts = { - html: "<div class='magicDiv'><div class='magictime " + options.magic + "'><img id='migic' src='" + options.iconUrl + "'/></div></div>" - // className: 'magicDiv', - } - } else { - opts = { - html: "<div class='magicDiv'><div class='magictime " + options.magic + "'>" + options.html + '</div></div>' - // className: 'magicDiv', - } - } - delete options.html - let magicIconOpts = setOptions(opts, options) - // console.log(magicIconOpts) - let magicIcon = L.divIcon(magicIconOpts) - return magicIcon + } else { + opts = { + html: "<div class='magicDiv'><div class='magictime " + options.magic + "'>" + options.html + '</div></div>' + // className: 'magicDiv', } + } + delete options.html + const magicIconOpts = setOptions(opts, options) + // console.log(magicIconOpts) + const magicIcon = L.divIcon(magicIconOpts) + return magicIcon + } - L.icon.magic = function(options) { - return new L.Icon.Magic(options) - } + L.icon.magic = function (options) { + return new L.Icon.Magic(options) + } - L.Marker.Magic = L.Marker.extend({ - initialize: function(latlng, options) { - options.icon = L.icon.magic(options) - L.Marker.prototype.initialize.call(this, latlng, options) - } - }) + L.Marker.Magic = L.Marker.extend({ + initialize: function (latlng, options) { + options.icon = L.icon.magic(options) + L.Marker.prototype.initialize.call(this, latlng, options) + } + }) - L.marker.magic = function(latlng, options) { - return new L.Marker.Magic(latlng, options) - } - })(window) + L.marker.magic = function (latlng, options) { + return new L.Marker.Magic(latlng, options) + } + })(window) } export default { - init + init } diff --git a/src/components/plugin/PathDashFlow.js b/src/components/plugin/PathDashFlow.js index 52792fc..1432190 100644 --- a/src/components/plugin/PathDashFlow.js +++ b/src/components/plugin/PathDashFlow.js @@ -1,43 +1,43 @@ // @class PolyLine import * as L from 'leaflet' -let DashFlow = () => { - L.Path.mergeOptions({ - // @option dashSpeed: Number - // The speed of the dash array, in pixels per second - dashSpeed: 0 - }) +const DashFlow = () => { + L.Path.mergeOptions({ + // @option dashSpeed: Number + // The speed of the dash array, in pixels per second + dashSpeed: 0 + }) - var _originalBeforeAdd = L.Path.prototype.beforeAdd + var _originalBeforeAdd = L.Path.prototype.beforeAdd - L.Path.include({ + L.Path.include({ - beforeAdd: function(map) { - _originalBeforeAdd.bind(this)(map) + beforeAdd: function (map) { + _originalBeforeAdd.bind(this)(map) - if (this.options.dashSpeed) { - this._lastDashFrame = performance.now() - this._dashFrame = L.Util.requestAnimFrame(this._onDashFrame.bind(this)) - } - }, + if (this.options.dashSpeed) { + this._lastDashFrame = performance.now() + this._dashFrame = L.Util.requestAnimFrame(this._onDashFrame.bind(this)) + } + }, - _onDashFrame: function() { - if (!this._renderer) { - return - } + _onDashFrame: function () { + if (!this._renderer) { + return + } - var now = performance.now() - var dashOffsetDelta = (now - this._lastDashFrame) * this.options.dashSpeed / 1000 + var now = performance.now() + var dashOffsetDelta = (now - this._lastDashFrame) * this.options.dashSpeed / 1000 - this.options.dashOffset = Number(this.options.dashOffset || 0) + dashOffsetDelta - this._renderer._updateStyle(this) + this.options.dashOffset = Number(this.options.dashOffset || 0) + dashOffsetDelta + this._renderer._updateStyle(this) - this._lastDashFrame = performance.now() + this._lastDashFrame = performance.now() - this._dashFrame = L.Util.requestAnimFrame(this._onDashFrame.bind(this)) - } + this._dashFrame = L.Util.requestAnimFrame(this._onDashFrame.bind(this)) + } - }) + }) } export default { DashFlow } diff --git a/src/components/plugin/PathDrag.js b/src/components/plugin/PathDrag.js index c4f2eea..88998a5 100644 --- a/src/components/plugin/PathDrag.js +++ b/src/components/plugin/PathDrag.js @@ -1,143 +1,143 @@ 'use strict' const init = (L) => { - /* A Draggable that does not update the element position + /* A Draggable that does not update the element position and takes care of only bubbling to targetted path in Canvas mode. */ - L.PathDraggable = L.Draggable.extend({ + L.PathDraggable = L.Draggable.extend({ - initialize: function(path) { - this._path = path - this._canvas = (path._map.getRenderer(path) instanceof L.Canvas) - let element = this._canvas ? this._path._map.getRenderer(this._path)._container : this._path._path - L.Draggable.prototype.initialize.call(this, element, element, true) - }, + initialize: function (path) { + this._path = path + this._canvas = (path._map.getRenderer(path) instanceof L.Canvas) + const element = this._canvas ? this._path._map.getRenderer(this._path)._container : this._path._path + L.Draggable.prototype.initialize.call(this, element, element, true) + }, - _updatePosition: function() { - let e = { originalEvent: this._lastEvent } - this.fire('drag', e) - }, + _updatePosition: function () { + const e = { originalEvent: this._lastEvent } + this.fire('drag', e) + }, - _onDown: function(e) { - let first = e.touches ? e.touches[0] : e - this._startPoint = new L.Point(first.clientX, first.clientY) - if (this._canvas && !this._path._containsPoint(this._path._map.mouseEventToLayerPoint(first))) { - return - } - L.Draggable.prototype._onDown.call(this, e) + _onDown: function (e) { + const first = e.touches ? e.touches[0] : e + this._startPoint = new L.Point(first.clientX, first.clientY) + if (this._canvas && !this._path._containsPoint(this._path._map.mouseEventToLayerPoint(first))) { + return + } + L.Draggable.prototype._onDown.call(this, e) + } + + }) + + L.Handler.PathDrag = L.Handler.extend({ + + initialize: function (path) { + this._path = path + }, + + getEvents: function () { + return { + dragstart: this._onDragStart, + drag: this._onDrag, + dragend: this._onDragEnd + } + }, + + addHooks: function () { + if (!this._draggable) { + this._draggable = new L.PathDraggable(this._path) + } + this._draggable.on(this.getEvents(), this).enable() + L.DomUtil.addClass(this._draggable._element, 'leaflet-path-draggable') + }, + + removeHooks: function () { + this._draggable.off(this.getEvents(), this).disable() + L.DomUtil.removeClass(this._draggable._element, 'leaflet-path-draggable') + }, + + moved: function () { + return this._draggable && this._draggable._moved + }, + + _onDragStart: function () { + this._startPoint = this._draggable._startPoint + this._path + .closePopup() + .fire('movestart') + .fire('dragstart') + }, + + _onDrag: function (e) { + const path = this._path + const event = (e.originalEvent.touches && e.originalEvent.touches.length === 1 ? e.originalEvent.touches[0] : e.originalEvent) + const newPoint = L.point(event.clientX, event.clientY) + const latlng = path._map.layerPointToLatLng(newPoint) + + this._offset = newPoint.subtract(this._startPoint) + this._startPoint = newPoint + + this._path.eachLatLng(this.updateLatLng, this) + path.redraw() + + e.latlng = latlng + e.offset = this._offset + path.fire('drag', e) + e.latlng = this._path.getCenter ? this._path.getCenter() : this._path.getLatLng() + path.fire('move', e) + }, + + _onDragEnd: function (e) { + if (this._path._bounds) this.resetBounds() + this._path.fire('moveend') + .fire('dragend', e) + }, + + latLngToLayerPoint: function (latlng) { + // Same as map.latLngToLayerPoint, but without the round(). + const projectedPoint = this._path._map.project(L.latLng(latlng)) + return projectedPoint._subtract(this._path._map.getPixelOrigin()) + }, + + updateLatLng: function (latlng) { + const oldPoint = this.latLngToLayerPoint(latlng) + oldPoint._add(this._offset) + const newLatLng = this._path._map.layerPointToLatLng(oldPoint) + latlng.lat = newLatLng.lat + latlng.lng = newLatLng.lng + }, + + resetBounds: function () { + this._path._bounds = new L.LatLngBounds() + this._path.eachLatLng(function (latlng) { + this._bounds.extend(latlng) + }) + } + + }) + + L.Path.include({ + + eachLatLng: function (callback, context) { + context = context || this + const loop = function (latlngs) { + for (let i = 0; i < latlngs.length; i++) { + if (L.Util.isArray(latlngs[i])) loop(latlngs[i]) + else callback.call(context, latlngs[i]) } + } + loop(this.getLatLngs ? this.getLatLngs() : [this.getLatLng()]) + } - }) + }) - L.Handler.PathDrag = L.Handler.extend({ - - initialize: function(path) { - this._path = path - }, - - getEvents: function() { - return { - dragstart: this._onDragStart, - drag: this._onDrag, - dragend: this._onDragEnd - } - }, - - addHooks: function() { - if (!this._draggable) { - this._draggable = new L.PathDraggable(this._path) - } - this._draggable.on(this.getEvents(), this).enable() - L.DomUtil.addClass(this._draggable._element, 'leaflet-path-draggable') - }, - - removeHooks: function() { - this._draggable.off(this.getEvents(), this).disable() - L.DomUtil.removeClass(this._draggable._element, 'leaflet-path-draggable') - }, - - moved: function() { - return this._draggable && this._draggable._moved - }, - - _onDragStart: function() { - this._startPoint = this._draggable._startPoint - this._path - .closePopup() - .fire('movestart') - .fire('dragstart') - }, - - _onDrag: function(e) { - let path = this._path - let event = (e.originalEvent.touches && e.originalEvent.touches.length === 1 ? e.originalEvent.touches[0] : e.originalEvent) - let newPoint = L.point(event.clientX, event.clientY) - let latlng = path._map.layerPointToLatLng(newPoint) - - this._offset = newPoint.subtract(this._startPoint) - this._startPoint = newPoint - - this._path.eachLatLng(this.updateLatLng, this) - path.redraw() - - e.latlng = latlng - e.offset = this._offset - path.fire('drag', e) - e.latlng = this._path.getCenter ? this._path.getCenter() : this._path.getLatLng() - path.fire('move', e) - }, - - _onDragEnd: function(e) { - if (this._path._bounds) this.resetBounds() - this._path.fire('moveend') - .fire('dragend', e) - }, - - latLngToLayerPoint: function(latlng) { - // Same as map.latLngToLayerPoint, but without the round(). - let projectedPoint = this._path._map.project(L.latLng(latlng)) - return projectedPoint._subtract(this._path._map.getPixelOrigin()) - }, - - updateLatLng: function(latlng) { - let oldPoint = this.latLngToLayerPoint(latlng) - oldPoint._add(this._offset) - let newLatLng = this._path._map.layerPointToLatLng(oldPoint) - latlng.lat = newLatLng.lat - latlng.lng = newLatLng.lng - }, - - resetBounds: function() { - this._path._bounds = new L.LatLngBounds() - this._path.eachLatLng(function(latlng) { - this._bounds.extend(latlng) - }) - } - - }) - - L.Path.include({ - - eachLatLng: function(callback, context) { - context = context || this - let loop = function(latlngs) { - for (let i = 0; i < latlngs.length; i++) { - if (L.Util.isArray(latlngs[i])) loop(latlngs[i]) - else callback.call(context, latlngs[i]) - } - } - loop(this.getLatLngs ? this.getLatLngs() : [this.getLatLng()]) - } - - }) - - L.Path.addInitHook(function() { - this.dragging = new L.Handler.PathDrag(this) - if (this.options.draggable) { - this.once('add', function() { - this.dragging.enable() - }) - } - }) + L.Path.addInitHook(function () { + this.dragging = new L.Handler.PathDrag(this) + if (this.options.draggable) { + this.once('add', function () { + this.dragging.enable() + }) + } + }) } export default { - init + init } diff --git a/src/components/plugin/cluster-layer/leaflet.markercluster-src.js b/src/components/plugin/cluster-layer/leaflet.markercluster-src.js index 85e3882..e0cfa31 100644 --- a/src/components/plugin/cluster-layer/leaflet.markercluster-src.js +++ b/src/components/plugin/cluster-layer/leaflet.markercluster-src.js @@ -1,1095 +1,1086 @@ -'use strict'; -function init(L) { - /* +'use strict' +function init (L) { + /* * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within */ - var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({ + var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({ - options: { - maxClusterRadius: 80, // A cluster will cover at most this many pixels from its center - iconCreateFunction: null, - clusterPane: L.Marker.prototype.options.pane, + options: { + maxClusterRadius: 80, // A cluster will cover at most this many pixels from its center + iconCreateFunction: null, + clusterPane: L.Marker.prototype.options.pane, - spiderfyOnMaxZoom: true, - showCoverageOnHover: true, - zoomToBoundsOnClick: true, - singleMarkerMode: false, + spiderfyOnMaxZoom: true, + showCoverageOnHover: true, + zoomToBoundsOnClick: true, + singleMarkerMode: false, - disableClusteringAtZoom: null, + disableClusteringAtZoom: null, - // Setting this to false prevents the removal of any clusters outside of the viewpoint, which - // is the default behaviour for performance reasons. - removeOutsideVisibleBounds: true, + // Setting this to false prevents the removal of any clusters outside of the viewpoint, which + // is the default behaviour for performance reasons. + removeOutsideVisibleBounds: true, - // Set to false to disable all animations (zoom and spiderfy). - // If false, option animateAddingMarkers below has no effect. - // If L.DomUtil.TRANSITION is falsy, this option has no effect. - animate: true, + // Set to false to disable all animations (zoom and spiderfy). + // If false, option animateAddingMarkers below has no effect. + // If L.DomUtil.TRANSITION is falsy, this option has no effect. + animate: true, - // Whether to animate adding markers after adding the MarkerClusterGroup to the map - // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains. - animateAddingMarkers: false, + // Whether to animate adding markers after adding the MarkerClusterGroup to the map + // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains. + animateAddingMarkers: false, - // Increase to increase the distance away that spiderfied markers appear from the center - spiderfyDistanceMultiplier: 1, + // Increase to increase the distance away that spiderfied markers appear from the center + spiderfyDistanceMultiplier: 1, - // Make it possible to specify a polyline options on a spider leg - spiderLegPolylineOptions: {weight: 1.5, color: '#222', opacity: 0.5}, + // Make it possible to specify a polyline options on a spider leg + spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 }, - // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts - chunkedLoading: false, - chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback) - chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser - chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator) + // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts + chunkedLoading: false, + chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback) + chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser + chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator) - // Options to pass to the L.Polygon constructor - polygonOptions: {} - }, + // Options to pass to the L.Polygon constructor + polygonOptions: {} + }, - initialize: function(options) { - L.Util.setOptions(this, options); - if (!this.options.iconCreateFunction) { - this.options.iconCreateFunction = this._defaultIconCreateFunction; + initialize: function (options) { + L.Util.setOptions(this, options) + if (!this.options.iconCreateFunction) { + this.options.iconCreateFunction = this._defaultIconCreateFunction + } + + this._featureGroup = L.featureGroup() + this._featureGroup.addEventParent(this) + + this._nonPointGroup = L.featureGroup() + this._nonPointGroup.addEventParent(this) + + this._inZoomAnimation = 0 + this._needsClustering = [] + this._needsRemoving = [] // Markers removed while we aren't on the map need to be kept track of + // The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move + this._currentShownBounds = null + + this._queue = [] + + this._childMarkerEventHandlers = { + dragstart: this._childMarkerDragStart, + move: this._childMarkerMoved, + dragend: this._childMarkerDragEnd + } + + // Hook the appropriate animation methods. + var animate = L.DomUtil.TRANSITION && this.options.animate + L.extend(this, animate ? this._withAnimation : this._noAnimation) + // Remember which MarkerCluster class to instantiate (animated or not). + this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated + }, + + addLayer: function (layer) { + if (layer instanceof L.LayerGroup) { + return this.addLayers([layer]) + } + + // Don't cluster non point data + if (!layer.getLatLng) { + this._nonPointGroup.addLayer(layer) + this.fire('layeradd', { layer: layer }) + return this + } + + if (!this._map) { + this._needsClustering.push(layer) + this.fire('layeradd', { layer: layer }) + return this + } + + if (this.hasLayer(layer)) { + return this + } + + // If we have already clustered we'll need to add this one to a cluster + + if (this._unspiderfy) { + this._unspiderfy() + } + + this._addLayer(layer, this._maxZoom) + this.fire('layeradd', { layer: layer }) + + // Refresh bounds and weighted positions. + this._topClusterLevel._recalculateBounds() + + this._refreshClustersIcons() + + // Work out what is visible + var visibleLayer = layer + var currentZoom = this._zoom + if (layer.__parent) { + while (visibleLayer.__parent._zoom >= currentZoom) { + visibleLayer = visibleLayer.__parent + } + } + + if (this._currentShownBounds.contains(visibleLayer.getLatLng())) { + if (this.options.animateAddingMarkers) { + this._animationAddLayer(layer, visibleLayer) + } else { + this._animationAddLayerNonAnimated(layer, visibleLayer) + } + } + return this + }, + + removeLayer: function (layer) { + if (layer instanceof L.LayerGroup) { + return this.removeLayers([layer]) + } + + // Non point layers + if (!layer.getLatLng) { + this._nonPointGroup.removeLayer(layer) + this.fire('layerremove', { layer: layer }) + return this + } + + if (!this._map) { + if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) { + this._needsRemoving.push({ layer: layer, latlng: layer._latlng }) + } + this.fire('layerremove', { layer: layer }) + return this + } + + if (!layer.__parent) { + return this + } + + if (this._unspiderfy) { + this._unspiderfy() + this._unspiderfyLayer(layer) + } + + // Remove the marker from clusters + this._removeLayer(layer, true) + this.fire('layerremove', { layer: layer }) + + // Refresh bounds and weighted positions. + this._topClusterLevel._recalculateBounds() + + this._refreshClustersIcons() + + layer.off(this._childMarkerEventHandlers, this) + + if (this._featureGroup.hasLayer(layer)) { + this._featureGroup.removeLayer(layer) + if (layer.clusterShow) { + layer.clusterShow() + } + } + + return this + }, + + // Takes an array of markers and adds them in bulk + addLayers: function (layersArray, skipLayerAddEvent) { + if (!L.Util.isArray(layersArray)) { + return this.addLayer(layersArray) + } + + var fg = this._featureGroup + var npg = this._nonPointGroup + var chunked = this.options.chunkedLoading + var chunkInterval = this.options.chunkInterval + var chunkProgress = this.options.chunkProgress + var l = layersArray.length + var offset = 0 + var originalArray = true + var m + + if (this._map) { + var started = (new Date()).getTime() + var process = L.bind(function () { + var start = (new Date()).getTime() + for (; offset < l; offset++) { + if (chunked && offset % 200 === 0) { + // every couple hundred markers, instrument the time elapsed since processing started: + var elapsed = (new Date()).getTime() - start + if (elapsed > chunkInterval) { + break // been working too hard, time to take a break :-) + } } - this._featureGroup = L.featureGroup(); - this._featureGroup.addEventParent(this); + m = layersArray[offset] - this._nonPointGroup = L.featureGroup(); - this._nonPointGroup.addEventParent(this); - - this._inZoomAnimation = 0; - this._needsClustering = []; - this._needsRemoving = []; // Markers removed while we aren't on the map need to be kept track of - // The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move - this._currentShownBounds = null; - - this._queue = []; - - this._childMarkerEventHandlers = { - 'dragstart': this._childMarkerDragStart, - 'move': this._childMarkerMoved, - 'dragend': this._childMarkerDragEnd, - }; - - // Hook the appropriate animation methods. - var animate = L.DomUtil.TRANSITION && this.options.animate; - L.extend(this, animate ? this._withAnimation : this._noAnimation); - // Remember which MarkerCluster class to instantiate (animated or not). - this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated; - }, - - addLayer: function(layer) { - - if (layer instanceof L.LayerGroup) { - return this.addLayers([layer]); + // Group of layers, append children to layersArray and skip. + // Side effects: + // - Total increases, so chunkProgress ratio jumps backward. + // - Groups are not included in this group, only their non-group child layers (hasLayer). + // Changing array length while looping does not affect performance in current browsers: + // http:// jsperf.com/for-loop-changing-length/6 + if (m instanceof L.LayerGroup) { + if (originalArray) { + layersArray = layersArray.slice() + originalArray = false + } + this._extractNonGroupLayers(m, layersArray) + l = layersArray.length + continue } - // Don't cluster non point data - if (!layer.getLatLng) { - this._nonPointGroup.addLayer(layer); - this.fire('layeradd', {layer: layer}); - return this; + // Not point data, can't be clustered + if (!m.getLatLng) { + npg.addLayer(m) + if (!skipLayerAddEvent) { + this.fire('layeradd', { layer: m }) + } + continue } - if (!this._map) { - this._needsClustering.push(layer); - this.fire('layeradd', {layer: layer}); - return this; + if (this.hasLayer(m)) { + continue } - if (this.hasLayer(layer)) { - return this; + this._addLayer(m, this._maxZoom) + if (!skipLayerAddEvent) { + this.fire('layeradd', { layer: m }) } - - // If we have already clustered we'll need to add this one to a cluster - - if (this._unspiderfy) { - this._unspiderfy(); + // If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will + if (m.__parent) { + if (m.__parent.getChildCount() === 2) { + var markers = m.__parent.getAllChildMarkers() + var otherMarker = markers[0] === m ? markers[1] : markers[0] + fg.removeLayer(otherMarker) + } } + } - this._addLayer(layer, this._maxZoom); - this.fire('layeradd', {layer: layer}); + if (chunkProgress) { + // report progress and time elapsed: + chunkProgress(offset, l, (new Date()).getTime() - started) + } + // Completed processing all markers. + if (offset === l) { // Refresh bounds and weighted positions. - this._topClusterLevel._recalculateBounds(); + this._topClusterLevel._recalculateBounds() - this._refreshClustersIcons(); + this._refreshClustersIcons() - // Work out what is visible - var visibleLayer = layer, - currentZoom = this._zoom; - if (layer.__parent) { - while (visibleLayer.__parent._zoom >= currentZoom) { - visibleLayer = visibleLayer.__parent; - } + this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds) + } else { + setTimeout(process, this.options.chunkDelay) + } + }, this) + + process() + } else { + var needsClustering = this._needsClustering + + for (; offset < l; offset++) { + m = layersArray[offset] + + // Group of layers, append children to layersArray and skip. + if (m instanceof L.LayerGroup) { + if (originalArray) { + layersArray = layersArray.slice() + originalArray = false } + this._extractNonGroupLayers(m, layersArray) + l = layersArray.length + continue + } - if (this._currentShownBounds.contains(visibleLayer.getLatLng())) { - if (this.options.animateAddingMarkers) { - this._animationAddLayer(layer, visibleLayer); - } else { - this._animationAddLayerNonAnimated(layer, visibleLayer); - } + // Not point data, can't be clustered + if (!m.getLatLng) { + npg.addLayer(m) + continue + } + + if (this.hasLayer(m)) { + continue + } + + needsClustering.push(m) + } + } + return this + }, + + // Takes an array of markers and removes them in bulk + removeLayers: function (layersArray) { + var i; var m + var l = layersArray.length + var fg = this._featureGroup + var npg = this._nonPointGroup + var originalArray = true + + if (!this._map) { + for (i = 0; i < l; i++) { + m = layersArray[i] + + // Group of layers, append children to layersArray and skip. + if (m instanceof L.LayerGroup) { + if (originalArray) { + layersArray = layersArray.slice() + originalArray = false } - return this; - }, + this._extractNonGroupLayers(m, layersArray) + l = layersArray.length + continue + } - removeLayer: function(layer) { + this._arraySplice(this._needsClustering, m) + npg.removeLayer(m) + if (this.hasLayer(m)) { + this._needsRemoving.push({ layer: m, latlng: m._latlng }) + } + this.fire('layerremove', { layer: m }) + } + return this + } - if (layer instanceof L.LayerGroup) { - return this.removeLayers([layer]); - } + if (this._unspiderfy) { + this._unspiderfy() - // Non point layers - if (!layer.getLatLng) { - this._nonPointGroup.removeLayer(layer); - this.fire('layerremove', {layer: layer}); - return this; - } + // Work on a copy of the array, so that next loop is not affected. + var layersArray2 = layersArray.slice() + var l2 = l + for (i = 0; i < l2; i++) { + m = layersArray2[i] - if (!this._map) { - if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) { - this._needsRemoving.push({layer: layer, latlng: layer._latlng}); - } - this.fire('layerremove', {layer: layer}); - return this; - } + // Group of layers, append children to layersArray and skip. + if (m instanceof L.LayerGroup) { + this._extractNonGroupLayers(m, layersArray2) + l2 = layersArray2.length + continue + } - if (!layer.__parent) { - return this; - } + this._unspiderfyLayer(m) + } + } - if (this._unspiderfy) { - this._unspiderfy(); - this._unspiderfyLayer(layer); - } + for (i = 0; i < l; i++) { + m = layersArray[i] - // Remove the marker from clusters - this._removeLayer(layer, true); - this.fire('layerremove', {layer: layer}); + // Group of layers, append children to layersArray and skip. + if (m instanceof L.LayerGroup) { + if (originalArray) { + layersArray = layersArray.slice() + originalArray = false + } + this._extractNonGroupLayers(m, layersArray) + l = layersArray.length + continue + } - // Refresh bounds and weighted positions. - this._topClusterLevel._recalculateBounds(); + if (!m.__parent) { + npg.removeLayer(m) + this.fire('layerremove', { layer: m }) + continue + } - this._refreshClustersIcons(); + this._removeLayer(m, true, true) + this.fire('layerremove', { layer: m }) - layer.off(this._childMarkerEventHandlers, this); + if (fg.hasLayer(m)) { + fg.removeLayer(m) + if (m.clusterShow) { + m.clusterShow() + } + } + } - if (this._featureGroup.hasLayer(layer)) { - this._featureGroup.removeLayer(layer); - if (layer.clusterShow) { - layer.clusterShow(); - } - } + // Refresh bounds and weighted positions. + this._topClusterLevel._recalculateBounds() - return this; - }, + this._refreshClustersIcons() - // Takes an array of markers and adds them in bulk - addLayers: function(layersArray, skipLayerAddEvent) { - if (!L.Util.isArray(layersArray)) { - return this.addLayer(layersArray); - } + // Fix up the clusters and markers on the map + this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds) - var fg = this._featureGroup, - npg = this._nonPointGroup, - chunked = this.options.chunkedLoading, - chunkInterval = this.options.chunkInterval, - chunkProgress = this.options.chunkProgress, - l = layersArray.length, - offset = 0, - originalArray = true, - m; + return this + }, - if (this._map) { - var started = (new Date()).getTime(); - var process = L.bind(function() { - var start = (new Date()).getTime(); - for (; offset < l; offset++) { - if (chunked && offset % 200 === 0) { - // every couple hundred markers, instrument the time elapsed since processing started: - var elapsed = (new Date()).getTime() - start; - if (elapsed > chunkInterval) { - break; // been working too hard, time to take a break :-) - } - } + // Removes all layers from the MarkerClusterGroup + clearLayers: function () { + // Need our own special implementation as the LayerGroup one doesn't work for us - m = layersArray[offset]; + // If we aren't on the map (yet), blow away the markers we know of + if (!this._map) { + this._needsClustering = [] + this._needsRemoving = [] + delete this._gridClusters + delete this._gridUnclustered + } - // Group of layers, append children to layersArray and skip. - // Side effects: - // - Total increases, so chunkProgress ratio jumps backward. - // - Groups are not included in this group, only their non-group child layers (hasLayer). - // Changing array length while looping does not affect performance in current browsers: - // http:// jsperf.com/for-loop-changing-length/6 - if (m instanceof L.LayerGroup) { - if (originalArray) { - layersArray = layersArray.slice(); - originalArray = false; - } - this._extractNonGroupLayers(m, layersArray); - l = layersArray.length; - continue; - } + if (this._noanimationUnspiderfy) { + this._noanimationUnspiderfy() + } - // Not point data, can't be clustered - if (!m.getLatLng) { - npg.addLayer(m); - if (!skipLayerAddEvent) { - this.fire('layeradd', {layer: m}); - } - continue; - } + // Remove all the visible layers + this._featureGroup.clearLayers() + this._nonPointGroup.clearLayers() - if (this.hasLayer(m)) { - continue; - } + this.eachLayer(function (marker) { + marker.off(this._childMarkerEventHandlers, this) + delete marker.__parent + }, this) - this._addLayer(m, this._maxZoom); - if (!skipLayerAddEvent) { - this.fire('layeradd', {layer: m}); - } + if (this._map) { + // Reset _topClusterLevel and the DistanceGrids + this._generateInitialClusters() + } - // If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will - if (m.__parent) { - if (m.__parent.getChildCount() === 2) { - var markers = m.__parent.getAllChildMarkers(), - otherMarker = markers[0] === m ? markers[1] : markers[0]; - fg.removeLayer(otherMarker); - } - } - } + return this + }, - if (chunkProgress) { - // report progress and time elapsed: - chunkProgress(offset, l, (new Date()).getTime() - started); - } + // Override FeatureGroup.getBounds as it doesn't work + getBounds: function () { + var bounds = new L.LatLngBounds() - // Completed processing all markers. - if (offset === l) { + if (this._topClusterLevel) { + bounds.extend(this._topClusterLevel._bounds) + } - // Refresh bounds and weighted positions. - this._topClusterLevel._recalculateBounds(); + for (var i = this._needsClustering.length - 1; i >= 0; i--) { + bounds.extend(this._needsClustering[i].getLatLng()) + } - this._refreshClustersIcons(); + bounds.extend(this._nonPointGroup.getBounds()) - this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds); - } else { - setTimeout(process, this.options.chunkDelay); - } - }, this); + return bounds + }, - process(); - } else { - var needsClustering = this._needsClustering; + // Overrides LayerGroup.eachLayer + eachLayer: function (method, context) { + var markers = this._needsClustering.slice() + var needsRemoving = this._needsRemoving + var thisNeedsRemoving; var i; var j - for (; offset < l; offset++) { - m = layersArray[offset]; + if (this._topClusterLevel) { + this._topClusterLevel.getAllChildMarkers(markers) + } - // Group of layers, append children to layersArray and skip. - if (m instanceof L.LayerGroup) { - if (originalArray) { - layersArray = layersArray.slice(); - originalArray = false; - } - this._extractNonGroupLayers(m, layersArray); - l = layersArray.length; - continue; - } + for (i = markers.length - 1; i >= 0; i--) { + thisNeedsRemoving = true - // Not point data, can't be clustered - if (!m.getLatLng) { - npg.addLayer(m); - continue; - } + for (j = needsRemoving.length - 1; j >= 0; j--) { + if (needsRemoving[j].layer === markers[i]) { + thisNeedsRemoving = false + break + } + } - if (this.hasLayer(m)) { - continue; - } + if (thisNeedsRemoving) { + method.call(context, markers[i]) + } + } - needsClustering.push(m); - } - } - return this; - }, + this._nonPointGroup.eachLayer(method, context) + }, - // Takes an array of markers and removes them in bulk - removeLayers: function(layersArray) { - var i, m, - l = layersArray.length, - fg = this._featureGroup, - npg = this._nonPointGroup, - originalArray = true; + // Overrides LayerGroup.getLayers + getLayers: function () { + var layers = [] + this.eachLayer(function (l) { + layers.push(l) + }) + return layers + }, - if (!this._map) { - for (i = 0; i < l; i++) { - m = layersArray[i]; + // Overrides LayerGroup.getLayer, WARNING: Really bad performance + getLayer: function (id) { + var result = null - // Group of layers, append children to layersArray and skip. - if (m instanceof L.LayerGroup) { - if (originalArray) { - layersArray = layersArray.slice(); - originalArray = false; - } - this._extractNonGroupLayers(m, layersArray); - l = layersArray.length; - continue; - } + id = parseInt(id, 10) - this._arraySplice(this._needsClustering, m); - npg.removeLayer(m); - if (this.hasLayer(m)) { - this._needsRemoving.push({layer: m, latlng: m._latlng}); - } - this.fire('layerremove', {layer: m}); - } - return this; - } + this.eachLayer(function (l) { + if (L.stamp(l) === id) { + result = l + } + }) - if (this._unspiderfy) { - this._unspiderfy(); + return result + }, - // Work on a copy of the array, so that next loop is not affected. - var layersArray2 = layersArray.slice(), - l2 = l; - for (i = 0; i < l2; i++) { - m = layersArray2[i]; + // Returns true if the given layer is in this MarkerClusterGroup + hasLayer: function (layer) { + if (!layer) { + return false + } - // Group of layers, append children to layersArray and skip. - if (m instanceof L.LayerGroup) { - this._extractNonGroupLayers(m, layersArray2); - l2 = layersArray2.length; - continue; - } + var i; var anArray = this._needsClustering - this._unspiderfyLayer(m); - } - } + for (i = anArray.length - 1; i >= 0; i--) { + if (anArray[i] === layer) { + return true + } + } - for (i = 0; i < l; i++) { - m = layersArray[i]; + anArray = this._needsRemoving + for (i = anArray.length - 1; i >= 0; i--) { + if (anArray[i].layer === layer) { + return false + } + } - // Group of layers, append children to layersArray and skip. - if (m instanceof L.LayerGroup) { - if (originalArray) { - layersArray = layersArray.slice(); - originalArray = false; - } - this._extractNonGroupLayers(m, layersArray); - l = layersArray.length; - continue; - } + return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer) + }, - if (!m.__parent) { - npg.removeLayer(m); - this.fire('layerremove', {layer: m}); - continue; - } - - this._removeLayer(m, true, true); - this.fire('layerremove', {layer: m}); - - if (fg.hasLayer(m)) { - fg.removeLayer(m); - if (m.clusterShow) { - m.clusterShow(); - } - } - } - - // Refresh bounds and weighted positions. - this._topClusterLevel._recalculateBounds(); - - this._refreshClustersIcons(); - - // Fix up the clusters and markers on the map - this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds); - - return this; - }, - - // Removes all layers from the MarkerClusterGroup - clearLayers: function() { - // Need our own special implementation as the LayerGroup one doesn't work for us - - // If we aren't on the map (yet), blow away the markers we know of - if (!this._map) { - this._needsClustering = []; - this._needsRemoving = []; - delete this._gridClusters; - delete this._gridUnclustered; - } - - if (this._noanimationUnspiderfy) { - this._noanimationUnspiderfy(); - } - - // Remove all the visible layers - this._featureGroup.clearLayers(); - this._nonPointGroup.clearLayers(); - - this.eachLayer(function(marker) { - marker.off(this._childMarkerEventHandlers, this); - delete marker.__parent; - }, this); - - if (this._map) { - // Reset _topClusterLevel and the DistanceGrids - this._generateInitialClusters(); - } - - return this; - }, - - // Override FeatureGroup.getBounds as it doesn't work - getBounds: function() { - var bounds = new L.LatLngBounds(); - - if (this._topClusterLevel) { - bounds.extend(this._topClusterLevel._bounds); - } - - for (var i = this._needsClustering.length - 1; i >= 0; i--) { - bounds.extend(this._needsClustering[i].getLatLng()); - } - - bounds.extend(this._nonPointGroup.getBounds()); - - return bounds; - }, - - // Overrides LayerGroup.eachLayer - eachLayer: function(method, context) { - var markers = this._needsClustering.slice(), - needsRemoving = this._needsRemoving, - thisNeedsRemoving, i, j; - - if (this._topClusterLevel) { - this._topClusterLevel.getAllChildMarkers(markers); - } - - for (i = markers.length - 1; i >= 0; i--) { - thisNeedsRemoving = true; - - for (j = needsRemoving.length - 1; j >= 0; j--) { - if (needsRemoving[j].layer === markers[i]) { - thisNeedsRemoving = false; - break; - } - } - - if (thisNeedsRemoving) { - method.call(context, markers[i]); - } - } - - this._nonPointGroup.eachLayer(method, context); - }, - - // Overrides LayerGroup.getLayers - getLayers: function() { - var layers = []; - this.eachLayer(function(l) { - layers.push(l); - }); - return layers; - }, - - // Overrides LayerGroup.getLayer, WARNING: Really bad performance - getLayer: function(id) { - var result = null; - - id = parseInt(id, 10); - - this.eachLayer(function(l) { - if (L.stamp(l) === id) { - result = l; - } - }); - - return result; - }, - - // Returns true if the given layer is in this MarkerClusterGroup - hasLayer: function(layer) { - if (!layer) { - return false; - } - - var i, anArray = this._needsClustering; - - for (i = anArray.length - 1; i >= 0; i--) { - if (anArray[i] === layer) { - return true; - } - } - - anArray = this._needsRemoving; - for (i = anArray.length - 1; i >= 0; i--) { - if (anArray[i].layer === layer) { - return false; - } - } - - return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer); - }, - - // Zoom down to show the given layer (spiderfying if necessary) then calls the callback - zoomToShowLayer: function(layer, callback) { - - if (typeof callback !== 'function') { - callback = function() { - }; - } + // Zoom down to show the given layer (spiderfying if necessary) then calls the callback + zoomToShowLayer: function (layer, callback) { + if (typeof callback !== 'function') { + callback = function () { + } + } - var showMarker = function() { - if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) { - this._map.off('moveend', showMarker, this); - this.off('animationend', showMarker, this); + var showMarker = function () { + if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) { + this._map.off('moveend', showMarker, this) + this.off('animationend', showMarker, this) - if (layer._icon) { - callback(); - } else if (layer.__parent._icon) { - this.once('spiderfied', callback, this); - layer.__parent.spiderfy(); - } - } - }; + if (layer._icon) { + callback() + } else if (layer.__parent._icon) { + this.once('spiderfied', callback, this) + layer.__parent.spiderfy() + } + } + } - if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) { - // Layer is visible ond on screen, immediate return - callback(); - } else if (layer.__parent._zoom < Math.round(this._map._zoom)) { - // Layer should be visible at this zoom level. It must not be on screen so just pan over to it - this._map.on('moveend', showMarker, this); - this._map.panTo(layer.getLatLng()); - } else { - this._map.on('moveend', showMarker, this); - this.on('animationend', showMarker, this); - layer.__parent.zoomToBounds(); - } - }, + if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) { + // Layer is visible ond on screen, immediate return + callback() + } else if (layer.__parent._zoom < Math.round(this._map._zoom)) { + // Layer should be visible at this zoom level. It must not be on screen so just pan over to it + this._map.on('moveend', showMarker, this) + this._map.panTo(layer.getLatLng()) + } else { + this._map.on('moveend', showMarker, this) + this.on('animationend', showMarker, this) + layer.__parent.zoomToBounds() + } + }, - // Overrides FeatureGroup.onAdd - onAdd: function(map) { - this._map = map; - var i, l, layer; + // Overrides FeatureGroup.onAdd + onAdd: function (map) { + this._map = map + var i, l, layer - if (!isFinite(this._map.getMaxZoom())) { - throw "Map has no maxZoom specified"; - } + if (!isFinite(this._map.getMaxZoom())) { + // eslint-disable-next-line no-throw-literal + throw 'Map has no maxZoom specified' + } - this._featureGroup.addTo(map); - this._nonPointGroup.addTo(map); + this._featureGroup.addTo(map) + this._nonPointGroup.addTo(map) - if (!this._gridClusters) { - this._generateInitialClusters(); - } + if (!this._gridClusters) { + this._generateInitialClusters() + } - this._maxLat = map.options.crs.projection.MAX_LATITUDE; + this._maxLat = map.options.crs.projection.MAX_LATITUDE - // Restore all the positions as they are in the MCG before removing them - for (i = 0, l = this._needsRemoving.length; i < l; i++) { - layer = this._needsRemoving[i]; - layer.newlatlng = layer.layer._latlng; - layer.layer._latlng = layer.latlng; - } - // Remove them, then restore their new positions - for (i = 0, l = this._needsRemoving.length; i < l; i++) { - layer = this._needsRemoving[i]; - this._removeLayer(layer.layer, true); - layer.layer._latlng = layer.newlatlng; - } - this._needsRemoving = []; + // Restore all the positions as they are in the MCG before removing them + for (i = 0, l = this._needsRemoving.length; i < l; i++) { + layer = this._needsRemoving[i] + layer.newlatlng = layer.layer._latlng + layer.layer._latlng = layer.latlng + } + // Remove them, then restore their new positions + for (i = 0, l = this._needsRemoving.length; i < l; i++) { + layer = this._needsRemoving[i] + this._removeLayer(layer.layer, true) + layer.layer._latlng = layer.newlatlng + } + this._needsRemoving = [] - // Remember the current zoom level and bounds - this._zoom = Math.round(this._map._zoom); - this._currentShownBounds = this._getExpandedVisibleBounds(); + // Remember the current zoom level and bounds + this._zoom = Math.round(this._map._zoom) + this._currentShownBounds = this._getExpandedVisibleBounds() - this._map.on('zoomend', this._zoomEnd, this); - this._map.on('moveend', this._moveEnd, this); + this._map.on('zoomend', this._zoomEnd, this) + this._map.on('moveend', this._moveEnd, this) - if (this._spiderfierOnAdd) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely - this._spiderfierOnAdd(); - } + if (this._spiderfierOnAdd) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely + this._spiderfierOnAdd() + } - this._bindEvents(); + this._bindEvents() - // Actually add our markers to the map: - l = this._needsClustering; - this._needsClustering = []; - this.addLayers(l, true); - }, + // Actually add our markers to the map: + l = this._needsClustering + this._needsClustering = [] + this.addLayers(l, true) + }, - // Overrides FeatureGroup.onRemove - onRemove: function(map) { - map.off('zoomend', this._zoomEnd, this); - map.off('moveend', this._moveEnd, this); + // Overrides FeatureGroup.onRemove + onRemove: function (map) { + map.off('zoomend', this._zoomEnd, this) + map.off('moveend', this._moveEnd, this) - this._unbindEvents(); + this._unbindEvents() - // In case we are in a cluster animation - this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', ''); + // In case we are in a cluster animation + this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '') - if (this._spiderfierOnRemove) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely - this._spiderfierOnRemove(); - } + if (this._spiderfierOnRemove) { // TODO FIXME: Not sure how to have spiderfier add something on here nicely + this._spiderfierOnRemove() + } - delete this._maxLat; + delete this._maxLat - // Clean up all the layers we added to the map - this._hideCoverage(); - this._featureGroup.remove(); - this._nonPointGroup.remove(); + // Clean up all the layers we added to the map + this._hideCoverage() + this._featureGroup.remove() + this._nonPointGroup.remove() - this._featureGroup.clearLayers(); + this._featureGroup.clearLayers() - this._map = null; - }, + this._map = null + }, - getVisibleParent: function(marker) { - var vMarker = marker; - while (vMarker && !vMarker._icon) { - vMarker = vMarker.__parent; - } - return vMarker || null; - }, + getVisibleParent: function (marker) { + var vMarker = marker + while (vMarker && !vMarker._icon) { + vMarker = vMarker.__parent + } + return vMarker || null + }, - // Remove the given object from the given array - _arraySplice: function(anArray, obj) { - for (var i = anArray.length - 1; i >= 0; i--) { - if (anArray[i] === obj) { - anArray.splice(i, 1); - return true; - } - } - }, + // Remove the given object from the given array + _arraySplice: function (anArray, obj) { + for (var i = anArray.length - 1; i >= 0; i--) { + if (anArray[i] === obj) { + anArray.splice(i, 1) + return true + } + } + }, - /** + /** * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom. * @param marker to be removed from _gridUnclustered. * @param z integer bottom start zoom level (included) * @private */ - _removeFromGridUnclustered: function(marker, z) { - var map = this._map, - gridUnclustered = this._gridUnclustered, - minZoom = Math.floor(this._map.getMinZoom()); + _removeFromGridUnclustered: function (marker, z) { + var map = this._map + var gridUnclustered = this._gridUnclustered + var minZoom = Math.floor(this._map.getMinZoom()) - for (; z >= minZoom; z--) { - if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) { - break; - } + for (; z >= minZoom; z--) { + if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) { + break + } + } + }, + + _childMarkerDragStart: function (e) { + e.target.__dragStart = e.target._latlng + }, + + _childMarkerMoved: function (e) { + if (!this._ignoreMove && !e.target.__dragStart) { + var isPopupOpen = e.target._popup && e.target._popup.isOpen() + + this._moveChild(e.target, e.oldLatLng, e.latlng) + + if (isPopupOpen) { + e.target.openPopup() + } + } + }, + + _moveChild: function (layer, from, to) { + layer._latlng = from + this.removeLayer(layer) + + layer._latlng = to + this.addLayer(layer) + }, + + _childMarkerDragEnd: function (e) { + var dragStart = e.target.__dragStart + delete e.target.__dragStart + if (dragStart) { + this._moveChild(e.target, dragStart, e.target._latlng) + } + }, + + // Internal function for removing a marker from everything. + // dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions) + _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) { + var gridClusters = this._gridClusters + var gridUnclustered = this._gridUnclustered + var fg = this._featureGroup + var map = this._map + var minZoom = Math.floor(this._map.getMinZoom()) + + // Remove the marker from distance clusters it might be in + if (removeFromDistanceGrid) { + this._removeFromGridUnclustered(marker, this._maxZoom) + } + + // Work our way up the clusters removing them as we go if required + var cluster = marker.__parent + var markers = cluster._markers + var otherMarker + + // Remove the marker from the immediate parents marker list + this._arraySplice(markers, marker) + + while (cluster) { + cluster._childCount-- + cluster._boundsNeedUpdate = true + + if (cluster._zoom < minZoom) { + // Top level, do nothing + break + } else if (removeFromDistanceGrid && cluster._childCount <= 1) { // Cluster no longer required + // We need to push the other marker up to the parent + otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0] + + // Update distance grid + gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom)) + gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom)) + + // Move otherMarker up to parent + this._arraySplice(cluster.__parent._childClusters, cluster) + cluster.__parent._markers.push(otherMarker) + otherMarker.__parent = cluster.__parent + + if (cluster._icon) { + // Cluster is currently on the map, need to put the marker on the map instead + fg.removeLayer(cluster) + if (!dontUpdateMap) { + fg.addLayer(otherMarker) } - }, + } + } else { + cluster._iconNeedsUpdate = true + } - _childMarkerDragStart: function(e) { - e.target.__dragStart = e.target._latlng; - }, + cluster = cluster.__parent + } - _childMarkerMoved: function(e) { - if (!this._ignoreMove && !e.target.__dragStart) { - var isPopupOpen = e.target._popup && e.target._popup.isOpen(); + delete marker.__parent + }, - this._moveChild(e.target, e.oldLatLng, e.latlng); + _isOrIsParent: function (el, oel) { + while (oel) { + if (el === oel) { + return true + } + oel = oel.parentNode + } + return false + }, - if (isPopupOpen) { - e.target.openPopup(); - } - } - }, + // Override L.Evented.fire + fire: function (type, data, propagate) { + if (data && data.layer instanceof L.MarkerCluster) { + // Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget) + if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) { + return + } + type = 'cluster' + type + } - _moveChild: function(layer, from, to) { - layer._latlng = from; - this.removeLayer(layer); + L.FeatureGroup.prototype.fire.call(this, type, data, propagate) + }, - layer._latlng = to; - this.addLayer(layer); - }, + // Override L.Evented.listens + listens: function (type, propagate) { + return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate) + }, - _childMarkerDragEnd: function(e) { - var dragStart = e.target.__dragStart; - delete e.target.__dragStart; - if (dragStart) { - this._moveChild(e.target, dragStart, e.target._latlng); - } - }, + // Default functionality + _defaultIconCreateFunction: function (cluster) { + var childCount = cluster.getChildCount() + var c = ' marker-cluster-' + if (childCount < 10) { + c += 'small' + } else if (childCount < 100) { + c += 'medium' + } else { + c += 'large' + } - // Internal function for removing a marker from everything. - // dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions) - _removeLayer: function(marker, removeFromDistanceGrid, dontUpdateMap) { - var gridClusters = this._gridClusters, - gridUnclustered = this._gridUnclustered, - fg = this._featureGroup, - map = this._map, - minZoom = Math.floor(this._map.getMinZoom()); + return new L.DivIcon({ + html: '<div><span>' + childCount + '</span></div>', + className: 'marker-cluster' + c, + iconSize: new L.Point(40, 40) + }) + }, - // Remove the marker from distance clusters it might be in - if (removeFromDistanceGrid) { - this._removeFromGridUnclustered(marker, this._maxZoom); - } + _bindEvents: function () { + var map = this._map + var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom + var showCoverageOnHover = this.options.showCoverageOnHover + var zoomToBoundsOnClick = this.options.zoomToBoundsOnClick - // Work our way up the clusters removing them as we go if required - var cluster = marker.__parent, - markers = cluster._markers, - otherMarker; + // Zoom on cluster click or spiderfy if we are at the lowest level + if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { + this.on('clusterclick', this._zoomOrSpiderfy, this) + } - // Remove the marker from the immediate parents marker list - this._arraySplice(markers, marker); + // Show convex hull (boundary) polygon on mouse over + if (showCoverageOnHover) { + this.on('clustermouseover', this._showCoverage, this) + this.on('clustermouseout', this._hideCoverage, this) + map.on('zoomend', this._hideCoverage, this) + } + }, - while (cluster) { - cluster._childCount--; - cluster._boundsNeedUpdate = true; + _zoomOrSpiderfy: function (e) { + var cluster = e.layer + var bottomCluster = cluster - if (cluster._zoom < minZoom) { - // Top level, do nothing - break; - } else if (removeFromDistanceGrid && cluster._childCount <= 1) { // Cluster no longer required - // We need to push the other marker up to the parent - otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0]; + while (bottomCluster._childClusters.length === 1) { + bottomCluster = bottomCluster._childClusters[0] + } - // Update distance grid - gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom)); - gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom)); - - // Move otherMarker up to parent - this._arraySplice(cluster.__parent._childClusters, cluster); - cluster.__parent._markers.push(otherMarker); - otherMarker.__parent = cluster.__parent; - - if (cluster._icon) { - // Cluster is currently on the map, need to put the marker on the map instead - fg.removeLayer(cluster); - if (!dontUpdateMap) { - fg.addLayer(otherMarker); - } - } - } else { - cluster._iconNeedsUpdate = true; - } - - cluster = cluster.__parent; - } - - delete marker.__parent; - }, - - _isOrIsParent: function(el, oel) { - while (oel) { - if (el === oel) { - return true; - } - oel = oel.parentNode; - } - return false; - }, - - // Override L.Evented.fire - fire: function(type, data, propagate) { - if (data && data.layer instanceof L.MarkerCluster) { - // Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget) - if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) { - return; - } - type = 'cluster' + type; - } - - L.FeatureGroup.prototype.fire.call(this, type, data, propagate); - }, - - // Override L.Evented.listens - listens: function(type, propagate) { - return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate); - }, - - // Default functionality - _defaultIconCreateFunction: function(cluster) { - var childCount = cluster.getChildCount(); - - var c = ' marker-cluster-'; - if (childCount < 10) { - c += 'small'; - } else if (childCount < 100) { - c += 'medium'; - } else { - c += 'large'; - } - - return new L.DivIcon({ - html: '<div><span>' + childCount + '</span></div>', - className: 'marker-cluster' + c, - iconSize: new L.Point(40, 40) - }); - }, - - _bindEvents: function() { - var map = this._map, - spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom, - showCoverageOnHover = this.options.showCoverageOnHover, - zoomToBoundsOnClick = this.options.zoomToBoundsOnClick; - - // Zoom on cluster click or spiderfy if we are at the lowest level - if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { - this.on('clusterclick', this._zoomOrSpiderfy, this); - } - - // Show convex hull (boundary) polygon on mouse over - if (showCoverageOnHover) { - this.on('clustermouseover', this._showCoverage, this); - this.on('clustermouseout', this._hideCoverage, this); - map.on('zoomend', this._hideCoverage, this); - } - }, - - _zoomOrSpiderfy: function(e) { - var cluster = e.layer, - bottomCluster = cluster; - - while (bottomCluster._childClusters.length === 1) { - bottomCluster = bottomCluster._childClusters[0]; - } - - if (bottomCluster._zoom === this._maxZoom && + if (bottomCluster._zoom === this._maxZoom && bottomCluster._childCount === cluster._childCount && this.options.spiderfyOnMaxZoom) { + // All child markers are contained in a single cluster from this._maxZoom to this cluster. + cluster.spiderfy() + } else if (this.options.zoomToBoundsOnClick) { + cluster.zoomToBounds() + } - // All child markers are contained in a single cluster from this._maxZoom to this cluster. - cluster.spiderfy(); - } else if (this.options.zoomToBoundsOnClick) { - cluster.zoomToBounds(); - } + // Focus the map again for keyboard users. + if (e.originalEvent && e.originalEvent.keyCode === 13) { + this._map._container.focus() + } + }, - // Focus the map again for keyboard users. - if (e.originalEvent && e.originalEvent.keyCode === 13) { - this._map._container.focus(); - } - }, + _showCoverage: function (e) { + var map = this._map + if (this._inZoomAnimation) { + return + } + if (this._shownPolygon) { + map.removeLayer(this._shownPolygon) + } + if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) { + this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions) + map.addLayer(this._shownPolygon) + } + }, - _showCoverage: function(e) { - var map = this._map; - if (this._inZoomAnimation) { - return; - } - if (this._shownPolygon) { - map.removeLayer(this._shownPolygon); - } - if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) { - this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions); - map.addLayer(this._shownPolygon); - } - }, + _hideCoverage: function () { + if (this._shownPolygon) { + this._map.removeLayer(this._shownPolygon) + this._shownPolygon = null + } + }, - _hideCoverage: function() { - if (this._shownPolygon) { - this._map.removeLayer(this._shownPolygon); - this._shownPolygon = null; - } - }, + _unbindEvents: function () { + var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom + var showCoverageOnHover = this.options.showCoverageOnHover + var zoomToBoundsOnClick = this.options.zoomToBoundsOnClick + var map = this._map - _unbindEvents: function() { - var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom, - showCoverageOnHover = this.options.showCoverageOnHover, - zoomToBoundsOnClick = this.options.zoomToBoundsOnClick, - map = this._map; + if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { + this.off('clusterclick', this._zoomOrSpiderfy, this) + } + if (showCoverageOnHover) { + this.off('clustermouseover', this._showCoverage, this) + this.off('clustermouseout', this._hideCoverage, this) + map.off('zoomend', this._hideCoverage, this) + } + }, - if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { - this.off('clusterclick', this._zoomOrSpiderfy, this); - } - if (showCoverageOnHover) { - this.off('clustermouseover', this._showCoverage, this); - this.off('clustermouseout', this._hideCoverage, this); - map.off('zoomend', this._hideCoverage, this); - } - }, + _zoomEnd: function () { + if (!this._map) { // May have been removed from the map by a zoomEnd handler + return + } + this._mergeSplitClusters() - _zoomEnd: function() { - if (!this._map) { // May have been removed from the map by a zoomEnd handler - return; - } - this._mergeSplitClusters(); + this._zoom = Math.round(this._map._zoom) + this._currentShownBounds = this._getExpandedVisibleBounds() + }, - this._zoom = Math.round(this._map._zoom); - this._currentShownBounds = this._getExpandedVisibleBounds(); - }, + _moveEnd: function () { + if (this._inZoomAnimation) { + return + } - _moveEnd: function() { - if (this._inZoomAnimation) { - return; - } + var newBounds = this._getExpandedVisibleBounds() - var newBounds = this._getExpandedVisibleBounds(); + this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds) + this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds) - this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds); - this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds); + this._currentShownBounds = newBounds + }, - this._currentShownBounds = newBounds; - return; - }, + _generateInitialClusters: function () { + var maxZoom = Math.ceil(this._map.getMaxZoom()) + var minZoom = Math.floor(this._map.getMinZoom()) + var radius = this.options.maxClusterRadius + var radiusFn = radius - _generateInitialClusters: function() { - var maxZoom = Math.ceil(this._map.getMaxZoom()), - minZoom = Math.floor(this._map.getMinZoom()), - radius = this.options.maxClusterRadius, - radiusFn = radius; + // If we just set maxClusterRadius to a single number, we need to create + // a simple function to return that number. Otherwise, we just have to + // use the function we've passed in. + if (typeof radius !== 'function') { + radiusFn = function () { + return radius + } + } - // If we just set maxClusterRadius to a single number, we need to create - // a simple function to return that number. Otherwise, we just have to - // use the function we've passed in. - if (typeof radius !== "function") { - radiusFn = function() { - return radius; - }; - } + if (this.options.disableClusteringAtZoom !== null) { + maxZoom = this.options.disableClusteringAtZoom - 1 + } + this._maxZoom = maxZoom + this._gridClusters = {} + this._gridUnclustered = {} - if (this.options.disableClusteringAtZoom !== null) { - maxZoom = this.options.disableClusteringAtZoom - 1; - } - this._maxZoom = maxZoom; - this._gridClusters = {}; - this._gridUnclustered = {}; + // Set up DistanceGrids for each zoom + for (var zoom = maxZoom; zoom >= minZoom; zoom--) { + this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom)) + this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom)) + } - // Set up DistanceGrids for each zoom - for (var zoom = maxZoom; zoom >= minZoom; zoom--) { - this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom)); - this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom)); - } + // Instantiate the appropriate L.MarkerCluster class (animated or not). + this._topClusterLevel = new this._markerCluster(this, minZoom - 1) + }, - // Instantiate the appropriate L.MarkerCluster class (animated or not). - this._topClusterLevel = new this._markerCluster(this, minZoom - 1); - }, + // Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom) + _addLayer: function (layer, zoom) { + var gridClusters = this._gridClusters + var gridUnclustered = this._gridUnclustered + var minZoom = Math.floor(this._map.getMinZoom()) + var markerPoint; var z - // Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom) - _addLayer: function(layer, zoom) { - var gridClusters = this._gridClusters, - gridUnclustered = this._gridUnclustered, - minZoom = Math.floor(this._map.getMinZoom()), - markerPoint, z; + if (this.options.singleMarkerMode) { + this._overrideMarkerIcon(layer) + } - if (this.options.singleMarkerMode) { - this._overrideMarkerIcon(layer); - } + layer.on(this._childMarkerEventHandlers, this) - layer.on(this._childMarkerEventHandlers, this); + // Find the lowest zoom level to slot this one in + for (; zoom >= minZoom; zoom--) { + markerPoint = this._map.project(layer.getLatLng(), zoom) // calculate pixel position - // Find the lowest zoom level to slot this one in - for (; zoom >= minZoom; zoom--) { - markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position + // Try find a cluster close by + var closest = gridClusters[zoom].getNearObject(markerPoint) + if (closest) { + closest._addChild(layer) + layer.__parent = closest + return + } - // Try find a cluster close by - var closest = gridClusters[zoom].getNearObject(markerPoint); - if (closest) { - closest._addChild(layer); - layer.__parent = closest; - return; - } + // Try find a marker close by to form a new cluster with + closest = gridUnclustered[zoom].getNearObject(markerPoint) + if (closest) { + var parent = closest.__parent + if (parent) { + this._removeLayer(closest, false) + } - // Try find a marker close by to form a new cluster with - closest = gridUnclustered[zoom].getNearObject(markerPoint); - if (closest) { - var parent = closest.__parent; - if (parent) { - this._removeLayer(closest, false); - } + // Create new cluster with these 2 in it - // Create new cluster with these 2 in it + var newCluster = new this._markerCluster(this, zoom, closest, layer) + gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom)) + closest.__parent = newCluster + layer.__parent = newCluster - var newCluster = new this._markerCluster(this, zoom, closest, layer); - gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom)); - closest.__parent = newCluster; - layer.__parent = newCluster; + // First create any new intermediate parent clusters that don't exist + var lastParent = newCluster + for (z = zoom - 1; z > parent._zoom; z--) { + lastParent = new this._markerCluster(this, z, lastParent) + gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z)) + } + parent._addChild(lastParent) - // First create any new intermediate parent clusters that don't exist - var lastParent = newCluster; - for (z = zoom - 1; z > parent._zoom; z--) { - lastParent = new this._markerCluster(this, z, lastParent); - gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z)); - } - parent._addChild(lastParent); + // Remove closest from this zoom level and any above that it is in, replace with newCluster + this._removeFromGridUnclustered(closest, zoom) - // Remove closest from this zoom level and any above that it is in, replace with newCluster - this._removeFromGridUnclustered(closest, zoom); + return + } - return; - } + // Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards + gridUnclustered[zoom].addObject(layer, markerPoint) + } - // Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards - gridUnclustered[zoom].addObject(layer, markerPoint); - } + // Didn't get in anything, add us to the top + this._topClusterLevel._addChild(layer) + layer.__parent = this._topClusterLevel + }, - // Didn't get in anything, add us to the top - this._topClusterLevel._addChild(layer); - layer.__parent = this._topClusterLevel; - return; - }, - - /** + /** * Refreshes the icon of all "dirty" visible clusters. * Non-visible "dirty" clusters will be updated when they are added to the map. * @private */ - _refreshClustersIcons: function() { - this._featureGroup.eachLayer(function(c) { - if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) { - c._updateIcon(); - } - }); - }, + _refreshClustersIcons: function () { + this._featureGroup.eachLayer(function (c) { + if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) { + c._updateIcon() + } + }) + }, - // Enqueue code to fire after the marker expand/contract has happened - _enqueue: function(fn) { - this._queue.push(fn); - if (!this._queueTimeout) { - this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300); - } - }, - _processQueue: function() { - for (var i = 0; i < this._queue.length; i++) { - this._queue[i].call(this); - } - this._queue.length = 0; - clearTimeout(this._queueTimeout); - this._queueTimeout = null; - }, + // Enqueue code to fire after the marker expand/contract has happened + _enqueue: function (fn) { + this._queue.push(fn) + if (!this._queueTimeout) { + this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300) + } + }, + _processQueue: function () { + for (var i = 0; i < this._queue.length; i++) { + this._queue[i].call(this) + } + this._queue.length = 0 + clearTimeout(this._queueTimeout) + this._queueTimeout = null + }, - // Merge and split any existing clusters that are too big or small - _mergeSplitClusters: function() { - var mapZoom = Math.round(this._map._zoom); + // Merge and split any existing clusters that are too big or small + _mergeSplitClusters: function () { + var mapZoom = Math.round(this._map._zoom) - // In case we are starting to split before the animation finished - this._processQueue(); + // In case we are starting to split before the animation finished + this._processQueue() - if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { // Zoom in, split - this._animationStart(); - // Remove clusters now off screen - this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds()); + if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { // Zoom in, split + this._animationStart() + // Remove clusters now off screen + this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds()) - this._animationZoomIn(this._zoom, mapZoom); + this._animationZoomIn(this._zoom, mapZoom) + } else if (this._zoom > mapZoom) { // Zoom out, merge + this._animationStart() - } else if (this._zoom > mapZoom) { // Zoom out, merge - this._animationStart(); + this._animationZoomOut(this._zoom, mapZoom) + } else { + this._moveEnd() + } + }, - this._animationZoomOut(this._zoom, mapZoom); - } else { - this._moveEnd(); - } - }, + // Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan) + _getExpandedVisibleBounds: function () { + if (!this.options.removeOutsideVisibleBounds) { + return this._mapBoundsInfinite + } else if (L.Browser.mobile) { + return this._checkBoundsMaxLat(this._map.getBounds()) + } - // Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan) - _getExpandedVisibleBounds: function() { - if (!this.options.removeOutsideVisibleBounds) { - return this._mapBoundsInfinite - } else if (L.Browser.mobile) { - return this._checkBoundsMaxLat(this._map.getBounds()) - } + return this._checkBoundsMaxLat(this._map.getBounds().pad(1)) // Padding expands the bounds by its own dimensions but scaled with the given factor. + }, - return this._checkBoundsMaxLat(this._map.getBounds().pad(1)) // Padding expands the bounds by its own dimensions but scaled with the given factor. - }, - - /** + /** * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https:// en.wikipedia.org/wiki/Web_Mercator#Formulas). * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without @@ -1099,691 +1090,689 @@ * @returns {L.LatLngBounds} * @private */ - _checkBoundsMaxLat: function(bounds) { - var maxLat = this._maxLat + _checkBoundsMaxLat: function (bounds) { + var maxLat = this._maxLat - if (maxLat !== undefined) { - if (bounds.getNorth() >= maxLat) { - bounds._northEast.lat = Infinity - } - if (bounds.getSouth() <= -maxLat) { - bounds._southWest.lat = -Infinity - } - } + if (maxLat !== undefined) { + if (bounds.getNorth() >= maxLat) { + bounds._northEast.lat = Infinity + } + if (bounds.getSouth() <= -maxLat) { + bounds._southWest.lat = -Infinity + } + } - return bounds - }, + return bounds + }, - // Shared animation code - _animationAddLayerNonAnimated: function(layer, newCluster) { - if (newCluster === layer) { - this._featureGroup.addLayer(layer) - } else if (newCluster._childCount === 2) { - newCluster._addToMap() + // Shared animation code + _animationAddLayerNonAnimated: function (layer, newCluster) { + if (newCluster === layer) { + this._featureGroup.addLayer(layer) + } else if (newCluster._childCount === 2) { + newCluster._addToMap() - var markers = newCluster.getAllChildMarkers() - this._featureGroup.removeLayer(markers[0]) - this._featureGroup.removeLayer(markers[1]) - } else { - newCluster._updateIcon() - } - }, + var markers = newCluster.getAllChildMarkers() + this._featureGroup.removeLayer(markers[0]) + this._featureGroup.removeLayer(markers[1]) + } else { + newCluster._updateIcon() + } + }, - /** + /** * Extracts individual (i.e. non-group) layers from a Layer Group. * @param group to extract layers from. * @param output {Array} in which to store the extracted layers. * @returns {*|Array} * @private */ - _extractNonGroupLayers: function(group, output) { - var layers = group.getLayers(), - i = 0, - layer + _extractNonGroupLayers: function (group, output) { + var layers = group.getLayers() + var i = 0 + var layer - output = output || [] + output = output || [] - for (; i < layers.length; i++) { - layer = layers[i] + for (; i < layers.length; i++) { + layer = layers[i] - if (layer instanceof L.LayerGroup) { - this._extractNonGroupLayers(layer, output) - continue - } + if (layer instanceof L.LayerGroup) { + this._extractNonGroupLayers(layer, output) + continue + } - output.push(layer) - } + output.push(layer) + } - return output - }, + return output + }, - /** + /** * Implements the singleMarkerMode option. * @param layer Marker to re-style using the Clusters iconCreateFunction. * @returns {L.Icon} The newly created icon. * @private */ - _overrideMarkerIcon: function(layer) { - var icon = layer.options.icon = this.options.iconCreateFunction({ - getChildCount: function() { - return 1 - }, - getAllChildMarkers: function() { - return [layer] - } + _overrideMarkerIcon: function (layer) { + var icon = layer.options.icon = this.options.iconCreateFunction({ + getChildCount: function () { + return 1 + }, + getAllChildMarkers: function () { + return [layer] + } + }) + + return icon + } + }) + + // Constant bounds used in case option "removeOutsideVisibleBounds" is set to false. + L.MarkerClusterGroup.include({ + _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity)) + }) + + L.MarkerClusterGroup.include({ + _noAnimation: { + // Non Animated versions of everything + _animationStart: function () { + // Do nothing... + }, + _animationZoomIn: function (previousZoomLevel, newZoomLevel) { + this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel) + this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()) + + // We didn't actually animate, but we use this event to mean "clustering animations have finished" + this.fire('animationend') + }, + _animationZoomOut: function (previousZoomLevel, newZoomLevel) { + this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel) + this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()) + + // We didn't actually animate, but we use this event to mean "clustering animations have finished" + this.fire('animationend') + }, + _animationAddLayer: function (layer, newCluster) { + this._animationAddLayerNonAnimated(layer, newCluster) + } + }, + + _withAnimation: { + // Animated versions here + _animationStart: function () { + this._map._mapPane.className += ' leaflet-cluster-anim' + this._inZoomAnimation++ + }, + + _animationZoomIn: function (previousZoomLevel, newZoomLevel) { + var bounds = this._getExpandedVisibleBounds() + var fg = this._featureGroup + var minZoom = Math.floor(this._map.getMinZoom()) + var i + + this._ignoreMove = true + + // Add all children of current clusters to map and remove those clusters from map + this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) { + var startPos = c._latlng + var markers = c._markers + var m + + if (!bounds.contains(startPos)) { + startPos = null + } + + if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { // Immediately add the new child and remove us + fg.removeLayer(c) + c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds) + } else { + // Fade out old cluster + c.clusterHide() + c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds) + } + + // Remove all markers that aren't visible any more + // TODO: Do we actually need to do this on the higher levels too? + for (i = markers.length - 1; i >= 0; i--) { + m = markers[i] + if (!bounds.contains(m._latlng)) { + fg.removeLayer(m) + } + } + }) + + this._forceLayout() + + // Update opacities + this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel) + // TODO Maybe? Update markers in _recursivelyBecomeVisible + fg.eachLayer(function (n) { + if (!(n instanceof L.MarkerCluster) && n._icon) { + n.clusterShow() + } + }) + + // update the positions of the just added clusters/markers + this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) { + c._recursivelyRestoreChildPositions(newZoomLevel) + }) + + this._ignoreMove = false + + // Remove the old clusters and close the zoom animation + this._enqueue(function () { + // update the positions of the just added clusters/markers + this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) { + fg.removeLayer(c) + c.clusterShow() + }) + + this._animationEnd() + }) + }, + + _animationZoomOut: function (previousZoomLevel, newZoomLevel) { + this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel) + + // Need to add markers for those that weren't on the map before but are now + this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()) + // Remove markers that were on the map before but won't be now + this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds()) + }, + + _animationAddLayer: function (layer, newCluster) { + var me = this + var fg = this._featureGroup + + fg.addLayer(layer) + if (newCluster !== layer) { + if (newCluster._childCount > 2) { // Was already a cluster + newCluster._updateIcon() + this._forceLayout() + this._animationStart() + + layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng())) + layer.clusterHide() + + this._enqueue(function () { + fg.removeLayer(layer) + layer.clusterShow() + + me._animationEnd() }) + } else { // Just became a cluster + this._forceLayout() - return icon + me._animationStart() + me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom) + } } - }) + } + }, -// Constant bounds used in case option "removeOutsideVisibleBounds" is set to false. - L.MarkerClusterGroup.include({ - _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity)) - }) + // Private methods for animated versions. + _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) { + var bounds = this._getExpandedVisibleBounds() + var minZoom = Math.floor(this._map.getMinZoom()) - L.MarkerClusterGroup.include({ - _noAnimation: { - // Non Animated versions of everything - _animationStart: function() { - // Do nothing... - }, - _animationZoomIn: function(previousZoomLevel, newZoomLevel) { - this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel); - this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()) + // Animate all of the markers in the clusters to move to their cluster center point + cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel) - // We didn't actually animate, but we use this event to mean "clustering animations have finished" - this.fire('animationend') - }, - _animationZoomOut: function(previousZoomLevel, newZoomLevel) { - this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel); - this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()) + var me = this - // We didn't actually animate, but we use this event to mean "clustering animations have finished" - this.fire('animationend') - }, - _animationAddLayer: function(layer, newCluster) { - this._animationAddLayerNonAnimated(layer, newCluster) - } - }, + // Update the opacity (If we immediately set it they won't animate) + this._forceLayout() + cluster._recursivelyBecomeVisible(bounds, newZoomLevel) - _withAnimation: { - // Animated versions here - _animationStart: function() { - this._map._mapPane.className += ' leaflet-cluster-anim' - this._inZoomAnimation++ - }, - - _animationZoomIn: function(previousZoomLevel, newZoomLevel) { - var bounds = this._getExpandedVisibleBounds(), - fg = this._featureGroup, - minZoom = Math.floor(this._map.getMinZoom()), - i - - this._ignoreMove = true - - // Add all children of current clusters to map and remove those clusters from map - this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function(c) { - var startPos = c._latlng, - markers = c._markers, - m - - if (!bounds.contains(startPos)) { - startPos = null - } - - if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { // Immediately add the new child and remove us - fg.removeLayer(c) - c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds) - } else { - // Fade out old cluster - c.clusterHide() - c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds) - } - - // Remove all markers that aren't visible any more - // TODO: Do we actually need to do this on the higher levels too? - for (i = markers.length - 1; i >= 0; i--) { - m = markers[i] - if (!bounds.contains(m._latlng)) { - fg.removeLayer(m) - } - } - - }); - - this._forceLayout() - - // Update opacities - this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel); - // TODO Maybe? Update markers in _recursivelyBecomeVisible - fg.eachLayer(function(n) { - if (!(n instanceof L.MarkerCluster) && n._icon) { - n.clusterShow() - } - }); - - // update the positions of the just added clusters/markers - this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function(c) { - c._recursivelyRestoreChildPositions(newZoomLevel); - }); - - this._ignoreMove = false; - - // Remove the old clusters and close the zoom animation - this._enqueue(function() { - // update the positions of the just added clusters/markers - this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function(c) { - fg.removeLayer(c) - c.clusterShow() - }) - - this._animationEnd() - }) - }, - - _animationZoomOut: function(previousZoomLevel, newZoomLevel) { - this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel) - - // Need to add markers for those that weren't on the map before but are now - this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()) - // Remove markers that were on the map before but won't be now - this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds()); - }, - - _animationAddLayer: function(layer, newCluster) { - var me = this, - fg = this._featureGroup - - fg.addLayer(layer) - if (newCluster !== layer) { - if (newCluster._childCount > 2) { // Was already a cluster - newCluster._updateIcon() - this._forceLayout() - this._animationStart() - - layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng())) - layer.clusterHide() - - this._enqueue(function() { - fg.removeLayer(layer) - layer.clusterShow() - - me._animationEnd() - }); - - } else { // Just became a cluster - this._forceLayout() - - me._animationStart() - me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom) - } - } - } - }, - - // Private methods for animated versions. - _animationZoomOutSingle: function(cluster, previousZoomLevel, newZoomLevel) { - var bounds = this._getExpandedVisibleBounds(), - minZoom = Math.floor(this._map.getMinZoom()) - - // Animate all of the markers in the clusters to move to their cluster center point - cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel) - - var me = this - - // Update the opacity (If we immediately set it they won't animate) - this._forceLayout(); - cluster._recursivelyBecomeVisible(bounds, newZoomLevel) - - // TODO: Maybe use the transition timing stuff to make this more reliable - // When the animations are done, tidy up - this._enqueue(function() { - // This cluster stopped being a cluster before the timeout fired - if (cluster._childCount === 1) { - var m = cluster._markers[0] - // If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it - this._ignoreMove = true - m.setLatLng(m.getLatLng()) - this._ignoreMove = false - if (m.clusterShow) { - m.clusterShow() - } - } else { - cluster._recursively(bounds, newZoomLevel, minZoom, function(c) { - c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1) - }) - } - me._animationEnd() - }); - }, - - _animationEnd: function() { - if (this._map) { - this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', ''); - } - this._inZoomAnimation-- - this.fire('animationend') - }, - - // Force a browser layout of stuff in the map - // Should apply the current opacity and location to all elements so we can update them again for an animation - _forceLayout: function() { - // In my testing this works, infact offsetWidth of any element seems to work. - // Could loop all this._layers and do this for each _icon if it stops working - - L.Util.falseFn(document.body.offsetWidth) + // TODO: Maybe use the transition timing stuff to make this more reliable + // When the animations are done, tidy up + this._enqueue(function () { + // This cluster stopped being a cluster before the timeout fired + if (cluster._childCount === 1) { + var m = cluster._markers[0] + // If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it + this._ignoreMove = true + m.setLatLng(m.getLatLng()) + this._ignoreMove = false + if (m.clusterShow) { + m.clusterShow() + } + } else { + cluster._recursively(bounds, newZoomLevel, minZoom, function (c) { + c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1) + }) } - }); + me._animationEnd() + }) + }, - L.markerClusterGroup = function(options) { - return new L.MarkerClusterGroup(options) - }; + _animationEnd: function () { + if (this._map) { + this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '') + } + this._inZoomAnimation-- + this.fire('animationend') + }, - var MarkerCluster = L.MarkerCluster = L.Marker.extend({ - options: L.Icon.prototype.options, + // Force a browser layout of stuff in the map + // Should apply the current opacity and location to all elements so we can update them again for an animation + _forceLayout: function () { + // In my testing this works, infact offsetWidth of any element seems to work. + // Could loop all this._layers and do this for each _icon if it stops working - initialize: function(group, zoom, a, b) { - L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), - { icon: this, pane: group.options.clusterPane }) + L.Util.falseFn(document.body.offsetWidth) + } + }) - this._group = group - this._zoom = zoom + L.markerClusterGroup = function (options) { + return new L.MarkerClusterGroup(options) + } - this._markers = [] - this._childClusters = [] - this._childCount = 0 - this._iconNeedsUpdate = true - this._boundsNeedUpdate = true + var MarkerCluster = L.MarkerCluster = L.Marker.extend({ + options: L.Icon.prototype.options, - this._bounds = new L.LatLngBounds() + initialize: function (group, zoom, a, b) { + L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), + { icon: this, pane: group.options.clusterPane }) - if (a) { - this._addChild(a); - } - if (b) { - this._addChild(b); - } - }, + this._group = group + this._zoom = zoom - // Recursively retrieve all child markers of this cluster - getAllChildMarkers: function(storageArray, ignoreDraggedMarker) { - storageArray = storageArray || [] + this._markers = [] + this._childClusters = [] + this._childCount = 0 + this._iconNeedsUpdate = true + this._boundsNeedUpdate = true - for (var i = this._childClusters.length - 1; i >= 0; i--) { - this._childClusters[i].getAllChildMarkers(storageArray) - } + this._bounds = new L.LatLngBounds() - for (var j = this._markers.length - 1; j >= 0; j--) { - if (ignoreDraggedMarker && this._markers[j].__dragStart) { - continue - } - storageArray.push(this._markers[j]) - } + if (a) { + this._addChild(a) + } + if (b) { + this._addChild(b) + } + }, - return storageArray - }, + // Recursively retrieve all child markers of this cluster + getAllChildMarkers: function (storageArray, ignoreDraggedMarker) { + storageArray = storageArray || [] - // Returns the count of how many child markers we have - getChildCount: function() { - return this._childCount; - }, + for (var i = this._childClusters.length - 1; i >= 0; i--) { + this._childClusters[i].getAllChildMarkers(storageArray) + } - // Zoom to the minimum of showing all of the child markers, or the extents of this cluster - zoomToBounds: function(fitBoundsOptions) { - var childClusters = this._childClusters.slice(), - map = this._group._map, - boundsZoom = map.getBoundsZoom(this._bounds), - zoom = this._zoom + 1, - mapZoom = map.getZoom(), - i; + for (var j = this._markers.length - 1; j >= 0; j--) { + if (ignoreDraggedMarker && this._markers[j].__dragStart) { + continue + } + storageArray.push(this._markers[j]) + } - // calculate how far we need to zoom down to see all of the markers - while (childClusters.length > 0 && boundsZoom > zoom) { - zoom++; - var newClusters = []; - for (i = 0; i < childClusters.length; i++) { - newClusters = newClusters.concat(childClusters[i]._childClusters); - } - childClusters = newClusters; - } + return storageArray + }, - if (boundsZoom > zoom) { - this._group._map.setView(this._latlng, zoom); - } else if (boundsZoom <= mapZoom) { // If fitBounds wouldn't zoom us down, zoom us down instead - this._group._map.setView(this._latlng, mapZoom + 1); - } else { - this._group._map.fitBounds(this._bounds, fitBoundsOptions) - } - }, + // Returns the count of how many child markers we have + getChildCount: function () { + return this._childCount + }, - getBounds: function() { - var bounds = new L.LatLngBounds() - bounds.extend(this._bounds) - return bounds - }, + // Zoom to the minimum of showing all of the child markers, or the extents of this cluster + zoomToBounds: function (fitBoundsOptions) { + var childClusters = this._childClusters.slice() + var map = this._group._map + var boundsZoom = map.getBoundsZoom(this._bounds) + var zoom = this._zoom + 1 + var mapZoom = map.getZoom() + var i - _updateIcon: function() { - this._iconNeedsUpdate = true - if (this._icon) { - this.setIcon(this) - } - }, + // calculate how far we need to zoom down to see all of the markers + while (childClusters.length > 0 && boundsZoom > zoom) { + zoom++ + var newClusters = [] + for (i = 0; i < childClusters.length; i++) { + newClusters = newClusters.concat(childClusters[i]._childClusters) + } + childClusters = newClusters + } - // Cludge for Icon, we pretend to be an icon for performance - createIcon: function() { - if (this._iconNeedsUpdate) { - this._iconObj = this._group.options.iconCreateFunction(this); - this._iconNeedsUpdate = false - } - return this._iconObj.createIcon() - }, - createShadow: function() { - return this._iconObj.createShadow() - }, + if (boundsZoom > zoom) { + this._group._map.setView(this._latlng, zoom) + } else if (boundsZoom <= mapZoom) { // If fitBounds wouldn't zoom us down, zoom us down instead + this._group._map.setView(this._latlng, mapZoom + 1) + } else { + this._group._map.fitBounds(this._bounds, fitBoundsOptions) + } + }, - _addChild: function(new1, isNotificationFromChild) { - this._iconNeedsUpdate = true + getBounds: function () { + var bounds = new L.LatLngBounds() + bounds.extend(this._bounds) + return bounds + }, - this._boundsNeedUpdate = true - this._setClusterCenter(new1) + _updateIcon: function () { + this._iconNeedsUpdate = true + if (this._icon) { + this.setIcon(this) + } + }, - if (new1 instanceof L.MarkerCluster) { - if (!isNotificationFromChild) { - this._childClusters.push(new1) - new1.__parent = this - } - this._childCount += new1._childCount - } else { - if (!isNotificationFromChild) { - this._markers.push(new1) - } - this._childCount++ - } + // Cludge for Icon, we pretend to be an icon for performance + createIcon: function () { + if (this._iconNeedsUpdate) { + this._iconObj = this._group.options.iconCreateFunction(this) + this._iconNeedsUpdate = false + } + return this._iconObj.createIcon() + }, + createShadow: function () { + return this._iconObj.createShadow() + }, - if (this.__parent) { - this.__parent._addChild(new1, true) - } - }, + _addChild: function (new1, isNotificationFromChild) { + this._iconNeedsUpdate = true - /** + this._boundsNeedUpdate = true + this._setClusterCenter(new1) + + if (new1 instanceof L.MarkerCluster) { + if (!isNotificationFromChild) { + this._childClusters.push(new1) + new1.__parent = this + } + this._childCount += new1._childCount + } else { + if (!isNotificationFromChild) { + this._markers.push(new1) + } + this._childCount++ + } + + if (this.__parent) { + this.__parent._addChild(new1, true) + } + }, + + /** * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position. * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet. * @private */ - _setClusterCenter: function(child) { - if (!this._cLatLng) { - // when clustering, take position of the first point as the cluster center - this._cLatLng = child._cLatLng || child._latlng - } - }, + _setClusterCenter: function (child) { + if (!this._cLatLng) { + // when clustering, take position of the first point as the cluster center + this._cLatLng = child._cLatLng || child._latlng + } + }, - /** + /** * Assigns impossible bounding values so that the next extend entirely determines the new bounds. * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class. * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended. * @private */ - _resetBounds: function() { - var bounds = this._bounds + _resetBounds: function () { + var bounds = this._bounds - if (bounds._southWest) { - bounds._southWest.lat = Infinity - bounds._southWest.lng = Infinity - } - if (bounds._northEast) { - bounds._northEast.lat = -Infinity - bounds._northEast.lng = -Infinity - } - }, + if (bounds._southWest) { + bounds._southWest.lat = Infinity + bounds._southWest.lng = Infinity + } + if (bounds._northEast) { + bounds._northEast.lat = -Infinity + bounds._northEast.lng = -Infinity + } + }, - _recalculateBounds: function() { - var markers = this._markers, - childClusters = this._childClusters, - latSum = 0, - lngSum = 0, - totalCount = this._childCount, - i, child, childLatLng, childCount + _recalculateBounds: function () { + var markers = this._markers + var childClusters = this._childClusters + var latSum = 0 + var lngSum = 0 + var totalCount = this._childCount + var i; var child; var childLatLng; var childCount - // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel. - if (totalCount === 0) { - return - } + // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel. + if (totalCount === 0) { + return + } - // Reset rather than creating a new object, for performance. - this._resetBounds() + // Reset rather than creating a new object, for performance. + this._resetBounds() - // Child markers. - for (i = 0; i < markers.length; i++) { - childLatLng = markers[i]._latlng + // Child markers. + for (i = 0; i < markers.length; i++) { + childLatLng = markers[i]._latlng - this._bounds.extend(childLatLng) + this._bounds.extend(childLatLng) - latSum += childLatLng.lat - lngSum += childLatLng.lng - } + latSum += childLatLng.lat + lngSum += childLatLng.lng + } - // Child clusters. - for (i = 0; i < childClusters.length; i++) { - child = childClusters[i] + // Child clusters. + for (i = 0; i < childClusters.length; i++) { + child = childClusters[i] - // Re-compute child bounds and weighted position first if necessary. - if (child._boundsNeedUpdate) { - child._recalculateBounds() - } - - this._bounds.extend(child._bounds) - - childLatLng = child._wLatLng - childCount = child._childCount - - latSum += childLatLng.lat * childCount - lngSum += childLatLng.lng * childCount - } - - this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount) - - // Reset dirty flag. - this._boundsNeedUpdate = false - }, - - // Set our markers position as given and add it to the map - _addToMap: function(startPos) { - if (startPos) { - this._backupLatlng = this._latlng - this.setLatLng(startPos) - } - this._group._featureGroup.addLayer(this) - }, - - _recursivelyAnimateChildrenIn: function(bounds, center, maxZoom) { - this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1, - function(c) { - var markers = c._markers, - i, m - for (i = markers.length - 1; i >= 0; i--) { - m = markers[i] - - // Only do it if the icon is still on the map - if (m._icon) { - m._setPos(center) - m.clusterHide() - } - } - }, - function(c) { - var childClusters = c._childClusters, - j, cm - for (j = childClusters.length - 1; j >= 0; j--) { - cm = childClusters[j] - if (cm._icon) { - cm._setPos(center) - cm.clusterHide() - } - } - } - ); - }, - - _recursivelyAnimateChildrenInAndAddSelfToMap: function(bounds, mapMinZoom, previousZoomLevel, newZoomLevel) { - this._recursively(bounds, newZoomLevel, mapMinZoom, - function(c) { - c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel); - - // TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be. - // As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate - if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) { - c.clusterShow() - c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel) // Immediately remove our children as we are replacing them. TODO previousBounds not bounds - } else { - c.clusterHide() - } - - c._addToMap() - } - ) - }, - - _recursivelyBecomeVisible: function(bounds, zoomLevel) { - this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function(c) { - c.clusterShow() - }) - }, - - _recursivelyAddChildrenToMap: function(startPos, zoomLevel, bounds) { - this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel, - function(c) { - if (zoomLevel === c._zoom) { - return - } - - // Add our child markers at startPos (so they can be animated out) - for (var i = c._markers.length - 1; i >= 0; i--) { - var nm = c._markers[i] - - if (!bounds.contains(nm._latlng)) { - continue - } - - if (startPos) { - nm._backupLatlng = nm.getLatLng() - - nm.setLatLng(startPos) - if (nm.clusterHide) { - nm.clusterHide() - } - } - - c._group._featureGroup.addLayer(nm) - } - }, - function(c) { - c._addToMap(startPos) - } - ) - }, - - _recursivelyRestoreChildPositions: function(zoomLevel) { - // Fix positions of child markers - for (var i = this._markers.length - 1; i >= 0; i--) { - var nm = this._markers[i] - if (nm._backupLatlng) { - nm.setLatLng(nm._backupLatlng) - delete nm._backupLatlng - } - } - - if (zoomLevel - 1 === this._zoom) { - // Reposition child clusters - for (var j = this._childClusters.length - 1; j >= 0; j--) { - this._childClusters[j]._restorePosition(); - } - } else { - for (var k = this._childClusters.length - 1; k >= 0; k--) { - this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel) - } - } - }, - - _restorePosition: function() { - if (this._backupLatlng) { - this.setLatLng(this._backupLatlng) - delete this._backupLatlng - } - }, - - // exceptBounds: If set, don't remove any markers/clusters in it - _recursivelyRemoveChildrenFromMap: function(previousBounds, mapMinZoom, zoomLevel, exceptBounds) { - var m, i - this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1, - function(c) { - // Remove markers at every level - for (i = c._markers.length - 1; i >= 0; i--) { - m = c._markers[i] - if (!exceptBounds || !exceptBounds.contains(m._latlng)) { - c._group._featureGroup.removeLayer(m) - if (m.clusterShow) { - m.clusterShow() - } - } - } - }, - function(c) { - // Remove child clusters at just the bottom level - for (i = c._childClusters.length - 1; i >= 0; i--) { - m = c._childClusters[i] - if (!exceptBounds || !exceptBounds.contains(m._latlng)) { - c._group._featureGroup.removeLayer(m) - if (m.clusterShow) { - m.clusterShow() - } - } - } - } - ) - }, - - // Run the given functions recursively to this and child clusters - // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to - // zoomLevelToStart: zoom level to start running functions (inclusive) - // zoomLevelToStop: zoom level to stop running functions (inclusive) - // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level - // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level - _recursively: function(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) { - var childClusters = this._childClusters, - zoom = this._zoom, - i, c - - if (zoomLevelToStart <= zoom) { - if (runAtEveryLevel) { - runAtEveryLevel(this) - } - if (runAtBottomLevel && zoom === zoomLevelToStop) { - runAtBottomLevel(this) - } - } - - if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) { - for (i = childClusters.length - 1; i >= 0; i--) { - c = childClusters[i] - if (c._boundsNeedUpdate) { - c._recalculateBounds() - } - if (boundsToApplyTo.intersects(c._bounds)) { - c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) - } - } - } - }, - - // Returns true if we are the parent of only one cluster and that cluster is the same as us - _isSingleParent: function() { - // Don't need to check this._markers as the rest won't work if there are any - return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount + // Re-compute child bounds and weighted position first if necessary. + if (child._boundsNeedUpdate) { + child._recalculateBounds() } - }); - /* + this._bounds.extend(child._bounds) + + childLatLng = child._wLatLng + childCount = child._childCount + + latSum += childLatLng.lat * childCount + lngSum += childLatLng.lng * childCount + } + + this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount) + + // Reset dirty flag. + this._boundsNeedUpdate = false + }, + + // Set our markers position as given and add it to the map + _addToMap: function (startPos) { + if (startPos) { + this._backupLatlng = this._latlng + this.setLatLng(startPos) + } + this._group._featureGroup.addLayer(this) + }, + + _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) { + this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1, + function (c) { + var markers = c._markers + var i; var m + for (i = markers.length - 1; i >= 0; i--) { + m = markers[i] + + // Only do it if the icon is still on the map + if (m._icon) { + m._setPos(center) + m.clusterHide() + } + } + }, + function (c) { + var childClusters = c._childClusters + var j; var cm + for (j = childClusters.length - 1; j >= 0; j--) { + cm = childClusters[j] + if (cm._icon) { + cm._setPos(center) + cm.clusterHide() + } + } + } + ) + }, + + _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) { + this._recursively(bounds, newZoomLevel, mapMinZoom, + function (c) { + c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel) + + // TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be. + // As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate + if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) { + c.clusterShow() + c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel) // Immediately remove our children as we are replacing them. TODO previousBounds not bounds + } else { + c.clusterHide() + } + + c._addToMap() + } + ) + }, + + _recursivelyBecomeVisible: function (bounds, zoomLevel) { + this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) { + c.clusterShow() + }) + }, + + _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) { + this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel, + function (c) { + if (zoomLevel === c._zoom) { + return + } + + // Add our child markers at startPos (so they can be animated out) + for (var i = c._markers.length - 1; i >= 0; i--) { + var nm = c._markers[i] + + if (!bounds.contains(nm._latlng)) { + continue + } + + if (startPos) { + nm._backupLatlng = nm.getLatLng() + + nm.setLatLng(startPos) + if (nm.clusterHide) { + nm.clusterHide() + } + } + + c._group._featureGroup.addLayer(nm) + } + }, + function (c) { + c._addToMap(startPos) + } + ) + }, + + _recursivelyRestoreChildPositions: function (zoomLevel) { + // Fix positions of child markers + for (var i = this._markers.length - 1; i >= 0; i--) { + var nm = this._markers[i] + if (nm._backupLatlng) { + nm.setLatLng(nm._backupLatlng) + delete nm._backupLatlng + } + } + + if (zoomLevel - 1 === this._zoom) { + // Reposition child clusters + for (var j = this._childClusters.length - 1; j >= 0; j--) { + this._childClusters[j]._restorePosition() + } + } else { + for (var k = this._childClusters.length - 1; k >= 0; k--) { + this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel) + } + } + }, + + _restorePosition: function () { + if (this._backupLatlng) { + this.setLatLng(this._backupLatlng) + delete this._backupLatlng + } + }, + + // exceptBounds: If set, don't remove any markers/clusters in it + _recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) { + var m, i + this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1, + function (c) { + // Remove markers at every level + for (i = c._markers.length - 1; i >= 0; i--) { + m = c._markers[i] + if (!exceptBounds || !exceptBounds.contains(m._latlng)) { + c._group._featureGroup.removeLayer(m) + if (m.clusterShow) { + m.clusterShow() + } + } + } + }, + function (c) { + // Remove child clusters at just the bottom level + for (i = c._childClusters.length - 1; i >= 0; i--) { + m = c._childClusters[i] + if (!exceptBounds || !exceptBounds.contains(m._latlng)) { + c._group._featureGroup.removeLayer(m) + if (m.clusterShow) { + m.clusterShow() + } + } + } + } + ) + }, + + // Run the given functions recursively to this and child clusters + // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to + // zoomLevelToStart: zoom level to start running functions (inclusive) + // zoomLevelToStop: zoom level to stop running functions (inclusive) + // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level + // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level + _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) { + var childClusters = this._childClusters + var zoom = this._zoom + var i; var c + + if (zoomLevelToStart <= zoom) { + if (runAtEveryLevel) { + runAtEveryLevel(this) + } + if (runAtBottomLevel && zoom === zoomLevelToStop) { + runAtBottomLevel(this) + } + } + + if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) { + for (i = childClusters.length - 1; i >= 0; i--) { + c = childClusters[i] + if (c._boundsNeedUpdate) { + c._recalculateBounds() + } + if (boundsToApplyTo.intersects(c._bounds)) { + c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) + } + } + } + }, + + // Returns true if we are the parent of only one cluster and that cluster is the same as us + _isSingleParent: function () { + // Don't need to check this._markers as the rest won't work if there are any + return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount + } + }) + + /* * Extends L.Marker to include two extra methods: clusterHide and clusterShow. * * They work as setOpacity(0) and setOpacity(1) respectively, but @@ -1791,134 +1780,134 @@ * */ - L.Marker.include({ - clusterHide: function() { - var backup = this.options.opacity - this.setOpacity(0) - this.options.opacity = backup - return this - }, + L.Marker.include({ + clusterHide: function () { + var backup = this.options.opacity + this.setOpacity(0) + this.options.opacity = backup + return this + }, - clusterShow: function() { - return this.setOpacity(this.options.opacity) - } - }) - - L.DistanceGrid = function(cellSize) { - this._cellSize = cellSize - this._sqCellSize = cellSize * cellSize - this._grid = {} - this._objectPoint = {} + clusterShow: function () { + return this.setOpacity(this.options.opacity) } + }) - L.DistanceGrid.prototype = { + L.DistanceGrid = function (cellSize) { + this._cellSize = cellSize + this._sqCellSize = cellSize * cellSize + this._grid = {} + this._objectPoint = {} + } - addObject: function(obj, point) { - var x = this._getCoord(point.x), - y = this._getCoord(point.y), - grid = this._grid, - row = grid[y] = grid[y] || {}, - cell = row[x] = row[x] || [], - stamp = L.Util.stamp(obj) + L.DistanceGrid.prototype = { - this._objectPoint[stamp] = point + addObject: function (obj, point) { + var x = this._getCoord(point.x) + var y = this._getCoord(point.y) + var grid = this._grid + var row = grid[y] = grid[y] || {} + var cell = row[x] = row[x] || [] + var stamp = L.Util.stamp(obj) - cell.push(obj) - }, + this._objectPoint[stamp] = point - updateObject: function(obj, point) { - this.removeObject(obj) - this.addObject(obj, point) - }, + cell.push(obj) + }, - // Returns true if the object was found - removeObject: function(obj, point) { - var x = this._getCoord(point.x), - y = this._getCoord(point.y), - grid = this._grid, - row = grid[y] = grid[y] || {}, - cell = row[x] = row[x] || [], - i, len; + updateObject: function (obj, point) { + this.removeObject(obj) + this.addObject(obj, point) + }, - delete this._objectPoint[L.Util.stamp(obj)]; + // Returns true if the object was found + removeObject: function (obj, point) { + var x = this._getCoord(point.x) + var y = this._getCoord(point.y) + var grid = this._grid + var row = grid[y] = grid[y] || {} + var cell = row[x] = row[x] || [] + var i; var len - for (i = 0, len = cell.length; i < len; i++) { - if (cell[i] === obj) { - cell.splice(i, 1) + delete this._objectPoint[L.Util.stamp(obj)] - if (len === 1) { - delete row[x] - } + for (i = 0, len = cell.length; i < len; i++) { + if (cell[i] === obj) { + cell.splice(i, 1) - return true - } - } - }, + if (len === 1) { + delete row[x] + } - eachObject: function(fn, context) { - var i, j, k, len, row, cell, removed, - grid = this._grid - - for (i in grid) { - row = grid[i] - - for (j in row) { - cell = row[j] - - for (k = 0, len = cell.length; k < len; k++) { - removed = fn.call(context, cell[k]); - if (removed) { - k-- - len-- - } - } - } - } - }, - - getNearObject: function(point) { - var x = this._getCoord(point.x), - y = this._getCoord(point.y), - i, j, k, row, cell, len, obj, dist, - objectPoint = this._objectPoint, - closestDistSq = this._sqCellSize, - closest = null - - for (i = y - 1; i <= y + 1; i++) { - row = this._grid[i] - if (row) { - for (j = x - 1; j <= x + 1; j++) { - cell = row[j] - if (cell) { - for (k = 0, len = cell.length; k < len; k++) { - obj = cell[k] - dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point); - if (dist < closestDistSq || - dist <= closestDistSq && closest === null) { - closestDistSq = dist - closest = obj - } - } - } - } - } - } - return closest - }, - - _getCoord: function(x) { - var coord = Math.floor(x / this._cellSize) - return isFinite(coord) ? coord : x - }, - - _sqDist: function(p, p2) { - var dx = p2.x - p.x, - dy = p2.y - p.y - return dx * dx + dy * dy + return true } - }; + } + }, - /* Copyright (c) 2012 the authors listed at the following URL, and/or + eachObject: function (fn, context) { + var i; var j; var k; var len; var row; var cell; var removed + var grid = this._grid + + for (i in grid) { + row = grid[i] + + for (j in row) { + cell = row[j] + + for (k = 0, len = cell.length; k < len; k++) { + removed = fn.call(context, cell[k]) + if (removed) { + k-- + len-- + } + } + } + } + }, + + getNearObject: function (point) { + var x = this._getCoord(point.x) + var y = this._getCoord(point.y) + var i; var j; var k; var row; var cell; var len; var obj; var dist + var objectPoint = this._objectPoint + var closestDistSq = this._sqCellSize + var closest = null + + for (i = y - 1; i <= y + 1; i++) { + row = this._grid[i] + if (row) { + for (j = x - 1; j <= x + 1; j++) { + cell = row[j] + if (cell) { + for (k = 0, len = cell.length; k < len; k++) { + obj = cell[k] + dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point) + if (dist < closestDistSq || + dist <= closestDistSq && closest === null) { + closestDistSq = dist + closest = obj + } + } + } + } + } + } + return closest + }, + + _getCoord: function (x) { + var coord = Math.floor(x / this._cellSize) + return isFinite(coord) ? coord : x + }, + + _sqDist: function (p, p2) { + var dx = p2.x - p.x + var dy = p2.y - p.y + return dx * dx + dy * dy + } + }; + + /* Copyright (c) 2012 the authors listed at the following URL, and/or the authors of referenced articles or incorporated external code: http:// en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256 @@ -1944,629 +1933,628 @@ Retrieved from: http:// en.literateprograms.org/Quickhull_(Javascript)?oldid=18434 */ - (function() { - L.QuickHull = { + (function () { + L.QuickHull = { - /* + /* * @param {Object} cpt a point to be measured from the baseline * @param {Array} bl the baseline, as represented by a two-element * array of latlng objects. * @returns {Number} an approximate distance measure */ - getDistant: function(cpt, bl) { - var vY = bl[1].lat - bl[0].lat, - vX = bl[0].lng - bl[1].lng - return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng)) - }, + getDistant: function (cpt, bl) { + var vY = bl[1].lat - bl[0].lat + var vX = bl[0].lng - bl[1].lng + return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng)) + }, - /* + /* * @param {Array} baseLine a two-element array of latlng objects * representing the baseline to project from * @param {Array} latLngs an array of latlng objects * @returns {Object} the maximum point and all new points to stay * in consideration for the hull. */ - findMostDistantPointFromBaseLine: function(baseLine, latLngs) { - var maxD = 0, - maxPt = null, - newPoints = [], - i, pt, d + findMostDistantPointFromBaseLine: function (baseLine, latLngs) { + var maxD = 0 + var maxPt = null + var newPoints = [] + var i; var pt; var d - for (i = latLngs.length - 1; i >= 0; i--) { - pt = latLngs[i] - d = this.getDistant(pt, baseLine) + for (i = latLngs.length - 1; i >= 0; i--) { + pt = latLngs[i] + d = this.getDistant(pt, baseLine) - if (d > 0) { - newPoints.push(pt) - } else { - continue - } + if (d > 0) { + newPoints.push(pt) + } else { + continue + } - if (d > maxD) { - maxD = d - maxPt = pt - } - } + if (d > maxD) { + maxD = d + maxPt = pt + } + } - return { maxPoint: maxPt, newPoints: newPoints } - }, + return { maxPoint: maxPt, newPoints: newPoints } + }, - /* + /* * Given a baseline, compute the convex hull of latLngs as an array * of latLngs. * * @param {Array} latLngs * @returns {Array} */ - buildConvexHull: function(baseLine, latLngs) { - var convexHullBaseLines = [], - t = this.findMostDistantPointFromBaseLine(baseLine, latLngs); + buildConvexHull: function (baseLine, latLngs) { + var convexHullBaseLines = [] + var t = this.findMostDistantPointFromBaseLine(baseLine, latLngs) - if (t.maxPoint) { // if there is still a point "outside" the base line - convexHullBaseLines = + if (t.maxPoint) { // if there is still a point "outside" the base line + convexHullBaseLines = convexHullBaseLines.concat( - this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints) - ); - convexHullBaseLines = - convexHullBaseLines.concat( - this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints) + this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints) ) - return convexHullBaseLines - } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull - return [baseLine[0]] - } - }, + convexHullBaseLines = + convexHullBaseLines.concat( + this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints) + ) + return convexHullBaseLines + } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull + return [baseLine[0]] + } + }, - /* + /* * Given an array of latlngs, compute a convex hull as an array * of latlngs * * @param {Array} latLngs * @returns {Array} */ - getConvexHull: function(latLngs) { - // find first baseline - var maxLat = false, minLat = false, - maxLng = false, minLng = false, - maxLatPt = null, minLatPt = null, - maxLngPt = null, minLngPt = null, - maxPt = null, minPt = null, - i + getConvexHull: function (latLngs) { + // find first baseline + var maxLat = false; var minLat = false + var maxLng = false; var minLng = false + var maxLatPt = null; var minLatPt = null + var maxLngPt = null; var minLngPt = null + var maxPt = null; var minPt = null + var i - for (i = latLngs.length - 1; i >= 0; i--) { - var pt = latLngs[i] - if (maxLat === false || pt.lat > maxLat) { - maxLatPt = pt - maxLat = pt.lat - } - if (minLat === false || pt.lat < minLat) { - minLatPt = pt - minLat = pt.lat - } - if (maxLng === false || pt.lng > maxLng) { - maxLngPt = pt - maxLng = pt.lng - } - if (minLng === false || pt.lng < minLng) { - minLngPt = pt - minLng = pt.lng - } - } - - if (minLat !== maxLat) { - minPt = minLatPt - maxPt = maxLatPt - } else { - minPt = minLngPt - maxPt = maxLngPt - } - - var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs), - this.buildConvexHull([maxPt, minPt], latLngs)) - return ch - } + for (i = latLngs.length - 1; i >= 0; i--) { + var pt = latLngs[i] + if (maxLat === false || pt.lat > maxLat) { + maxLatPt = pt + maxLat = pt.lat + } + if (minLat === false || pt.lat < minLat) { + minLatPt = pt + minLat = pt.lat + } + if (maxLng === false || pt.lng > maxLng) { + maxLngPt = pt + maxLng = pt.lng + } + if (minLng === false || pt.lng < minLng) { + minLngPt = pt + minLng = pt.lng + } } - }()) - L.MarkerCluster.include({ - getConvexHull: function() { - var childMarkers = this.getAllChildMarkers(), - points = [], - p, i - - for (i = childMarkers.length - 1; i >= 0; i--) { - p = childMarkers[i].getLatLng() - points.push(p) - } - - return L.QuickHull.getConvexHull(points); + if (minLat !== maxLat) { + minPt = minLatPt + maxPt = maxLatPt + } else { + minPt = minLngPt + maxPt = maxLngPt } - }) -// This code is 100% based on https:// github.com/jawj/OverlappingMarkerSpiderfier-Leaflet -// Huge thanks to jawj for implementing it first to make my job easy :-) + var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs), + this.buildConvexHull([maxPt, minPt], latLngs)) + return ch + } + } + }()) - L.MarkerCluster.include({ + L.MarkerCluster.include({ + getConvexHull: function () { + var childMarkers = this.getAllChildMarkers() + var points = [] + var p; var i - _2PI: Math.PI * 2, - _circleFootSeparation: 25, // related to circumference of circle - _circleStartAngle: 0, + for (i = childMarkers.length - 1; i >= 0; i--) { + p = childMarkers[i].getLatLng() + points.push(p) + } - _spiralFootSeparation: 28, // related to size of spiral (experiment!) - _spiralLengthStart: 11, - _spiralLengthFactor: 5, + return L.QuickHull.getConvexHull(points) + } + }) - _circleSpiralSwitchover: 9, // show spiral instead of circle from this marker count upwards. - // 0 -> always spiral; Infinity -> always circle + // This code is 100% based on https:// github.com/jawj/OverlappingMarkerSpiderfier-Leaflet + // Huge thanks to jawj for implementing it first to make my job easy :-) - spiderfy: function() { - if (this._group._spiderfied === this || this._group._inZoomAnimation) { - return - } + L.MarkerCluster.include({ - var childMarkers = this.getAllChildMarkers(null, true), - group = this._group, - map = group._map, - center = map.latLngToLayerPoint(this._latlng), - positions; + _2PI: Math.PI * 2, + _circleFootSeparation: 25, // related to circumference of circle + _circleStartAngle: 0, - this._group._unspiderfy(); - this._group._spiderfied = this; + _spiralFootSeparation: 28, // related to size of spiral (experiment!) + _spiralLengthStart: 11, + _spiralLengthFactor: 5, - // TODO Maybe: childMarkers order by distance to center + _circleSpiralSwitchover: 9, // show spiral instead of circle from this marker count upwards. + // 0 -> always spiral; Infinity -> always circle - if (childMarkers.length >= this._circleSpiralSwitchover) { - positions = this._generatePointsSpiral(childMarkers.length, center) - } else { - center.y += 10 // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons. - positions = this._generatePointsCircle(childMarkers.length, center) - } + spiderfy: function () { + if (this._group._spiderfied === this || this._group._inZoomAnimation) { + return + } - this._animationSpiderfy(childMarkers, positions) - }, + var childMarkers = this.getAllChildMarkers(null, true) + var group = this._group + var map = group._map + var center = map.latLngToLayerPoint(this._latlng) + var positions - unspiderfy: function(zoomDetails) { - // <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param> - if (this._group._inZoomAnimation) { - return - } - this._animationUnspiderfy(zoomDetails) + this._group._unspiderfy() + this._group._spiderfied = this - this._group._spiderfied = null - }, + // TODO Maybe: childMarkers order by distance to center - _generatePointsCircle: function(count, centerPt) { - var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count), - legLength = circumference / this._2PI, // radius from circumference - angleStep = this._2PI / count, - res = [], - i, angle + if (childMarkers.length >= this._circleSpiralSwitchover) { + positions = this._generatePointsSpiral(childMarkers.length, center) + } else { + center.y += 10 // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons. + positions = this._generatePointsCircle(childMarkers.length, center) + } - legLength = Math.max(legLength, 35) // Minimum distance to get outside the cluster icon. + this._animationSpiderfy(childMarkers, positions) + }, - res.length = count + unspiderfy: function (zoomDetails) { + // <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param> + if (this._group._inZoomAnimation) { + return + } + this._animationUnspiderfy(zoomDetails) - for (i = 0; i < count; i++) { // Clockwise, like spiral. - angle = this._circleStartAngle + i * angleStep; - res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round(); - } + this._group._spiderfied = null + }, - return res - }, + _generatePointsCircle: function (count, centerPt) { + var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count) + var legLength = circumference / this._2PI // radius from circumference + var angleStep = this._2PI / count + var res = [] + var i; var angle - _generatePointsSpiral: function(count, centerPt) { - var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier, - legLength = spiderfyDistanceMultiplier * this._spiralLengthStart, - separation = spiderfyDistanceMultiplier * this._spiralFootSeparation, - lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI, - angle = 0, - res = [], - i + legLength = Math.max(legLength, 35) // Minimum distance to get outside the cluster icon. - res.length = count + res.length = count - // Higher index, closer position to cluster center. - for (i = count; i >= 0; i--) { - // Skip the first position, so that we are already farther from center and we avoid - // being under the default cluster icon (especially important for Circle Markers). - if (i < count) { - res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round() - } - angle += separation / legLength + i * 0.0005 - legLength += lengthFactor / angle - } - return res; - }, + for (i = 0; i < count; i++) { // Clockwise, like spiral. + angle = this._circleStartAngle + i * angleStep + res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round() + } - _noanimationUnspiderfy: function() { - var group = this._group, - map = group._map, - fg = group._featureGroup, - childMarkers = this.getAllChildMarkers(null, true), - m, i + return res + }, - group._ignoreMove = true + _generatePointsSpiral: function (count, centerPt) { + var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier + var legLength = spiderfyDistanceMultiplier * this._spiralLengthStart + var separation = spiderfyDistanceMultiplier * this._spiralFootSeparation + var lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI + var angle = 0 + var res = [] + var i - this.setOpacity(1) - for (i = childMarkers.length - 1; i >= 0; i--) { - m = childMarkers[i] + res.length = count - fg.removeLayer(m) - - if (m._preSpiderfyLatlng) { - m.setLatLng(m._preSpiderfyLatlng) - delete m._preSpiderfyLatlng - } - if (m.setZIndexOffset) { - m.setZIndexOffset(0) - } - - if (m._spiderLeg) { - map.removeLayer(m._spiderLeg) - delete m._spiderLeg - } - } - - group.fire('unspiderfied', { - cluster: this, - markers: childMarkers - }) - group._ignoreMove = false - group._spiderfied = null + // Higher index, closer position to cluster center. + for (i = count; i >= 0; i--) { + // Skip the first position, so that we are already farther from center and we avoid + // being under the default cluster icon (especially important for Circle Markers). + if (i < count) { + res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round() } - }) + angle += separation / legLength + i * 0.0005 + legLength += lengthFactor / angle + } + return res + }, -// Non Animated versions of everything - L.MarkerClusterNonAnimated = L.MarkerCluster.extend({ - _animationSpiderfy: function(childMarkers, positions) { - var group = this._group, - map = group._map, - fg = group._featureGroup, - legOptions = this._group.options.spiderLegPolylineOptions, - i, m, leg, newPos + _noanimationUnspiderfy: function () { + var group = this._group + var map = group._map + var fg = group._featureGroup + var childMarkers = this.getAllChildMarkers(null, true) + var m; var i - group._ignoreMove = true + group._ignoreMove = true - // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition. - // The reverse order trick no longer improves performance on modern browsers. - for (i = 0; i < childMarkers.length; i++) { - newPos = map.layerPointToLatLng(positions[i]) - m = childMarkers[i] + this.setOpacity(1) + for (i = childMarkers.length - 1; i >= 0; i--) { + m = childMarkers[i] - // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it. - leg = new L.Polyline([this._latlng, newPos], legOptions) - map.addLayer(leg) - m._spiderLeg = leg + fg.removeLayer(m) - // Now add the marker. - m._preSpiderfyLatlng = m._latlng - m.setLatLng(newPos) - if (m.setZIndexOffset) { - m.setZIndexOffset(1000000) // Make these appear on top of EVERYTHING - } - - fg.addLayer(m) - } - this.setOpacity(0.3) - - group._ignoreMove = false - group.fire('spiderfied', { - cluster: this, - markers: childMarkers - }) - }, - - _animationUnspiderfy: function() { - this._noanimationUnspiderfy() + if (m._preSpiderfyLatlng) { + m.setLatLng(m._preSpiderfyLatlng) + delete m._preSpiderfyLatlng } - }) - -// Animated versions here - L.MarkerCluster.include({ - - _animationSpiderfy: function(childMarkers, positions) { - var me = this, - group = this._group, - map = group._map, - fg = group._featureGroup, - thisLayerLatLng = this._latlng, - thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng), - svg = L.Path.SVG, - legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation. - finalLegOpacity = legOptions.opacity, - i, m, leg, legPath, legLength, newPos - - if (finalLegOpacity === undefined) { - finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity; - } - - if (svg) { - // If the initial opacity of the spider leg is not 0 then it appears before the animation starts. - legOptions.opacity = 0 - - // Add the class for CSS transitions. - legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg' - } else { - // Make sure we have a defined opacity. - legOptions.opacity = finalLegOpacity - } - - group._ignoreMove = true - - // Add markers and spider legs to map, hidden at our center point. - // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition. - // The reverse order trick no longer improves performance on modern browsers. - for (i = 0; i < childMarkers.length; i++) { - m = childMarkers[i] - - newPos = map.layerPointToLatLng(positions[i]) - - // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it. - leg = new L.Polyline([thisLayerLatLng, newPos], legOptions) - map.addLayer(leg) - m._spiderLeg = leg - - // Explanations: https:// jakearchibald.com/2013/animated-line-drawing-svg/ - // In our case the transition property is declared in the CSS file. - if (svg) { - legPath = leg._path - legLength = legPath.getTotalLength() + 0.1 // Need a small extra length to avoid remaining dot in Firefox. - legPath.style.strokeDasharray = legLength // Just 1 length is enough, it will be duplicated. - legPath.style.strokeDashoffset = legLength - } - - // If it is a marker, add it now and we'll animate it out - if (m.setZIndexOffset) { - m.setZIndexOffset(1000000) // Make normal markers appear on top of EVERYTHING - } - if (m.clusterHide) { - m.clusterHide() - } - - // Vectors just get immediately added - fg.addLayer(m) - - if (m._setPos) { - m._setPos(thisLayerPos) - } - } - - group._forceLayout() - group._animationStart() - - // Reveal markers and spider legs. - for (i = childMarkers.length - 1; i >= 0; i--) { - newPos = map.layerPointToLatLng(positions[i]) - m = childMarkers[i] - - // Move marker to new position - m._preSpiderfyLatlng = m._latlng - m.setLatLng(newPos) - - if (m.clusterShow) { - m.clusterShow() - } - - // Animate leg (animation is actually delegated to CSS transition). - if (svg) { - leg = m._spiderLeg - legPath = leg._path - legPath.style.strokeDashoffset = 0 - // legPath.style.strokeOpacity = finalLegOpacity; - leg.setStyle({ opacity: finalLegOpacity }) - } - } - this.setOpacity(0.3) - - group._ignoreMove = false - - setTimeout(function() { - group._animationEnd() - group.fire('spiderfied', { - cluster: me, - markers: childMarkers - }) - }, 200) - }, - - _animationUnspiderfy: function(zoomDetails) { - var me = this, - group = this._group, - map = group._map, - fg = group._featureGroup, - thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng), - childMarkers = this.getAllChildMarkers(null, true), - svg = L.Path.SVG, - m, i, leg, legPath, legLength, nonAnimatable - - group._ignoreMove = true - group._animationStart() - - // Make us visible and bring the child markers back in - this.setOpacity(1) - for (i = childMarkers.length - 1; i >= 0; i--) { - m = childMarkers[i] - - // Marker was added to us after we were spiderfied - if (!m._preSpiderfyLatlng) { - continue - } - - // Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll - m.closePopup() - - // Fix up the location to the real one - m.setLatLng(m._preSpiderfyLatlng) - delete m._preSpiderfyLatlng - - // Hack override the location to be our center - nonAnimatable = true - if (m._setPos) { - m._setPos(thisLayerPos) - nonAnimatable = false - } - if (m.clusterHide) { - m.clusterHide() - nonAnimatable = false - } - if (nonAnimatable) { - fg.removeLayer(m) - } - - // Animate the spider leg back in (animation is actually delegated to CSS transition). - if (svg) { - leg = m._spiderLeg - legPath = leg._path - legLength = legPath.getTotalLength() + 0.1 - legPath.style.strokeDashoffset = legLength - leg.setStyle({ opacity: 0 }) - } - } - - group._ignoreMove = false - - setTimeout(function() { - // If we have only <= one child left then that marker will be shown on the map so don't remove it! - var stillThereChildCount = 0; - for (i = childMarkers.length - 1; i >= 0; i--) { - m = childMarkers[i] - if (m._spiderLeg) { - stillThereChildCount++ - } - } - - for (i = childMarkers.length - 1; i >= 0; i--) { - m = childMarkers[i] - - if (!m._spiderLeg) { // Has already been unspiderfied - continue - } - - if (m.clusterShow) { - m.clusterShow() - } - if (m.setZIndexOffset) { - m.setZIndexOffset(0) - } - - if (stillThereChildCount > 1) { - fg.removeLayer(m) - } - - map.removeLayer(m._spiderLeg) - delete m._spiderLeg - } - group._animationEnd() - group.fire('unspiderfied', { - cluster: me, - markers: childMarkers - }) - }, 200) + if (m.setZIndexOffset) { + m.setZIndexOffset(0) } - }) - L.MarkerClusterGroup.include({ - // The MarkerCluster currently spiderfied (if any) - _spiderfied: null, - - unspiderfy: function() { - this._unspiderfy.apply(this, arguments); - }, - - _spiderfierOnAdd: function() { - this._map.on('click', this._unspiderfyWrapper, this) - - if (this._map.options.zoomAnimation) { - this._map.on('zoomstart', this._unspiderfyZoomStart, this) - } - // Browsers without zoomAnimation or a big zoom don't fire zoomstart - this._map.on('zoomend', this._noanimationUnspiderfy, this) - - if (!L.Browser.touch) { - this._map.getRenderer(this); - // Needs to happen in the pageload, not after, or animations don't work in webkit - // http:// stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements - // Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable - } - }, - - _spiderfierOnRemove: function() { - this._map.off('click', this._unspiderfyWrapper, this) - this._map.off('zoomstart', this._unspiderfyZoomStart, this) - this._map.off('zoomanim', this._unspiderfyZoomAnim, this) - this._map.off('zoomend', this._noanimationUnspiderfy, this) - - // Ensure that markers are back where they should be - // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane - this._noanimationUnspiderfy() - }, - - // On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated) - // This means we can define the animation they do rather than Markers doing an animation to their actual location - _unspiderfyZoomStart: function() { - if (!this._map) { // May have been removed from the map by a zoomEnd handler - return - } - - this._map.on('zoomanim', this._unspiderfyZoomAnim, this); - }, - - _unspiderfyZoomAnim: function(zoomDetails) { - // Wait until the first zoomanim after the user has finished touch-zooming before running the animation - if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) { - return - } - - this._map.off('zoomanim', this._unspiderfyZoomAnim, this) - this._unspiderfy(zoomDetails) - }, - - _unspiderfyWrapper: function() { - // <summary>_unspiderfy but passes no arguments</summary> - this._unspiderfy() - }, - - _unspiderfy: function(zoomDetails) { - if (this._spiderfied) { - this._spiderfied.unspiderfy(zoomDetails) - } - }, - - _noanimationUnspiderfy: function() { - if (this._spiderfied) { - this._spiderfied._noanimationUnspiderfy() - } - }, - - // If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc - _unspiderfyLayer: function(layer) { - if (layer._spiderLeg) { - this._featureGroup.removeLayer(layer) - - if (layer.clusterShow) { - layer.clusterShow() - } - // Position will be fixed up immediately in _animationUnspiderfy - if (layer.setZIndexOffset) { - layer.setZIndexOffset(0) - } - - this._map.removeLayer(layer._spiderLeg) - delete layer._spiderLeg - } + if (m._spiderLeg) { + map.removeLayer(m._spiderLeg) + delete m._spiderLeg } - }); + } - /** + group.fire('unspiderfied', { + cluster: this, + markers: childMarkers + }) + group._ignoreMove = false + group._spiderfied = null + } + }) + + // Non Animated versions of everything + L.MarkerClusterNonAnimated = L.MarkerCluster.extend({ + _animationSpiderfy: function (childMarkers, positions) { + var group = this._group + var map = group._map + var fg = group._featureGroup + var legOptions = this._group.options.spiderLegPolylineOptions + var i; var m; var leg; var newPos + + group._ignoreMove = true + + // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition. + // The reverse order trick no longer improves performance on modern browsers. + for (i = 0; i < childMarkers.length; i++) { + newPos = map.layerPointToLatLng(positions[i]) + m = childMarkers[i] + + // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it. + leg = new L.Polyline([this._latlng, newPos], legOptions) + map.addLayer(leg) + m._spiderLeg = leg + + // Now add the marker. + m._preSpiderfyLatlng = m._latlng + m.setLatLng(newPos) + if (m.setZIndexOffset) { + m.setZIndexOffset(1000000) // Make these appear on top of EVERYTHING + } + + fg.addLayer(m) + } + this.setOpacity(0.3) + + group._ignoreMove = false + group.fire('spiderfied', { + cluster: this, + markers: childMarkers + }) + }, + + _animationUnspiderfy: function () { + this._noanimationUnspiderfy() + } + }) + + // Animated versions here + L.MarkerCluster.include({ + + _animationSpiderfy: function (childMarkers, positions) { + var me = this + var group = this._group + var map = group._map + var fg = group._featureGroup + var thisLayerLatLng = this._latlng + var thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng) + var svg = L.Path.SVG + var legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions) // Copy the options so that we can modify them for animation. + var finalLegOpacity = legOptions.opacity + var i; var m; var leg; var legPath; var legLength; var newPos + + if (finalLegOpacity === undefined) { + finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity + } + + if (svg) { + // If the initial opacity of the spider leg is not 0 then it appears before the animation starts. + legOptions.opacity = 0 + + // Add the class for CSS transitions. + legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg' + } else { + // Make sure we have a defined opacity. + legOptions.opacity = finalLegOpacity + } + + group._ignoreMove = true + + // Add markers and spider legs to map, hidden at our center point. + // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition. + // The reverse order trick no longer improves performance on modern browsers. + for (i = 0; i < childMarkers.length; i++) { + m = childMarkers[i] + + newPos = map.layerPointToLatLng(positions[i]) + + // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it. + leg = new L.Polyline([thisLayerLatLng, newPos], legOptions) + map.addLayer(leg) + m._spiderLeg = leg + + // Explanations: https:// jakearchibald.com/2013/animated-line-drawing-svg/ + // In our case the transition property is declared in the CSS file. + if (svg) { + legPath = leg._path + legLength = legPath.getTotalLength() + 0.1 // Need a small extra length to avoid remaining dot in Firefox. + legPath.style.strokeDasharray = legLength // Just 1 length is enough, it will be duplicated. + legPath.style.strokeDashoffset = legLength + } + + // If it is a marker, add it now and we'll animate it out + if (m.setZIndexOffset) { + m.setZIndexOffset(1000000) // Make normal markers appear on top of EVERYTHING + } + if (m.clusterHide) { + m.clusterHide() + } + + // Vectors just get immediately added + fg.addLayer(m) + + if (m._setPos) { + m._setPos(thisLayerPos) + } + } + + group._forceLayout() + group._animationStart() + + // Reveal markers and spider legs. + for (i = childMarkers.length - 1; i >= 0; i--) { + newPos = map.layerPointToLatLng(positions[i]) + m = childMarkers[i] + + // Move marker to new position + m._preSpiderfyLatlng = m._latlng + m.setLatLng(newPos) + + if (m.clusterShow) { + m.clusterShow() + } + + // Animate leg (animation is actually delegated to CSS transition). + if (svg) { + leg = m._spiderLeg + legPath = leg._path + legPath.style.strokeDashoffset = 0 + // legPath.style.strokeOpacity = finalLegOpacity; + leg.setStyle({ opacity: finalLegOpacity }) + } + } + this.setOpacity(0.3) + + group._ignoreMove = false + + setTimeout(function () { + group._animationEnd() + group.fire('spiderfied', { + cluster: me, + markers: childMarkers + }) + }, 200) + }, + + _animationUnspiderfy: function (zoomDetails) { + var me = this + var group = this._group + var map = group._map + var fg = group._featureGroup + var thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng) + var childMarkers = this.getAllChildMarkers(null, true) + var svg = L.Path.SVG + var m; var i; var leg; var legPath; var legLength; var nonAnimatable + + group._ignoreMove = true + group._animationStart() + + // Make us visible and bring the child markers back in + this.setOpacity(1) + for (i = childMarkers.length - 1; i >= 0; i--) { + m = childMarkers[i] + + // Marker was added to us after we were spiderfied + if (!m._preSpiderfyLatlng) { + continue + } + + // Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll + m.closePopup() + + // Fix up the location to the real one + m.setLatLng(m._preSpiderfyLatlng) + delete m._preSpiderfyLatlng + + // Hack override the location to be our center + nonAnimatable = true + if (m._setPos) { + m._setPos(thisLayerPos) + nonAnimatable = false + } + if (m.clusterHide) { + m.clusterHide() + nonAnimatable = false + } + if (nonAnimatable) { + fg.removeLayer(m) + } + + // Animate the spider leg back in (animation is actually delegated to CSS transition). + if (svg) { + leg = m._spiderLeg + legPath = leg._path + legLength = legPath.getTotalLength() + 0.1 + legPath.style.strokeDashoffset = legLength + leg.setStyle({ opacity: 0 }) + } + } + + group._ignoreMove = false + + setTimeout(function () { + // If we have only <= one child left then that marker will be shown on the map so don't remove it! + var stillThereChildCount = 0 + for (i = childMarkers.length - 1; i >= 0; i--) { + m = childMarkers[i] + if (m._spiderLeg) { + stillThereChildCount++ + } + } + + for (i = childMarkers.length - 1; i >= 0; i--) { + m = childMarkers[i] + + if (!m._spiderLeg) { // Has already been unspiderfied + continue + } + + if (m.clusterShow) { + m.clusterShow() + } + if (m.setZIndexOffset) { + m.setZIndexOffset(0) + } + + if (stillThereChildCount > 1) { + fg.removeLayer(m) + } + + map.removeLayer(m._spiderLeg) + delete m._spiderLeg + } + group._animationEnd() + group.fire('unspiderfied', { + cluster: me, + markers: childMarkers + }) + }, 200) + } + }) + + L.MarkerClusterGroup.include({ + // The MarkerCluster currently spiderfied (if any) + _spiderfied: null, + + unspiderfy: function () { + this._unspiderfy.apply(this, arguments) + }, + + _spiderfierOnAdd: function () { + this._map.on('click', this._unspiderfyWrapper, this) + + if (this._map.options.zoomAnimation) { + this._map.on('zoomstart', this._unspiderfyZoomStart, this) + } + // Browsers without zoomAnimation or a big zoom don't fire zoomstart + this._map.on('zoomend', this._noanimationUnspiderfy, this) + + if (!L.Browser.touch) { + this._map.getRenderer(this) + // Needs to happen in the pageload, not after, or animations don't work in webkit + // http:// stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements + // Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable + } + }, + + _spiderfierOnRemove: function () { + this._map.off('click', this._unspiderfyWrapper, this) + this._map.off('zoomstart', this._unspiderfyZoomStart, this) + this._map.off('zoomanim', this._unspiderfyZoomAnim, this) + this._map.off('zoomend', this._noanimationUnspiderfy, this) + + // Ensure that markers are back where they should be + // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane + this._noanimationUnspiderfy() + }, + + // On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated) + // This means we can define the animation they do rather than Markers doing an animation to their actual location + _unspiderfyZoomStart: function () { + if (!this._map) { // May have been removed from the map by a zoomEnd handler + return + } + + this._map.on('zoomanim', this._unspiderfyZoomAnim, this) + }, + + _unspiderfyZoomAnim: function (zoomDetails) { + // Wait until the first zoomanim after the user has finished touch-zooming before running the animation + if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) { + return + } + + this._map.off('zoomanim', this._unspiderfyZoomAnim, this) + this._unspiderfy(zoomDetails) + }, + + _unspiderfyWrapper: function () { + // <summary>_unspiderfy but passes no arguments</summary> + this._unspiderfy() + }, + + _unspiderfy: function (zoomDetails) { + if (this._spiderfied) { + this._spiderfied.unspiderfy(zoomDetails) + } + }, + + _noanimationUnspiderfy: function () { + if (this._spiderfied) { + this._spiderfied._noanimationUnspiderfy() + } + }, + + // If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc + _unspiderfyLayer: function (layer) { + if (layer._spiderLeg) { + this._featureGroup.removeLayer(layer) + + if (layer.clusterShow) { + layer.clusterShow() + } + // Position will be fixed up immediately in _animationUnspiderfy + if (layer.setZIndexOffset) { + layer.setZIndexOffset(0) + } + + this._map.removeLayer(layer._spiderLeg) + delete layer._spiderLeg + } + } + }) + + /** * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing * markers' icon options and refreshing their icon and their parent clusters * accordingly (case where their iconCreateFunction uses data of childMarkers * to make up the cluster icon). */ - - L.MarkerClusterGroup.include({ - /** + L.MarkerClusterGroup.include({ + /** * Updates the icon of all clusters which are parents of the given marker(s). * In singleMarkerMode, also updates the given marker(s) icon. * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)| @@ -2574,105 +2562,104 @@ * clusters need to be updated. If not provided, retrieves all child markers of this. * @returns {L.MarkerClusterGroup} */ - refreshClusters: function(layers) { - if (!layers) { - layers = this._topClusterLevel.getAllChildMarkers() - } else if (layers instanceof L.MarkerClusterGroup) { - layers = layers._topClusterLevel.getAllChildMarkers() - } else if (layers instanceof L.LayerGroup) { - layers = layers._layers - } else if (layers instanceof L.MarkerCluster) { - layers = layers.getAllChildMarkers() - } else if (layers instanceof L.Marker) { - layers = [layers] - } // else: must be an Array(L.Marker)|Map(L.Marker) - this._flagParentsIconsNeedUpdate(layers) - this._refreshClustersIcons() + refreshClusters: function (layers) { + if (!layers) { + layers = this._topClusterLevel.getAllChildMarkers() + } else if (layers instanceof L.MarkerClusterGroup) { + layers = layers._topClusterLevel.getAllChildMarkers() + } else if (layers instanceof L.LayerGroup) { + layers = layers._layers + } else if (layers instanceof L.MarkerCluster) { + layers = layers.getAllChildMarkers() + } else if (layers instanceof L.Marker) { + layers = [layers] + } // else: must be an Array(L.Marker)|Map(L.Marker) + this._flagParentsIconsNeedUpdate(layers) + this._refreshClustersIcons() - // In case of singleMarkerMode, also re-draw the markers. - if (this.options.singleMarkerMode) { - this._refreshSingleMarkerModeMarkers(layers) - } + // In case of singleMarkerMode, also re-draw the markers. + if (this.options.singleMarkerMode) { + this._refreshSingleMarkerModeMarkers(layers) + } - return this - }, + return this + }, - /** + /** * Simply flags all parent clusters of the given markers as having a "dirty" icon. * @param layers Array(L.Marker)|Map(L.Marker) list of markers. * @private */ - _flagParentsIconsNeedUpdate: function(layers) { - var id, parent + _flagParentsIconsNeedUpdate: function (layers) { + var id, parent - // Assumes layers is an Array or an Object whose prototype is non-enumerable. - for (id in layers) { - // Flag parent clusters' icon as "dirty", all the way up. - // Dumb process that flags multiple times upper parents, but still - // much more efficient than trying to be smart and make short lists, - // at least in the case of a hierarchy following a power law: - // http:// jsperf.com/flag-nodes-in-power-hierarchy/2 - parent = layers[id].__parent - while (parent) { - parent._iconNeedsUpdate = true - parent = parent.__parent - } - } - }, + // Assumes layers is an Array or an Object whose prototype is non-enumerable. + for (id in layers) { + // Flag parent clusters' icon as "dirty", all the way up. + // Dumb process that flags multiple times upper parents, but still + // much more efficient than trying to be smart and make short lists, + // at least in the case of a hierarchy following a power law: + // http:// jsperf.com/flag-nodes-in-power-hierarchy/2 + parent = layers[id].__parent + while (parent) { + parent._iconNeedsUpdate = true + parent = parent.__parent + } + } + }, - /** + /** * Re-draws the icon of the supplied markers. * To be used in singleMarkerMode only. * @param layers Array(L.Marker)|Map(L.Marker) list of markers. * @private */ - _refreshSingleMarkerModeMarkers: function(layers) { - var id, layer + _refreshSingleMarkerModeMarkers: function (layers) { + var id, layer - for (id in layers) { - layer = layers[id] + for (id in layers) { + layer = layers[id] - // Make sure we do not override markers that do not belong to THIS group. - if (this.hasLayer(layer)) { - // Need to re-create the icon first, then re-draw the marker. - layer.setIcon(this._overrideMarkerIcon(layer)) - } - } + // Make sure we do not override markers that do not belong to THIS group. + if (this.hasLayer(layer)) { + // Need to re-create the icon first, then re-draw the marker. + layer.setIcon(this._overrideMarkerIcon(layer)) } - }); + } + } + }) - L.Marker.include({ - /** + L.Marker.include({ + /** * Updates the given options in the marker's icon and refreshes the marker. * @param options map object of icon options. * @param directlyRefreshClusters boolean (optional) true to trigger * MCG.refreshClustersOf() right away with this single marker. * @returns {L.Marker} */ - refreshIconOptions: function(options, directlyRefreshClusters) { - var icon = this.options.icon + refreshIconOptions: function (options, directlyRefreshClusters) { + var icon = this.options.icon - L.setOptions(icon, options) + L.setOptions(icon, options) - this.setIcon(icon) + this.setIcon(icon) - // Shortcut to refresh the associated MCG clusters right away. - // To be used when refreshing a single marker. - // Otherwise, better use MCG.refreshClusters() once at the end with - // the list of modified markers. - if (directlyRefreshClusters && this.__parent) { - this.__parent._group.refreshClusters(this) - } + // Shortcut to refresh the associated MCG clusters right away. + // To be used when refreshing a single marker. + // Otherwise, better use MCG.refreshClusters() once at the end with + // the list of modified markers. + if (directlyRefreshClusters && this.__parent) { + this.__parent._group.refreshClusters(this) + } - return this - } - }) + return this + } + }) - exports.MarkerClusterGroup = MarkerClusterGroup - exports.MarkerCluster = MarkerCluster - + exports.MarkerClusterGroup = MarkerClusterGroup + exports.MarkerCluster = MarkerCluster } export default { - init + init } diff --git a/src/components/plugin/wmts_plugins.js b/src/components/plugin/wmts_plugins.js index 7c6e370..55fe18a 100644 --- a/src/components/plugin/wmts_plugins.js +++ b/src/components/plugin/wmts_plugins.js @@ -1,96 +1,96 @@ /* eslint-disable no-prototype-builtins */ 'use strict' const init = (L) => { - L.TileLayer.WMTS = L.TileLayer.extend({ - defaultWmtsParams: { - service: 'WMTS', - request: 'GetTile', - version: '1.0.0', - layer: '', - style: '', - tilematrixset: '', - format: 'image/jpeg' - }, - - initialize: function (url, options) { // (String, Object) - this._url = url; - var lOptions= {}; - var cOptions = Object.keys(options); - cOptions.forEach(element=>{ - lOptions[element.toLowerCase()]=options[element]; - }); - var wmtsParams = L.extend({}, this.defaultWmtsParams); - var tileSize = lOptions.tileSize || this.options.tileSize; - if (lOptions.detectRetina && L.Browser.retina) { - wmtsParams.width = wmtsParams.height = tileSize * 2; - } else { - wmtsParams.width = wmtsParams.height = tileSize; - } - for (var i in lOptions) { - // all keys that are in defaultWmtsParams options go to WMTS params - if (wmtsParams.hasOwnProperty(i) && i!="matrixIds") { - wmtsParams[i] = lOptions[i]; - } - } - this.wmtsParams = wmtsParams; - this.matrixIds = options.matrixIds||this.getDefaultMatrix(); - L.setOptions(this, options); - }, - - onAdd: function (map) { - this._crs = this.options.crs || map.options.crs; - L.TileLayer.prototype.onAdd.call(this, map); - }, - - getTileUrl: function (coords) { // (Point, Number) -> String - var tileSize = this.options.tileSize; - var nwPoint = coords.multiplyBy(tileSize); - nwPoint.x+=1; - nwPoint.y-=1; - var sePoint = nwPoint.add(new L.Point(tileSize, tileSize)); - var zoom = this._tileZoom; - var nw = this._crs.project(this._map.unproject(nwPoint, zoom)); - var se = this._crs.project(this._map.unproject(sePoint, zoom)); - var tilewidth = se.x-nw.x; - var ident = this.matrixIds[zoom].identifier; - var tilematrix = this.wmtsParams.tilematrixset + ":" + ident; - var X0 = this.matrixIds[zoom].topLeftCorner.lng; - var Y0 = this.matrixIds[zoom].topLeftCorner.lat; - var tilecol=Math.floor((nw.x-X0)/tilewidth); - var tilerow=-Math.floor((nw.y-Y0)/tilewidth); - var url = L.Util.template(this._url, {s: this._getSubdomain(coords)}); - return url + L.Util.getParamString(this.wmtsParams, url) + "&tilematrix=" + tilematrix + "&tilerow=" + tilerow +"&tilecol=" + tilecol; - }, - - setParams: function (params, noRedraw) { - L.extend(this.wmtsParams, params); - if (!noRedraw) { - this.redraw(); - } - return this; - }, - - getDefaultMatrix : function () { - /** - * the matrix3857 represents the projection + L.TileLayer.WMTS = L.TileLayer.extend({ + defaultWmtsParams: { + service: 'WMTS', + request: 'GetTile', + version: '1.0.0', + layer: '', + style: '', + tilematrixset: '', + format: 'image/jpeg' + }, + + initialize: function (url, options) { // (String, Object) + this._url = url + var lOptions = {} + var cOptions = Object.keys(options) + cOptions.forEach(element => { + lOptions[element.toLowerCase()] = options[element] + }) + var wmtsParams = L.extend({}, this.defaultWmtsParams) + var tileSize = lOptions.tileSize || this.options.tileSize + if (lOptions.detectRetina && L.Browser.retina) { + wmtsParams.width = wmtsParams.height = tileSize * 2 + } else { + wmtsParams.width = wmtsParams.height = tileSize + } + for (var i in lOptions) { + // all keys that are in defaultWmtsParams options go to WMTS params + if (wmtsParams.hasOwnProperty(i) && i != 'matrixIds') { + wmtsParams[i] = lOptions[i] + } + } + this.wmtsParams = wmtsParams + this.matrixIds = options.matrixIds || this.getDefaultMatrix() + L.setOptions(this, options) + }, + + onAdd: function (map) { + this._crs = this.options.crs || map.options.crs + L.TileLayer.prototype.onAdd.call(this, map) + }, + + getTileUrl: function (coords) { // (Point, Number) -> String + var tileSize = this.options.tileSize + var nwPoint = coords.multiplyBy(tileSize) + nwPoint.x += 1 + nwPoint.y -= 1 + var sePoint = nwPoint.add(new L.Point(tileSize, tileSize)) + var zoom = this._tileZoom + var nw = this._crs.project(this._map.unproject(nwPoint, zoom)) + var se = this._crs.project(this._map.unproject(sePoint, zoom)) + var tilewidth = se.x - nw.x + var ident = this.matrixIds[zoom].identifier + var tilematrix = this.wmtsParams.tilematrixset + ':' + ident + var X0 = this.matrixIds[zoom].topLeftCorner.lng + var Y0 = this.matrixIds[zoom].topLeftCorner.lat + var tilecol = Math.floor((nw.x - X0) / tilewidth) + var tilerow = -Math.floor((nw.y - Y0) / tilewidth) + var url = L.Util.template(this._url, { s: this._getSubdomain(coords) }) + return url + L.Util.getParamString(this.wmtsParams, url) + '&tilematrix=' + tilematrix + '&tilerow=' + tilerow + '&tilecol=' + tilecol + }, + + setParams: function (params, noRedraw) { + L.extend(this.wmtsParams, params) + if (!noRedraw) { + this.redraw() + } + return this + }, + + getDefaultMatrix: function () { + /** + * the matrix3857 represents the projection * for in the IGN WMTS for the google coordinates. */ - var matrixIds3857 = new Array(22); - for (var i= 0; i<22; i++) { - matrixIds3857[i]= { - identifier : "" + i, - topLeftCorner : new L.LatLng(20037508.3428,-20037508.3428) - }; - } - return matrixIds3857; + var matrixIds3857 = new Array(22) + for (var i = 0; i < 22; i++) { + matrixIds3857[i] = { + identifier: '' + i, + topLeftCorner: new L.LatLng(20037508.3428, -20037508.3428) } - }); - - L.tileLayer.wmts = function (url, options) { - return new L.TileLayer.WMTS(url, options); - }; + } + return matrixIds3857 + } + }) + + L.tileLayer.wmts = function (url, options) { + return new L.TileLayer.WMTS(url, options) + } } export default { - init + init } diff --git a/src/utils/tools.js b/src/utils/tools.js index 9aeb1f0..0838a7f 100644 --- a/src/utils/tools.js +++ b/src/utils/tools.js @@ -1,9 +1,9 @@ import * as $CONST from './constant' +import { Message } from 'element-ui' // import { notify } from '@nutui/nutui' export const _ = require('lodash') -const notify = window.vm.$notify /** * 闆嗗悎杞崲涓篔SON * @param obj collection鏁版嵁 @@ -993,23 +993,38 @@ } export function success (msg = $CONST.MSG_SYS_SUCCESS) { - notify.success(msg) + Message({ + message: msg, + type: 'success' + }) } export function fail (msg = $CONST.MSG_SYS_FAIL) { - notify.warn(msg) + Message({ + message: msg, + type: 'error' + }) } export function error (msg = $CONST.MSG_SYS_ERR) { - notify.danger(msg) + Message({ + message: msg, + type: 'error' + }) } export function warning (msg = $CONST.MSG_SYS_WARNING) { - notify.warn(msg) + Message({ + message: msg, + type: 'warning' + }) } export function info (msg = $CONST.MSG_SYS_CANCELED) { - notify.primary(msg) + Message({ + message: msg, + type: 'info' + }) } /** -- Gitblit v1.8.0