Leaflet【七】加载海量点数据

本文深入探讨了Leaflet在渲染海量点数据时面临的性能挑战,提出了一种创新的解决方案——利用leaflet-marker-canvas插件。传统的循环绘制Marker方式在数据量巨大时会导致明显的性能下降,而通过将点数据加入Canvas进行批量渲染,显著提高了绘图效率。文章还细致分析了leaflet-marker-canvas的源码,揭示了其内部优化机制,为开发者提供了有效利用该插件的深入见解。

传统方式海量数据加载

在实际的业务开发当中,我们需要加载海量数据,那么我们通常会使用类似下面的方式来加载数据:同时添加一个测试加载耗时的输出

const addManyPoint = () => {
  console.time('代码执行时长');
  const myIcon = L.icon({
    iconUrl: CITY_IMG,
    iconSize: [20, 20]
  });
  const data = [];
  for (let i = 0; i < 1000; i++) {
    data.push({name: i, lng: Math.random() * 360 - 180, lat: Math.random() * 180 - 90});
  }
  data.forEach((item) => {
    const marker = L.marker([item.lat, item.lng], {icon: myIcon}).addTo(map);
    marker.bindPopup(`<b>${item.name}</b>`);
  });
  console.timeEnd('代码执行时长');
};

在上面模拟添加了1000个点,现在看起来还不是很卡,这和浏览器本身性能有关,但是随着数据量的增加,浏览器会卡顿,甚至崩溃。

原因:这是因为leaflet通过这种方式添加的marker对象都是一个dom元素,而浏览器渲染dom元素非常耗费性能,所以导致页面卡顿。

<img src="/web-vite-vue3/src/assets/image/city.png"
     class="leaflet-marker-icon leaflet-zoom-animated leaflet-interactive" alt="Marker" tabindex="0" role="button"
     style="margin-left: -10px; margin-top: -10px; width: 20px; height: 20px; transform: translate3d(925px, 270px, 0px); z-index: 270;">

在这里插入图片描述

leaflet-markers-canvas

在浏览leaflet插件的时候,发现一个叫leaflet-markers-canvas的插件,这个插件可以解决上述的问题,它通过canvas来渲染marker,从而解决上述的问题。

npm install leaflet-markers-canvas

使用插件

  • 创建canvas对象,拿到markersCanvas将其添加到地图上
  • 模拟数据,得到十万个点
  • 通过markersCanvas.addMarkers()方法将marker添加到图层上,添加完之后,就可以看到marker了。
import 'leaflet-markers-canvas';

const addManyMarker = () => {
  // https://www.npmjs.com/package/leaflet-markers-canvas
  console.time('代码执行时长');
  const markersCanvas = new L.MarkersCanvas();
  markersCanvas.addTo(map);

  var icon = L.icon({
    iconUrl: Money,
    iconSize: [20, 20],
    iconAnchor: [0, 0]
  });

  const markers = [];

  for (let i = 0; i < 100000; i++) {
    const marker = L.marker(
        [Math.random() * 180 - 90, Math.random() * 180 - 90],
        {icon}
      )
      .bindPopup(`${i}`)
      .on({
        mouseover(e) {
          this.openPopup();
        },
        mouseout(e) {
          this.closePopup();
        }
      });

    markers.push(marker);
  }

  markersCanvas.addMarkers(markers);
  console.timeEnd('代码执行时长');
};

在这里插入图片描述

方法说明

方法名说明
addTo(map)将该layer添加到map上
getBounds()获取当前的边界范围
redraw()重新绘制整个canvas图层
clear()清空canvas图层
addMarker(marker)添加单个marker
addMarkers(markers)添加多个marker
removeMarker(marker)移除单个marker
removeMarkers(markers)移除多个marker

插件源码分析

看到这个插件的名字大概上也就能猜到他是如何实现的,无非就是将所有的marker通过canvas绘制出来,然后把整个canvas加到地图上就完事了。

