Leaflet.canvaslabel在Ajax异步请求时bindPopup无效的解决办法

目录

前言

一、场景重现

1、遇到问题的代码

2、问题排查

二、通过实验验证猜想

1、排查LayerGroup和FeatureGroup

2、排查Leaflet.canvaslabel.js

三、柳暗花明又一村

1、点聚类的办法

2、歪打正着

总结


前言

        在上一篇博客中介绍了基于SpringBoot的全国风景区WebGIS按省展示实践。在这篇博客中,关于旅游景点我是使用Leaflet.canvaslabel.js进行静态标注的,其实在案例中要实现点击风景区点,然后自动弹出它的其它信息,比如景点名称,所在省市县信息,还有设立年份。最开始打算采用bindPopup的方案,在编写代码的过程当中,发现一个有趣的问题。在点击所在省份之后,再把景点信息列表在地图上渲染出来,然后针对每个点绑定popup。但实际的情况是,界面上的景点点击后,没有任何反应,奇怪的是有时候又可以点。当时想着,如果是代码的绑定得有问题,肯定是都出不来,不存在一份代码,两种效果的情况。

        遇到了问题,便解决问题。在没有找到具体的问题前,首先检查代码的问题,然后检查相应的组件是否有什么缺陷,最后再梳理解决办法,大致的思路如此。本文在此背景下诞生,博文首先详细介绍了风景区展示时具体的问题,然后分析排查思路和方向,接着使用不同的方案验证思路,最后给出最终的解决方案。解决问题就是对症下药一样,找到了病根基本上就是药到病除,解决了问题的酸爽感一定让很多技术圈朋友深有体会吧。

一、场景重现

        我想在讲述一些解决办法之前,还是把碰到的详细问题描述清楚。首先是不至于让看到博文的朋友感到很诧异,第二是对于解决方案的前提条件有一个具象的描述。能让我们知道为什么会这样?出现了什么情况?我们要解决的问题是什么。这里结合代码和实际效果来讲解,这样可能比较有针对性。

1、遇到问题的代码

        这里将之前的第一版代码贴出来,肯定有朋友第一眼就能看到问题的所在,但当时折腾了我很久。也许旁观者清,也许是经验比较多,曾经遇到过,一下子就解决了。

