leaflet,canvas渲染目标,可加载大批量数据

2024-06-20 15:52

本文主要是介绍leaflet,canvas渲染目标,可加载大批量数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基于Leaflet-CanvasMarker: 在Canvas上绘制Marker,而不是每个marker插件一个dom节点,极大地提高了渲染效率。主要代码参考自 https://github.com/eJuke/Leaflet.Canvas-Markers,不过此插件有些Bug,github国内不方便,作者也不维护了,所以在gitee上新建一个仓库进行维护。icon-default.png?t=N7T8https://gitee.com/panzhiyue/Leaflet-CanvasMarker 修改的canvas渲染marker

参数

collisionFlg

  • 类型:boolean
  • 默认值:false

配置是否启用碰撞检测,即重叠图标只显示一个

moveReset

  • 类型:boolean
  • 默认值:false

在move时是否刷新地图

zIndex

  • 类型:number
  • 默认值:null

Leaflet.Marker对象zIndex的默认值

opacity

  • 类型:number
  • 默认值:1

图层的不透明度,0(完全透明)-1(完全不透明)

Leaflet.Marker扩展参数

zIndex

  • 类型:number

显示顺序

Leaflet.Icon扩展参数

rotate

  • 类型:number

旋转角度:Math.PI/2

方法

  • addLayer(marker):向图层添加标记。
  • addLayers(markers):向图层添加标记。
  • removeLayer(marker, redraw):从图层中删除一个标记。redraw为true时删除后直接重绘,默认为true
  • redraw() : 重绘图层
  • addOnClickListener(eventHandler):为所有标记添加通用点击侦听器
  • addOnHoverListener(eventHandler):为所有标记添加悬停监听器
  • addOnMouseDownListener(eventHandler):为所有标记添加鼠标按下监听器
  • addOnMouseUpListener(eventHandler):为所有标记添加鼠标松开监听器

使用:
/*** 加载marker 点位** */export const useLoadMarker = () => {let MapCanvasLayer = null;// 添加10W个数据点let addLargeNumberMarker = () => {console.time("加载渲染canvas目标");// 创建图层MapCanvasLayer = L.canvasMarkerLayer({collisionFlg: false,moveReset: false,userDrawFunc: function (layer, marker, pointPos, size) {// console.log("lllllllllllll", layer);const ctx = layer._ctx;ctx.beginPath();ctx.arc(pointPos.x, pointPos.y, size[0] / 2, 0, 2 * Math.PI);ctx.fillStyle = "rgba(255,12,0,0.4)";ctx.fill();ctx.closePath();},}).addTo(window.MapLeaflet);let icon = L.icon({iconUrl: new URL("../assets/marker.jpg", import.meta.url).href,iconSize: [20, 20],iconAnchor: [10, 9],});// 定义Markerlet markers = [];for (var i = 0; i < 80000; i++) {let marker = L.marker([39.26203 + Math.random() * 1.2, 122.58546 + Math.random() * 2], {icon: L.icon({iconSize: [20, 20],iconAnchor: [10, 9],}),zIndex: 2,riseOnHover: true,});marker.bindTooltip("我是浮动出来的标记" + i, {//添加提示文字permanent: false, //是永久打开还是悬停打开direction: "top", //方向}); //在图层打开markers.push(marker);}// 把marker添加到图层MapCanvasLayer.addLayers(markers);console.timeEnd("加载渲染canvas目标");//定义事件MapCanvasLayer.addOnClickListener(function (e, data) {console.log(data);});MapCanvasLayer.addOnHoverListener(function (e, data) {console.log(data[0].data);});};return {addLargeNumberMarker,};
};

修改版本v1.0,可以自定义画marker,使用canvas画marrker再添加到canvas上

