'use strict' import Rbush from 'rbush' 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 = [] }, 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 = [] 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 = '' } } }) L.canvasIconLayer = function(options) { return new CanvasIconLayer(options) } } export default { init }