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,一经查实,立即删除!

相关文章

Flutter 中的 Card 小部件:全面指南

Flutter 中的 Card 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;Card 是一个用于呈现内容的容器&#xff0c;它带有圆角边缘和阴影效果&#xff0c;常用于展示信息块&#xff0c;如用户头像、相册、笔记或任何需要突出显示的内容。Card 小部件提供了一种简单而直观的…

unity---常用API

1. Vector3&#xff1a;结构体由x、y、z这3个数值组成&#xff0c;表示一个向量 magnitude变量返回该向量的长度normalized变量返回 magnitude 为 1 时的该向量zero静态变量Vector3(0, 0, 0)one静态变量Vector3(1, 1, 1)forward静态变量Vector3(0, 0, 1)back静态变量Vector3(0…

Flutter 中的 Stack 小部件:全面指南

Flutter 中的 Stack 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;Stack 是一个用于叠加多个小部件的布局小部件。它允许你将多个小部件重叠放置&#xff0c;通过控制每个小部件的位置和大小&#xff0c;你可以创建出复杂的布局效果&#xff0c;如徽章图标、对话框、…

pandas DataFrame 常用遍历方法

在Pandas中&#xff0c;可以使用多种方法遍历DataFrame中的数据。以下是几种常见的方法&#xff1a; 基于索引遍历DataFrame的每一行。 基于行号遍历DataFrame的每一行, 该方式通过行号获取行数据信息&#xff0c;格式为Series&#xff0c;无法获取改行的index信息。 使用iterr…

1.8. 离散时间鞅-无界停时定理与随机游走

无界停时定理与随机游走 无界停时定理与随机游走1. 无界停时定理1.1. 一致可积1.2. 非一致可积2. 应用于随机游动-鞅方法2.1. 随机游走构造的鞅2.2. 对称简单随机游走无界停时定理与随机游走 1. 无界停时定理 本节给出一致可积下鞅的无界停时定理,说明一致可积下鞅的停止过程…

Agent AI智能体:未来社会的角色、发展与挑战

Agent AI智能体在未来社会中的角色、发展路径以及可能带来的挑战是一个非常值得关注的话题。让我们来深入探讨一下这些方面。 1. 角色与应用场景 Agent AI智能体是指具有自主决策能力和执行能力的人工智能系统&#xff0c;它们可以代表个人或组织执行各种任务和活动。在未来社…

大学课程中的算法java实现【学习算法】

大学课程中的算法java实现【学习算法】 前言前言推荐离散数学Warshall算法 P102可达性矩阵 P187欧拉图 Fleury算法 P201最小生成树 P234哈夫曼数 P240 数据结构与算法计算机操作系统最后 前言 这是陈旧已久的草稿2023-04-22 23:20:38 这是准备自己用java实现大学课程中所有遇…

免疫优化算法(Immune Optimization Algorithm)

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

mysql 事物

MySQL中的事务&#xff08;Transaction&#xff09;是一个确保数据完整性和一致性的重要概念。它将一组SQL操作捆绑在一起&#xff0c;当作一个单一的工作单元来执行。事务具备以下四个关键特性&#xff0c;即ACID特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&am…

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 设置镜像加速 …