import rbush from "rbush"; //https://www.5axxw.com/wiki/content/7wjc4t
/*** @typedef {Object} MarkerData marker的rubsh数据* @property {Number} MarkerData.minX  marker的经度* @property {Number} MarkerData.minY  marker的纬度* @property {Number} MarkerData.maxX  marker的经度* @property {Number} MarkerData.maxY  marker的纬度* @property {L.Marker} MarkerData.data  marker对象* @example* let latlng=marker.getLatlng();* let markerData={*      minX:latlng.lng,*      minY:latlng.lat,*      maxX:latlng.lng,*      maxY:latlng.lat,*      data:marker* }*//*** @typedef {Object} MarkerBoundsData marker的像素边界rubsh数据* @property {Number} MarkerBoundsData.minX  marker的左上角x轴像素坐标* @property {Number} MarkerBoundsData.minY  marker的左上角y轴像素坐标* @property {Number} MarkerBoundsData.maxX  marker的右下角x轴像素坐标* @property {Number} MarkerBoundsData.maxY  marker的右下角y轴像素坐标* @property {L.Marker} MarkerBoundsData.data  marker对象* @example* let options = marker.options.icon.options;* let minX, minY, maxX, maxY;* minX = pointPos.x - ((options.iconAnchor && options.iconAnchor[0]) || 0);* maxX = minX + options.iconSize[0];* minY = pointPos.y - ((options.iconAnchor && options.iconAnchor[1]) || 0);* maxY = minY + options.iconSize[1];** let markerBounds = {*     minX,*     minY,*     maxX,*     maxY* };*//*** 用于在画布而不是DOM上显示标记的leaflet插件。使用单页1.0.0及更高版本。* 已修改源码适配项目需求* 注意:开启碰撞检测 图标重叠合并 L.icon({ iconAnchor: [10, 9],}); 必须要配置iconAnchor属性*/
export var CanvasMarkerLayer = (L.CanvasMarkerLayer = L.Layer.extend({options: {zIndex: null, //图层dom元素的堆叠顺序collisionFlg: false, //碰撞检测,使用canvas画marker时 不建议开启碰撞会影响性能moveReset: false, //在move时是否刷新地图opacity: 1, //图层透明度userDrawFunc: null, //改源码:自定义canvas画图标},//Add event listeners to initialized section.initialize: function (options) {L.setOptions(this, options);this._onClickListeners = [];this._onHoverListeners = [];this._onMouseDownListeners = [];this._onMouseUpListeners = [];/*** 所有marker的集合* @type {rbush<MarkerData>}*/this._markers = new rbush();this._markers.dirty = 0; //单个插入/删除this._markers.total = 0; //总数/*** 在地图当前范围内的marker的集合* @type {rbush<MarkerData>}*/this._containMarkers = new rbush();/*** 当前显示在地图上的marker的集合* @type {rbush<MarkerData>}*/this._showMarkers = new rbush();/*** 当前显示在地图上的marker的范围集合* @type {rbush<MarkerBoundsData>}*/this._showMarkerBounds = new rbush();},setOptions: function (options) {L.setOptions(this, options);return this.redraw();},/*** 重绘*/redraw: function () {return this._redraw(true);},/*** 获取事件对象** 表示给map添加的监听器* @return {Object} 监听器/函数键值对*/getEvents: function () {var events = {viewreset: this._reset,zoom: this._onZoom,moveend: this._reset,click: this._executeListeners,mousemove: this._executeListeners,mousedown: this._executeListeners,mouseup: this._executeListeners,};if (this._zoomAnimated) {events.zoomanim = this._onAnimZoom;}if (this.options.moveReset) {events.move = this._reset;}return events;},/*** 添加标注* @param {L/Marker} layer 标注* @return {Object} this*/addLayer: function (layer, redraw = true) {if (!(layer.options.pane == "markerPane" && layer.options.icon)) {console.error("Layer isn't a marker");return;}layer._map = this._map;var latlng = layer.getLatLng();L.Util.stamp(layer);this._markers.insert({minX: latlng.lng,minY: latlng.lat,maxX: latlng.lng,maxY: latlng.lat,data: layer,});this._markers.dirty++;this._markers.total++;var isDisplaying = this._map.getBounds().contains(latlng);if (redraw == true && isDisplaying) {this._redraw(true);}return this;},/*** 添加标注数组,在一次性添加许多标注时使用此函数会比循环调用marker函数效率更高* @param {Array.<L/Marker>} layers 标注数组* @return {Object} this*/addLayers: function (layers, redraw = true) {console.time("canvas-layer渲染时间")layers.forEach((layer) => {this.addLayer(layer, false);});if (redraw) {this._redraw(true);}console.timeEnd("canvas-layer渲染时间");return this;},/*** 删除标注* @param {*} layer 标注* @param {boolean=true} redraw 是否重新绘制(默认为true),如果要批量删除可以设置为false,然后手动更新* @return {Object} this*/removeLayer: function (layer, redraw = true) {var self = this;//If we are removed point// 改掩码if (layer && layer["minX"]) layer = layer.data;var latlng = layer.getLatLng();var isDisplaying = self._map.getBounds().contains(latlng);var markerData = {minX: latlng.lng,minY: latlng.lat,maxX: latlng.lng,maxY: latlng.lat,data: layer,};self._markers.remove(markerData, function (a, b) {return a.data._leaflet_id === b.data._leaflet_id;});self._markers.total--;self._markers.dirty++;if (isDisplaying === true && redraw === true) {self._redraw(true);}return this;},/*** 清除所有*/clearLayers: function () {this._markers = new rbush();this._markers.dirty = 0; //单个插入/删除this._markers.total = 0; //总数this._containMarkers = new rbush();this._showMarkers = new rbush();this._showMarkerBounds = new rbush();this._redraw(true);},/*** 继承L.Layer必须实现的方法** 图层Dom节点创建添加到地图容器*/onAdd: function (map) {this._map = map;if (!this._container) this._initCanvas();if (this.options.pane) this.getPane().appendChild(this._container);else map._panes.overlayPane.appendChild(this._container);this._reset();},/*** 继承L.Layer必须实现的方法** 图层Dom节点销毁*/onRemove: function (map) {if (this.options.pane) this.getPane().removeChild(this._container);else map.getPanes().overlayPane.removeChild(this._container);},/*** 绘制图标* @param {L/Marker} marker 图标* @param {L/Point} pointPos 图标中心点在屏幕上的像素位置*/_drawMarker: function (marker, pointPos) {var self = this;//创建图标缓存if (!this._imageLookup) this._imageLookup = {};//没有传入像素位置,则计算marker自身的位置if (!pointPos) {pointPos = self._map.latLngToContainerPoint(marker.getLatLng());}// 改源码:添加构造方法userDrawFunc--canvas图标// S 配置是否启用碰撞检测,即重叠图标只显示一个,使用canvas画marker时 不建议开启碰撞会影响性能let options = marker.options.icon.options;let minX, minY, maxX, maxY;minX = pointPos.x - ((options.iconAnchor && options.iconAnchor[0]) || 0);maxX = minX + options.iconSize[0];minY = pointPos.y - ((options.iconAnchor && options.iconAnchor[1]) || 0);maxY = minY + options.iconSize[1];let markerBounds = {minX,minY,maxX,maxY,};if (this.options.collisionFlg == true) {if (this._showMarkerBounds.collides(markerBounds)) {return;} else {this._showMarkerBounds.insert(markerBounds);let latlng = marker.getLatLng();this._showMarkers.insert({minX,minY,maxX,maxY,lng: latlng.lng,lat: latlng.lat,data: marker,});}}// E 配置是否启用碰撞检测,即重叠图标只显示一个,使用canvas画marker时 不建议开启碰撞会影响性能// console.log("图标------", marker.options.icon.options);if (marker.options.icon.options.iconUrl && !!marker.options.icon.options.iconUrl) {// 使用icon的marker执行//图标图片地址var iconUrl = marker.options.icon.options.iconUrl;//已经有canvas_img对象,表示之前已经绘制过,直接使用,提高渲染效率if (marker.canvas_img) {self._drawImage(marker, pointPos);} else {//图标已经在缓存中if (self._imageLookup[iconUrl]) {marker.canvas_img = self._imageLookup[iconUrl][0];//图片还未加载,把marker添加到预加载列表中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]]:预加载列表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]);});};}}} else if (this.options.userDrawFunc && typeof this.options.userDrawFunc === "function") {// 使用canvas-userDrawFunc的marker执行const size = marker.options.icon.options.iconSize;this.options.userDrawFunc(this, marker, pointPos, size);}},/*** 绘制图标* @param {L/Marker} marker 图标* @param {L/Point} pointPos 图标中心点在屏幕上的像素位置*/_drawImage: function (marker, pointPos) {var options = marker.options.icon.options;this._ctx.save();this._ctx.globalAlpha = this.options.opacity;this._ctx.translate(pointPos.x, pointPos.y);this._ctx.rotate(options.rotate);this._ctx.drawImage(marker.canvas_img,-((options.iconAnchor && options.iconAnchor[0]) || 0),-((options.iconAnchor && options.iconAnchor[1]) || 0),options.iconSize[0],options.iconSize[1]);this._ctx.restore();},/*** 重置画布(大小,位置,内容)*/_reset: function () {var topLeft = this._map.containerPointToLayerPoint([0, 0]);L.DomUtil.setPosition(this._container, topLeft);var size = this._map.getSize();this._container.width = size.x;this._container.height = size.y;this._update();},/*** 重绘画布* @param {boolean} clear 是否清空*/_redraw: function (clear) {console.time("canvas一次重绘时间");this._showMarkerBounds = new rbush();this._showMarkers = new rbush();var self = this;//清空画布if (clear) this._ctx.clearRect(0, 0, this._container.width, this._container.height);if (!this._map || !this._markers) return;var tmp = [];//如果单个插入/删除的数量超过总数的10%,则重建查找以提高效率if (self._markers.dirty / self._markers.total >= 0.1) {self._markers.all().forEach(function (e) {tmp.push(e);});self._markers.clear();self._markers.load(tmp);self._markers.dirty = 0;tmp = [];}//地图地理坐标边界var mapBounds = self._map.getBounds();//适用于runsh的边界对象var mapBoxCoords = {minX: mapBounds.getWest(),minY: mapBounds.getSouth(),maxX: mapBounds.getEast(),maxY: mapBounds.getNorth(),};//查询范围内的图标self._markers.search(mapBoxCoords).forEach(function (e) {//图标屏幕坐标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,pointPos: pointPos,};tmp.push(newCoords);});// console.log("说有目标---", tmp);//需要做碰撞检测则降序排序,zIndex值大的优先绘制;不需要碰撞检测则升序排序,zIndex值的的后绘制tmp.sort((layer1, layer2) => {let zIndex1 = layer1.data.options.zIndex ? layer1.data.options.zIndex : 1;let zIndex2 = layer2.data.options.zIndex ? layer2.data.options.zIndex : 1;return (-zIndex1 + zIndex2) * (this.options.collisionFlg ? 1 : -1);}).forEach((layer) => {//图标屏幕坐标var pointPos = layer.pointPos;self._drawMarker(layer.data, pointPos);});//Clear rBush & Bulk Load for performancethis._containMarkers.clear();this._containMarkers.load(tmp);if (this.options.collisionFlg != true) {this._showMarkers = this._containMarkers;}console.timeEnd("canvas一次重绘时间");return this;},/*** 初始化容器*/_initCanvas: function () {this._container = L.DomUtil.create("canvas", "leaflet-canvas-icon-layer leaflet-layer");if (this.options.zIndex) {this._container.style.zIndex = this.options.zIndex;}var size = this._map.getSize();this._container.width = size.x;this._container.height = size.y;this._ctx = this._container.getContext("2d");var animated = this._map.options.zoomAnimation && L.Browser.any3d;L.DomUtil.addClass(this._container, "leaflet-zoom-" + (animated ? "animated" : "hide"));},/*** 添加click侦听器*/addOnClickListener: function (listener) {this._onClickListeners.push(listener);},/*** 添加hover侦听器*/addOnHoverListener: function (listener) {this._onHoverListeners.push(listener);},/*** 添加mousedown侦听器*/addOnMouseDownListener: function (listener) {this._onMouseDownListeners.push(listener);},/*** 添加mouseup侦听器*/addOnMouseUpListener: function (listener) {this._onMouseUpListeners.push(listener);},/*** 执行侦听器*/_executeListeners: function (event) {if (!this._showMarkers) 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._showMarkers.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);});}if (event.type === "mousedown") {me._onMouseDownListeners.forEach(function (listener) {listener(event, ret);});}if (event.type === "mouseup") {me._onMouseUpListeners.forEach(function (listener) {listener(event, ret);});}} else {me._map._container.style.cursor = "";}},/*** 地图Zoomanim事件监听器函数* @param {Object} env {center:L.LatLng,zoom:number}格式的对象*/_onAnimZoom(ev) {this._updateTransform(ev.center, ev.zoom);},/*** 地图修改zoom事件监听器函数*/_onZoom: function () {this._updateTransform(this._map.getCenter(), this._map.getZoom());},/*** 修改dom原始的transform或position* @param {L/LatLng} center 中心点* @param {number} zoom 地图缩放级别*/_updateTransform: function (center, zoom) {var scale = this._map.getZoomScale(zoom, this._zoom),position = L.DomUtil.getPosition(this._container),viewHalf = this._map.getSize().multiplyBy(0.5),currentCenterPoint = this._map.project(this._center, zoom),destCenterPoint = this._map.project(center, zoom),centerOffset = destCenterPoint.subtract(currentCenterPoint),topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);if (L.Browser.any3d) {L.DomUtil.setTransform(this._container, topLeftOffset, scale);} else {L.DomUtil.setPosition(this._container, topLeftOffset);}},/*** 更新渲染器容器的像素边界(用于以后的定位/大小/剪裁)子类负责触发“update”事件。*/_update: function () {var p = 0,size = this._map.getSize(),min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());this._center = this._map.getCenter();this._zoom = this._map.getZoom();this._redraw();},/*** 设置图层透明度* @param {Number} opacity 图层透明度*/setOpacity(opacity) {this.options.opacity = opacity;return this._redraw(true);},
}));export var canvasMarkerLayer = (L.canvasMarkerLayer = function (options) {return new L.CanvasMarkerLayer(options);
});

这篇关于leaflet,canvas渲染目标,可加载大批量数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1078575

相关文章

【服务器运维】MySQL数据存储至数据盘

查看磁盘及分区 [root@MySQL tmp]# fdisk -lDisk /dev/sda: 21.5 GB, 21474836480 bytes255 heads, 63 sectors/track, 2610 cylindersUnits = cylinders of 16065 * 512 = 8225280 bytesSector size (logical/physical)

加载资源文件失败

背景         自己以前装了一个海康的深度学习算法平台,试用期是一个月,过了一个月之后,因为没有有效注册码或者加密狗的支持了导致无法使用,于是打算卸载掉,在卸载一个软件的时候,无论是使用控制面板还是软件自带的卸载功能,总是卸载不掉,提示“加载资源文件失败”。该软体主要包括以下两部分: 用自带卸载功能卸载的时候分别提示如下:     用控制面板卸载的时候反应很慢,最后也是提示这个

以canvas方式绘制粒子背景效果,感觉还可以

这个是看到项目中别人写好的,感觉这种写法效果还可以,就存留记录下 就是这种的背景效果。如果想改背景颜色可以通过canvas.js文件中的fillStyle值改。 附上demo下载地址。 https://download.csdn.net/download/u012138137/11249872

vue同页面多路由懒加载-及可能存在问题的解决方式

先上图,再解释 图一是多路由页面,图二是路由文件。从图一可以看出每个router-view对应的name都不一样。从图二可以看出层路由对应的组件加载方式要跟图一中的name相对应,并且图二的路由层在跟图一对应的页面中要加上components层,多一个s结尾,里面的的方法名就是图一路由的name值,里面还可以照样用懒加载的方式。 页面上其他的路由在路由文件中也跟图二是一样的写法。 附送可能存在

SQL Server中,查询数据库中有多少个表,以及数据库其余类型数据统计查询

sqlserver查询数据库中有多少个表 sql server 数表:select count(1) from sysobjects where xtype='U'数视图:select count(1) from sysobjects where xtype='V'数存储过程select count(1) from sysobjects where xtype='P' SE

数据时代的数字企业

1.写在前面 讨论数据治理在数字企业中的影响和必要性,并介绍数据治理的核心内容和实践方法。作者强调了数据质量、数据安全、数据隐私和数据合规等方面是数据治理的核心内容,并介绍了具体的实践措施和案例分析。企业需要重视这些方面以实现数字化转型和业务增长。 数字化转型行业小伙伴可以加入我的星球,初衷成为各位数字化转型参考库,星球内容每周更新 个人工作经验资料全部放在这里,包含数据治理、数据要

如何在Java中处理JSON数据?

如何在Java中处理JSON数据? 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨在Java中如何处理JSON数据。JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,在现代应用程序中被广泛使用。Java通过多种库和API提供了处理JSON的能力,我们将深入了解其用法和最佳

两个基因相关性CPTAC蛋白组数据

目录 蛋白数据下载 ①蛋白数据下载 1,TCGA-选择泛癌数据  2,TCGA-TCPA 3,CPTAC(非TCGA) ②蛋白相关性分析 1,数据整理 2,蛋白相关性分析 PCAS在线分析 蛋白数据下载 CPTAC蛋白组学数据库介绍及数据下载分析 – 王进的个人网站 (jingege.wang) ①蛋白数据下载 可以下载泛癌蛋白数据:UCSC Xena (xena

3月份目标——刷完乙级真题

https://www.patest.cn/contests/pat-b-practisePAT (Basic Level) Practice (中文) 标号标题通过提交通过率1001害死人不偿命的(3n+1)猜想 (15)31858792260.41002写出这个数 (20)21702664840.331003我要通过!(20)11071447060.251004成绩排名 (20)159644

VS2012加载失败

1、通过命令提示行工具进入VS安装目录下的Common7\IDE 2、执行devenv.exe /setup /resetuserdata /resetsettings 3、重启VS