'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
|
}
|