源码位置:node_modules/leaflet-markers-canvas/src/leaflet-markers-canvas.js

  • 这一块关于执行逻辑可以看一下这个系列前面对其他插件的源码分析,插件都是继承Layer基类,那执行顺序也是按照基类来的。
  • 初始化initialize,将options赋值给this,
  • 执行onAdd,初始化canvas并且将canvas添加到地图上。添加对应的地图事件
  • addMarkers方法
    • 遍历markers对象,将他们存到_markersTree、_positionsTree当中
    • 上面存储的对象是new RBush(),RBush是一个二叉树,用来存储坐标点,通过二叉树来快速查找坐标点。
    • 在添加数据的时候,单个数据可以通过insert方法,批量数据可以通过load方法。
    • 同理在删除、清空图层的时候也是通过这个二叉树来操作。单个通过remove(marker),多个通过clear(),或者重新给他初始化
const markersCanvas = {
  // https://www.npmjs.com/package/rbush

  _markersTree: new RBush(),
  _positionsTree: new RBush(),
  
  // 添加多个个marker
  addMarkers(markers) {
    const markerBoxes = [];
    const positionBoxes = [];

    markers.forEach((marker) => {
      // 会通过_addMarker去组装这几个对象
      const {markerBox, positionBox, isVisible} = this._addMarker(marker);

      if (markerBox && isVisible) {
        markerBoxes.push(markerBox);
      }

      if (positionBox) {
        positionBoxes.push(positionBox);
      }
    });

    // 通过load全部添加到二叉树中
    this._markersTree.load(markerBoxes);
    this._positionsTree.load(positionBoxes);
  }
};

聚合cluster

除了将marker对象放在一个canvas上进行渲染,还可以通过聚合分组的方式进行渲染,也就是在最上面的层级不展示点信息,只有在放大到一定层级之后才进行渲染marker。可以使用leaflet当中的插件:leaflet.markercluster

npm install leaflet.markercluster

使用

通过聚合分组的方案也可以满足加载大量数据不卡顿,下面是使用案例,创建了十万个点渲染到页面上。

import 'leaflet.markercluster/dist/leaflet.markercluster';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';

const addCluster = () => {
  const clusterLayer = L.markerClusterGroup({
    // showCoverageOnHover: false,
    // zoomToBoundsOnClick: false,
    // spiderfyOnMaxZoom: false,
    // removeOutsideVisibleBounds: false,
    // spiderLegPolylineOptions: {}
    // 自定义聚合样式
    // iconCreateFunction: function (cluster) {
    // return L.divIcon({html: '<b class="bg-#[f40]">' + cluster.getChildCount() + '</b>'});
    // }
  });
  for (let i = 0; i < 100000; i++) {
    const marker = L.marker([18.8567 + Math.random(), 102.3508 + Math.random()], {
      title: `${i}`,
      icon: L.icon({
        iconUrl: Money,
        iconSize: [20, 20],
        iconAnchor: [0, 0]
      })
    });
    marker.on('click', function (event) {
      console.log('marker ====', event.latlng);
    });

    marker.on('clusterclick', function (a) {
      // a.layer is actually a cluster
      console.log('cluster ' + a.layer.getAllChildMarkers().length);
    });
    clusterLayer.addLayer(marker);
  }
  map.addLayer(clusterLayer);
};

在这里插入图片描述

markerClusterGroup配置对象

属性/方法说明
showCoverageOnHover将鼠标悬停在集群上时,它会显示其标记的边界
zoomToBoundsOnClick单击集群时,会缩放到其边界
spiderfyOnMaxZoom当您单击底部缩放级别的集群时,我们会对其进行蜘蛛化,以便您可以查看其所有标记
removeOutsideVisibleBounds为了提高性能,将从地图中删除离视口太远的聚类和标记
spiderLegPolylineOptions允许您指定 PolylineOptions 来设置蜘蛛腿的样式。默认情况下,它们是 { weight: 1.5, color: '#222', opacity: 0.5 }
animate在缩放和蜘蛛化时平滑拆分/合并集群子项
iconCreateFunctionFn,自定义聚合样式
spiderfyShapePositionsFn,覆盖蜘蛛形状位置

