diff --git a/package.json b/package.json index ae6b6ab..69f43c6 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "leaflet": "^1.8.0", "leaflet.heat": "^0.2.0", "moment": "^2.29.4", + "rbush": "^3.0.1", "vue": "^2.6.14", "vue-ctk-date-time-picker": "^2.5.0", "vue-feather-icons": "^5.1.0", diff --git a/public/point_marker.svg b/public/point_marker.svg new file mode 100644 index 0000000..3247183 --- /dev/null +++ b/public/point_marker.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + diff --git a/src/components/LCanvasMarker.vue b/src/components/LCanvasMarker.vue new file mode 100644 index 0000000..6cc7954 --- /dev/null +++ b/src/components/LCanvasMarker.vue @@ -0,0 +1,187 @@ + + + + +## Demo +::: demo + + + +::: + diff --git a/src/components/utils/leaflet-canvas-marker.js b/src/components/utils/leaflet-canvas-marker.js new file mode 100644 index 0000000..0676151 --- /dev/null +++ b/src/components/utils/leaflet-canvas-marker.js @@ -0,0 +1,414 @@ +"use strict"; +/* eslint-disable */ + 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); + }, + + 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); + }, + + addTo: function (map) { + + map.addLayer(this); + return this; + }, + + clearLayers: function() { + + this._latlngMarkers = null; + this._markers = null; + this._redraw(true); + }, + + _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 adj_x = iconSize[0]/2; + var adj_y = iconSize[1]/2; + var ret = [({ + minX: (pointPos.x - adj_x), + minY: (pointPos.y - adj_y), + maxX: (pointPos.x + adj_x), + maxY: (pointPos.y + adj_y), + 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 >= .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 adj_x = iconSize[0]/2; + var adj_y = iconSize[1]/2; + + var newCoords = { + minX: (pointPos.x - adj_x), + minY: (pointPos.y - adj_y), + maxX: (pointPos.x + adj_x), + maxY: (pointPos.y + adj_y), + 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); + }; \ No newline at end of file diff --git a/src/views/Map.vue b/src/views/Map.vue index 9106c78..fb17d30 100644 --- a/src/views/Map.vue +++ b/src/views/Map.vue @@ -24,7 +24,6 @@ :tileSize="tileSize" :options="{ maxNativeZoom, maxZoom, zoomOffset }" /> -