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 }"
/>
-
+
+
+
+
{
+ counter++;
+ let lat = element.lat;
+ let lng = element.lon;
+ var icon = L.icon({
+ iconUrl: "point_marker.svg",
+ iconSize: [8, 8],
+ iconAnchor: [6, 6],
+ });
+ markers.push(L.marker([lat, lng], { icon: icon }));
+ });
+ }
+ }
+ console.log("Loaded point count: " + counter);
+ return markers;
+ },
},
methods: {
...mapMutations({
diff --git a/yarn.lock b/yarn.lock
index 0087925..3f525d2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7503,6 +7503,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+quickselect@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
+ integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
+
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -7525,6 +7530,13 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
+rbush@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf"
+ integrity sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==
+ dependencies:
+ quickselect "^2.0.0"
+
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"