/* 展示省份geojson范围*/
function showProvince(id){var myStyle = {color:"white",weight:5,"opacity":1,fillOpacity: 0};$.ajax({  type:"get",  url:prefix + "/geojson/" + id, dataType:"json",  cache:false,processData:false,success:function(result){if(undefined != provinceAreaLayer ){provinceAreaLayer.removeFrom(mymap);//先移除}if(result.code == web_status.SUCCESS){var geojson = JSON.parse(result.data);provinceAreaLayer = L.geoJSON(geojson,{style:myStyle}).addTo(mymap);//同时设置中心位置和级别,一般省份设置为7mymap.setView(provinceAreaLayer.getBounds().getCenter(),7);}},error:function(){$.modal.alertWarning("获取空间信息失败");});
}/*风景区展示*/        
function showScenicSpot(code){$.ajax({  type:"get",  url:prefix + "/datalist/" + code,  dataType:"json",  success:function(result){if(result.code == web_status.SUCCESS){var strokeStyleSet = "#23168d";var lat,lng,cityInfo;for(var i=0;i<result.data.length;i++){var dataInfo = result.data[i];var geomObj = JSON.parse(dataInfo.geomJson);if(i == 0){lat = geomObj.coordinates[1];lng = geomObj.coordinates[0];continue;}var radiusSize = 5;var content = "<strong>名称:</strong>"+dataInfo.name + "<br/><strong>级别:</strong>"+ dataInfo.level;content += "<br/><strong>所属行政区划:</strong>"+ dataInfo.province + "/" + dataInfo.city + "/" + dataInfo.area;content += "<br/><strong>评定时间:</strong>"+ dataInfo.evaluationTime ;var latlng = new L.latLng(geomObj.coordinates[1], geomObj.coordinates[0]);let marker = L.circleMarker(latlng, {radius: radiusSize,color: strokeStyleSet,labelStyle: {offsetX: 0, //横坐标偏移(像素)offsetY: 30, //纵坐标偏移(像素)text: dataInfo.name,rotation: 0,zIndex: radiusSize,minZoom : 5,strokeStyle: strokeStyleSet}}).addTo(showLayerGroup);marker.bindPopup(content); }mymap.addLayer(showLayerGroup);}},error:function(){$.modal.alertWarning("获取信息失败");}});
}

        不出意外的话还是出了意外,我们来计划在点击风景区时,通过bindPopup弹出经典的一些附属信息。但是无论点击哪个景点,似乎都没有符合我们的预期,效果如下:

         遇到点击了没有效果的情况,第一个想到的原因是看一下网页的控制台,看是否有什么报错信息。根据这个思路,我们打开浏览器的控制台看一下:

        很遗憾,控制台没有想要的信息,代码是没有问题的,可以正常运行的。那么问题到底出在哪里呢? 

2、问题排查

        在明确没有代码语法问题和报错的前提下,怎么解决这个问题呢?代码是严格按照我们的设计去执行的,但是为什么没有按照我们的预期效果来展示呢?当时,博主从下面两个方向进行思考。首先、排查相关组件的引用是否有问题,因为在本案例中,涉及到LayerGroup和FeatureGroup的使用,是否是因为涉及Group的增加和删除导致了标记对象的事件显示不征程。其次想到的是Leaflet.canvaslabel.js这个组件本身是不是有问题,在数量规模和事件绑定上是不是有什么不对的地方。受限于博主的经验,首先想到了这两个方面,因此排查工作也由此展开。

二、通过实验验证猜想

        针对问题我们有了两个方面的猜想,要想验证猜想的成立,就必须通过代码实验,通过实验说明问题可以解决,这样才是有说服力的。本节针对上述猜想,阐述两个实验环节。

1、排查LayerGroup和FeatureGroup

        怀疑LayerGroup和FeatureGroup有问题的根据是因为,在展示省份地理范围和风景区点位列表时,我们都会设计Group的操作,通过Group,可以实现图层级的隐藏和展示,可以实现某一专题的元素共同的可视化。但是在切换省份时,要想实现只展示当前省份和所属的风景区信息,首先要把之前的省份地理范围清空掉,同时也把之前省份的风景区列表清除掉。所以映入眼帘的第一个嫌疑人出现。添加元素和移除的方法如下:

function preview(code,id){showLayerGroup.clearLayers();//图层要素清除showProvince(id);showScenicSpot(code);
}

        要素添加到图层的代码如下:

let marker = L.circleMarker(/*省略*/).addTo(showLayerGroup);
mymap.addLayer(showLayerGroup);

        通过在Leaflet的官方网站中查看API文档,查看相关的示例代码,并没有发现什么问题。甚至在当时调试的情况下,已经不采用Group的方案,直接把marker添加到Map当中,问题依然没有解决,至此,排除是LayerGroup或者FeatureGroup引起的问题。

2、排查Leaflet.canvaslabel.js

        在把LayerGroup和FeatureGroup排除了嫌疑之后,把目光转向了Leaflet.canvaslabel.js。思考是不是因为Leaflet.canvaslabel.js这个组件自身有什么缺陷(在大规模数据量的时候),或者说我们在API的调用有没有什么问题。带着这个疑问,我们来做两个实验。第一个实验是针对性的看API,是否有什么方法或者属性配置没有正确配置。第二个实验是Leaflet.canvaslabel.js配合大规模数据点,比如5000个点的情况下,能否正常bindPopup。下面逐步来进行验证:

options: {defaultLabelStyle: {offsetX: 0, //横坐标偏移(像素)offsetY: 0, //纵坐标偏移(像素)scale: 1, //放大比例rotation: 0, //旋转角度(弧度),可能会导致碰撞检测不准确text: null, //标注文本内容minZoom: null, //最小显示级别maxZoom: null, //最大显示级别collisionFlg: true, //碰撞检测center: null, //标注位置,默认为null,会自动计算几何中心zIndex: 0, //排序defaultHeight: 20, //文本高度,无法自动计算,所以直接传参手动调整//文本样式,具体值请参考[canvas](https://www.runoob.com/tags/ref-canvas.html)font: "10px sans-serif",fillStyle: "rgba(0,0,0,1)",lineCap: "round",lineDash: [],lineDashOffset: 0,lineJoin: "round",strokeStyle: "rgba(0,0,0,1)",textAlign: "center",textBaseline: "middle",lineWidth: 1,},},

        这是组件的属性配置,基本上与标注的可视化没有关系。再来看事件和执行器:

/*** 执行侦听器*/_executeListeners: function (event) {if (!this._textBounds) return;var me = this;var ret = this.getTextByEvent(event);if (ret && ret.length > 0) {me._map._container.style.cursor = "pointer";if (event.type === "click") {me._onClickListeners.forEach(function (listener) {listener(event, ret);});}if (event.type === "mousemove") {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 = "";}},/*** 添加click侦听器*/addOnClickListener: function (listener) {this._onClickListeners.push(listener);},

        通过代码发现,组件也并有修改逻辑,只是接收点击事件,然后添加到执行器中。基本排除了组件自身的问题。那么剩下最后一个猜想,大规模数量下组件的事件是不是失效了。因此我们进行大规模数据量的加载试验。

        在之前的博文中在Leaflet中点对象使用SVG和Canvas两种模式的对比,对组件的加载能力有较详细的描述,在使用5000甚至10000个点的规模下,展示非常迅速,同时点击对象,也能实现Popup的快速提示。

var count = 30000;
for (let i = 0; i < count; i++) {let latlng = L.latLng(23.95 + Math.random() * 10, 112.40034 + Math.random() * 15);var content = "<strong>名称:</strong>城市"+i + "<br/><strong>级别:</strong>"+ i;content += "<br/><strong>所属行政区划:</strong>"+ i + "/" ;content += "<br/><strong>评定时间:</strong>"+ i ;var title = "重要城市" + Math.random();let c = L.circleMarker(latlng, {radius: 5,labelStyle: {text: title,rotation: 0,zIndex: 1,strokeStyle: "red",}}).addTo(showGroup);c.bindPopup(content);
}
map.addLayer(showGroup);

        经过两个方向,几个步骤的排查,依然没有找到具体的原因,也没有找到解决方案。有点懊恼,于是想着换个思路。对组件的多点展示能力有初步怀疑,但没有找到原因。

三、柳暗花明又一村

        山重水复疑无路,柳暗花明又一村。这句话用形容当时解决问题的情况非常贴切,在两个方向都明确没有定位最终问题的情况下。我决定用之前遇到的一个点聚类的思路。遇到问题不要放弃,再没有方向的时候,发散思路,解决了问题,经验值就此提升。因此,遇到bug,解决bug才是好的态度。这里分两个步骤描述问题的解决办法。

1、点聚类的办法

        带着之前没有找到的根本问题,一直将注意力集中在风景区的点数量上。怀疑是不是点多了,导致了事件响应的问题。所以考虑减少点位信息,在Leaflet当中,可以通过点聚类的办法介绍视野内的对象,通过减少点的渲染,保障Popup的正常可视化。

        大家还记得点聚类的组件用什么吗?之前也有文章进行深入讲解,就是用leaflet.markercluster-src。代码就不详细贴了,创建聚合图层,将点添加到图层中。

//创建聚合图层并设置最大聚合半径为 26像素
var markersGroup = L.markerClusterGroup({maxClusterRadius: 26});

        通过点聚合,发现确实能解决几个省的展示问题,当时以为找到了解决办法很欣喜,但是在浙江时,发现有风景区不能很好的展示出来。开始对数据的加载机制有个第一次怀疑,这算个歪打正着。本来没有怀疑数据的加载机制。

2、歪打正着

        在对数据的加载机制有了怀疑之后,便迅速去看代码。在展示省份地理范围的代码中,我们使用ajax的方式去后台请求数据,ajax请求头如下:

type:"get",  
url:prefix + "/geojson/" + id,  
data:{},  
dataType:"json"

        众所周知,在ajax当中,其默认的数据加载方式异步的,也就是async=true。其实到了这里,才是真正的问题根源。就是由于数据请求的异步方式导致了组件的事件响应问题。再来看一下代码:

 function preview(code,id){showLayerGroup.clearLayers();//图层要素清除showProvince(id);showScenicSpot(code);}

        在上述代码中,首先异步加载省份的geojson地理范围,再异步加载风景区信息。也就是异步的问题导致的,虽然方法看起来像是先加载地理范围,实际上设置了异步,可能地理范围还没有加载完,风景区信息已经再加载了。也正是这个原因导致了事件不响应。按照这个思路,我们将ajax方法设置为同步的方式,即async=false

        再来看具体的效果,以浙江为例:

杭州西湖风景区

杭州市千岛湖风景区

浙江普陀山风景区

        至此,问题得以全部解决,Leaflet.canvaslabel在Ajax异步请求时bindPopup无效的解决办法。导致popup无效的问题是ajax的异步机制导致的。当然在实际开发当中,您可以使用一个解决返回所有的数据,把空间范围和数据列表同时返回就不存在上述问题了。解决办法很多,找到问题根本很重要。遇到问题,慢慢分析,大胆设想,小心验证,一定会有收获,不放弃。

总结

        以上就是本文的主要内容,本文分享了作者在排查GIS问题当中的一些思路和方法。在没有找到具体的问题前,首先检查代码的问题,然后检查相应的组件是否有什么缺陷,最后再梳理解决办法,大致的思路如此。本文在此背景下诞生,博文首先详细介绍了风景区展示时具体的问题,然后分析排查思路和方向,接着使用不同的方案验证思路,最后给出最终的解决方案。行文仓促,定有不足之处,案例的排查方法和经验都不足,欢迎有经验的大佬基于批评指正,万分感谢。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免疫优化算法(Immune Optimization Algorithm)

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 算法背景 免疫算法是一种模拟生物免疫系统的智能优化算法。想象一下&#xff0c;当我们的身体遇到病毒或细菌侵袭时&#xff0c;免疫系统会启动…

DHCP原理

什么是DHCP DHCP (Dynamic Host Configuration Protocol,动态主机配置协议&#xff09;是由Internet工作任务小组设计开发的&#xff0c;专门用于为TCP/IP网络中的计算机自动分配TCP/IP参数的协议&#xff0c;是一个应用层协议&#xff0c;使用UDP的67和68端口。 DHCP的前身是B…

Windows下安装 Emscripten 详细过程

背景 最近研究AV1编码标准的aom编码器&#xff0c;编译的过程中发现需要依赖EMSDK&#xff0c;看解释EMSDK就是Emscripten 的相应SDK&#xff0c;所以此博客记录下EMSDK的安装过程&#xff1b;因为之前完全没接触过Emscripten 。 Emscripten Emscripten 是一个用于将 C 和 …

[hpssupfast@mailfence.com].Elbie勒索病毒如何恢复数据和预防

[hpssupfastmailfence.com].Elbie是一种新型勒索病毒,快速恢复重要数据请添加技术服务号(safe130)。以下是关于elbie勒索病毒的详细信息&#xff1a; 病毒介绍&#xff1a; elbie勒索病毒&#xff0c;也称为PHOBOS勒索软件&#xff0c;它通过加密文件并要求支付赎金以恢复对文…

涉案财物管理系统|DW-S405系统全国都有案例

涉案财物管理系统&#xff08;智财物&#xff1a;DW-S404&#xff09;是一套成熟系统&#xff0c;依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对涉密载体进行统一管理、分析的信息化、智能化、规范化的系统。 涉案财物管理系统主要实现对涉案物品进…

RisingWave基本操作

什么是RisingWave RisingWave 是一款基于 Apache 2.0 协议开源的分布式流数据库。RisingWave 让用户使用操作传统数据库的方式来处理流数据。通过创建实时物化视图&#xff0c;RisingWave 可以让用户轻松编写流计算逻辑&#xff0c;并通过访问物化视图来对流计算结果进行及时、…

分享一个处理大文件效率拉满的神器

&#x1f3c3;‍♂️ 微信公众号: 朕在debugger© 版权: 本文由【朕在debugger】原创、需要转载请联系博主&#x1f4d5; 如果文章对您有所帮助&#xff0c;欢迎关注、点赞、转发和订阅专栏&#xff01; 前言 系统当天有些表的数据需要恢复成前一天的样子&#xff0c;幸好有…

Rpcx (一):详解【介绍、基础示例 demo】

一.rpcx介绍 1.1 rpc是什么 远程过程调用的通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。简单地说就是能使应用像调用本地…

【动态规划】子序列问题I|最长递增子序列|摆动序列|最长递增子序列的个数|最长数对链

一、最长递增子序列 300. 最长递增子序列 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.注意子序列和子数组的区别&#xff1a; (1)子序列&#xff1a;要求顺序是固定的&#xff08;要求没那么高&#xff0c;所以子序列就多一些&#xff09; (2)子数组&#xff1a;要…

2024年深圳市教师招聘报名流程(建议电脑)

2024年深圳市教师招聘报名流程&#xff08;建议电脑&#xff09; #深圳教师招聘 #深圳教招 #深圳教师招聘考试 #教师招聘报名照片处理 #深圳教师招聘笔试

(Java)心得:LeetCode——19.删除链表的倒数第 N 个节点

一、原题 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&…

1-02-02:虚拟化与容器化Docker环境搭建

1.02.02 虚拟化与容器化Docker环境搭建 一. 虚拟化与容器化技术简介1. 虚拟机环境2. docker环境 二. Docker 架构与隔离机制2.1 Docker 架构2.2 Docker 隔离机制2.3 资源限制2.4 Docker应用场景 三. 实战:Docker在Centos7安装与镜像加速 ❤❤❤3.1 docker安装3.2 设置镜像加速 …

17 SPI FLASH读写

SPI 协议简介 SPI 即 Serial Periphera linterface 的缩写&#xff0c;顾名思义就是串行外围设备接口&#xff0c;主要用于与FLASH、实时时钟、AD 转换器等外设模块的通信&#xff0c;它是一种高速的全双工同步的通信总线。 SPI 设备分为主设备和从设备&#xff0c;SPI 通信必…

crossover24中文破解版百度云免费下载 crossover永久免激活汉化包安装使用教程 crossover24激活码分享

原则上&#xff0c;我们不提倡各位使用破解版&#xff0c;这是处于对知识产权的保护&#xff0c;也是为了各位的长远利益。使用正版你可以获得更优质的服务和完善的产品功能。 但仍然有部分用户由于预算、使用习惯等原因&#xff0c;需要破解版。所以本文不讲原则&#xff0c;…

有没有适合女生或者宝妈下班后可以做的副业?

宝妈与上班族女生的新篇章&#xff1a;水牛社副业兼职之旅 在繁忙的职场和温馨的家庭之间&#xff0c;不少女性渴望找到一种既能兼顾家庭又能实现自我价值的兼职方式。对于上班族女生和宝妈们来说&#xff0c;水牛社这样的线上任务平台为她们提供了一个全新的选择。 上班族女…

【备战软考(嵌入式系统设计师)】12 - 嵌入式系统总线接口

我们嵌入式系统的总线接口可以分为两类&#xff0c;一类是并行接口&#xff0c;另一类是串行接口。 并行通信就是用多个数据线&#xff0c;每条数据线表示一个位来进行传输数据&#xff0c;串行接口就是一根数据线可以来一位一位地传递数据。 从上图也可以看出&#xff0c;并行…

SR3-05S电机保护器 施耐德 EOCR-SR3

EOCR-SR3电机保护器 施耐德 EOCR主要产品有电子式电动机保护继电器&#xff0c;电子式过电流继电器&#xff0c;电子式欠电流继电器&#xff0c;电子式欠电压继电器&#xff0c;其它保护和监视装置&#xff0c;电流互感器。 系列型号&#xff1a; EOCR-SR3-05S EOCR-SR3-30…

VR全景技术在养老院的应用优势浅析

随着时代的快速发展&#xff0c;人口老龄化越来越严重&#xff0c;如何利用VR技术提升养老服务的质量&#xff0c;成为了社会各界关注的焦点。为养老院拍摄制作VR全景&#xff0c;不仅能够为养老院的老人子女们跨越空间限制&#xff0c;实现与家人的情感连接&#xff0c;还可以…

Vditor集成于VUE笔记

文章目录 前言一、安装Vditor二、渲染markdown三、options3.1 自建CDN3.2 outline大纲不显示、不跳转问题3.3 upload 图片/视频上传3.4 toolbar提示位置点击事件more中文字 3.5 sv分屏渲染模式隐藏编辑框3.6 after中的insertValue或者setValue 前言 Vditor是一款易于使用的 Ma…

带你手撕红黑树! c++实现 带源码

目录 一、概念 二、特性 三、接口实现 1、插入 情况一&#xff1a;p为黑&#xff0c;结束 情况二&#xff1a;p为红 1&#xff09;叔叔存在且为红色 2&#xff09;u不存在/u存在且为黑色 &#xff08;1&#xff09;p在左&#xff0c;u在右 &#xff08;2&#xff09;…