拓展

  • openlayer:在openlayer当中那是否也可以通过构建一个canvas来存储这些海量数据加载捏?或者使用他的一个聚合类,一个层级只展示能完全展示的几个
  • mapbox:mapbox加载海量点数据会默认进行一个聚合操作,也就是他不会全部渲染出来,而是在渲染的时候会根据层级去默认隐藏很多,只有层级足够大能够全部展示出来才会显示
  • cesium:cesium当中有一个加载海量点的类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/784017.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vite+vue3拍照上传到nodejs服务器

一:效果展示: 拍照效果 二:Nodejs后端接口代码: 三:前端完整代码:

【productj服务导出文件503问题】

设备服务只要导出文件&#xff0c;就报503&#xff0c;查看K8S发现服务重启 1. 复现问题1.1 问题复现频繁fullGC 宿主pod服务日志监控1.2 小内存复现 接口没啥问题&#xff0c;导出就会导致服务重启 1. 复现问题 当前uat环境配置&#xff1a; pod内存&#xff1a;2G JVM参数&a…

C++入门(C语言过渡)

文章目录 前言一、C关键字二、命名空间三、C输入&输出四、缺省参数五、函数重载六、引用七、inline八、nullptr总结 前言 C是一种通用的、高级的、静态类型的编程语言&#xff0c;它在20世纪80年代由丹尼斯里奇创建的C语言基础上发展而来。以下是C发展的一些重要里程碑。 1…

【目录】阅读须知!全博文、专栏大纲

首先要和大家说一下&#xff0c;博主的文章并不是想到哪里写到哪里&#xff0c;而是以整个大后端为主题&#xff0c;成体系的在写专栏&#xff0c;从和后端紧相关的计算机核心课程开始、到JAVA SE、JAVA EE、到数据库、MQ等各类中间件、再到业务场景、性能优化。当然也会涉及一…

Docker拉取失败,利用github将镜像推送到阿里云

背景 由于近期国内docker镜像地址失效&#xff08;2024年6月份开始&#xff09;&#xff0c;导致pull docker 镜像总是超时。 涉及到的网址和工具 https://github.com/tech-shrimp/docker_image_pusherhttps://hub.docker.com/search阿里云 GITHUB配置 fork https://githu…

FP5207+音频功率放大器的组合解决方案-适用于便携式音频播放器、无线耳机、智能音箱和车载音响系统等高质量音频输出需求的产品,以提高电池供电的效率和输出功率

随着消费者对智能家居的需求增长&#xff0c;智能音响市场成为重要增长点。同时&#xff0c;音响技术也在不断发展&#xff0c;音响及扬声器的功能和性能不断提升。 蓝牙音箱&#xff0c;这类音箱供电是以锂电池为主&#xff0c;一般选用内置升压的音频功放芯片&#xff0c;音响…

Vue框架引入

vue简介 1.1.vue是什么?Vue官网 英文官网: https://vuejs.org/中文官网: https://cn.vuejs.org/ vue是一套构建用户界面的渐进式javascript框架 构建用户界面:将我们手里拿到的数据通过某种办法变成用户可以看见的界面前端工程师的职责:就是在合适的时候发出合适的请求,然后…

Docker-11☆ Docker Compose部署RuoYi-Cloud

一、环境准备 1.安装Docker 附:Docker-02-01☆ Docker在线下载安装与配置(linux) 2.安装Docker Compose 附:Docker-10☆ Docker Compose 二、源码下载 若依官网:RuoYi 若依官方网站 鼠标放到"源码地址"上,点击"RuoYi-Cloud 微服务版"。 跳转至G…

微信如何快速回复信息呢?

时业务繁忙的时候可能会出现一大堆消息需要去回复&#xff0c;很多客户也会来问重复的问题&#xff0c;有时候回复消息也需要一个及时性&#xff0c;如果回复慢了有可能客户就跑了&#xff0c;那这个时候就会体现出自动回复的优势。 只要设置好一个关键词&#xff0c;只要对方…

基于React 实现井字棋

一、简介 这篇文章会基于React 实现井字棋小游戏功能。 二、效果演示 三、技术实现 import {useEffect, useState} from "react";export default (props) > {return <Board/> }const Board () > {let initialState [[, , ], [, , ], [, , ]];const [s…

【CW32F030CxTx StartKit开发板】利用超声波传感器实现智能灯控

目录 1、超声波传感器 2、硬件连线 3. 程序开发 3.1 超声波测距 3.2 LED控制 4. 演示视频 本文首发于21ic。 感谢21ic和武汉芯源提供的测试机会。 在上一篇帖子中介绍了CW32F030CxTxStartKit 评估板的环境构建。本次介绍如何利用超声波传感器实现人来灯亮&#xff0c;人…

Milvus lite start 及存储策略

背景 今天开始写下Milvus&#xff0c;为了方便&#xff0c;我直接使用的是 milvus-lite 版本&#xff0c;default 情况下&#xff0c;你可能不知道他到底将 db 存储到什么位置了。启动 default-server&#xff0c;看下Milvus 的start及存储逻辑 主逻辑 def start(self):sel…

合并pdf的方法,如何合并pdf文件到一个pdf,简单方法

在现代办公和学习中&#xff0c;pdf格式的文件因其跨平台兼容性和安全性得到了广泛应用。然而&#xff0c;有时我们需要将多个pdf文件合并成一个&#xff0c;以便于管理和分享。本文将详细介绍几种合并pdf的方法&#xff0c;帮助读者轻松完成pdf文件的合并工作。 方法一、使用p…

【NLP学习路线的总结】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 目录 0. 👉前言1. 👉前置知识👉基础数学知识👉编程语言👉…

matlab:对带参数a关于x的方程求解

题目 讲解 简洁对各个式子的内部含义用浅显易懂的话语总结出来了&#xff0c;耐心体会 f(a) (x)exp(x)x^ax^(sqrt(x))-100;%因为下面的fzero的第一个数需要一个fun&#xff0c;所以这里有两个句柄&#xff0c;第一个a是输入的&#xff0c;第二个x是需要被解出的 A0:0.1:2;%创…

12种增强Python代码的函数式编程技术

前言 什么是函数式编程&#xff1f; 一句话总结&#xff1a;函数式编程(functional programming)是一种编程范式&#xff0c;之外还有面向对象&#xff08;OOP&#xff09;、面向过程、逻辑式编程等。 函数式编程是一种高度抽象的编程范式&#xff0c;它倡导使用纯函数&#x…

VTD的RDB介绍,从入门到放弃

文章目录 前言一、二、常见的RDB数据类型1、RDB_OBJECT_STATE_BASE_t2、RDB_OBJECT_STATE_EXT_t3、RDB_OBJECT_STATE_t4、RDB_SENSOR_OBJECT_t5、RDB_COORD_t6 RDB_GEOMETRY_t7、RDB_MSG_ENTRY_HDR_t 三、疑惑的问题点&#xff1a;1、在RDB_OBJECT_STATE_EXT_t中这两个的区别是…

前端面试题26(vue3中响应式实现原理)

Vue 3 中响应式系统的实现主要依赖于 ES6 的 Proxy 对象&#xff0c;这与 Vue 2 中使用 Object.defineProperty 的方式有着本质的区别。Proxy 提供了一种更为强大且灵活的方法来拦截和定制对象的操作&#xff0c;例如获取、设置属性值等。下面是对 Vue 3 响应式系统实现方式的详…

5款好用公司监控软件分享|管理者必看

当今社会&#xff0c;企业数据安全和员工工作效率成为了管理者不可忽视的重要议题。 选择合适的公司监控软件&#xff0c;不仅有助于提升管理效率&#xff0c;还能有效保障企业信息安全。 下面小编将为您分享五款备受好评的公司监控软件&#xff0c;助力管理者更好地管理企业…

faskapi好用的模板

在Web开发领域&#xff0c;FastAPI作为一个基于Python的高性能Web框架&#xff0c;因其快速、易用以及强大的功能而备受开发者青睐。关于FastAPI的好用模板&#xff0c;这里介绍几个不同角度的模板或项目框架&#xff0c;以帮助您更好地理解和选择适合自己的起点。 1. FastAPI…