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.rhkb.cn/news/328467.html

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

相关文章

【多模态】31、Qwen-VL | 一个开源的全能的视觉-语言多模态大模型

文章目录 一、背景二、方法2.1 模型架构2.2 输入和输出2.3 训练 三、效果3.1 Image Caption 和 General Visual Question Answering3.2 Text-oriented Visual Question Answering3.3 Refer Expression Comprehension3.4 视觉-语言任务的少样本学习3.5 真实世界用户行为中的指令…

BGP(border gateway protocol)边界网关协议初识篇

BGP它是一种路径矢量协议&#xff0c;用于决定数据包在互联网中的最佳路径。 1、工作原理&#xff1a; 自治系统&#xff08;AS&#xff09;间路由: BGP主要用于连接不同自治系统之间的路由器&#xff0c;其中每个自治系统&#xff08;AS&#xff09;代表一组具有共同路由的网…

Rust构造JSON和解析JSON

目录 一、Rust构造JSON和解析JSON 二、知识点 serde_json JSON 一、Rust构造JSON和解析JSON 添加依赖项 cargo add serde-json 代码&#xff1a; use serde_json::{Result, Value};fn main() -> Result<()>{//构造json结构 cpu_loadlet data r#"{"…

【C -> Cpp】由C迈向Cpp (6):静态、友元和内部类

标题&#xff1a;【C -&#xff1e; Cpp】由C迈向Cpp &#xff08;6&#xff09;&#xff1a;静态、友元和内部类 水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 &#xff08;一&#xff09;静态成员 &#xff08;二&#xff09;友元 &#xff08;三&#xff09…

想让普通金额数字显示为逗号分隔的数字?

使用vueelement 后台传的数据 1.编写方法 放在method当中 /** 数字转换显示格式 */priceFormat (num, n) {n n || 2;let symbol ",";if (num null) return num;if (typeof num ! number) throw new TypeError(num参数应该是一个number类型);if (n < 0) thro…

eNSP中小型园区网络拓扑搭建(上)

→b站直通车&#xff0c;感谢大佬← →eNSP中小型园区网络拓扑搭建&#xff08;下&#xff09;← 不带配置命令的拓扑图已上传~ 项目背景&#xff1a; 某公司准备新建一张网络供企业办公使用。写字楼共3层&#xff0c;一层会客大厅、二层行政部及市场部、三层研发部。一层设…

基于java的超级玛丽游戏的设计与实现(论文 + 源码)

Java的超级玛丽游戏.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89313347 基于java的超级玛丽游戏的设计与实现 摘要 近年来&#xff0c;Java作为一种新的编程语言&#xff0c;以其简单性、可移植性和平台无关性等优点&#xff0c;得到了广泛地应用。J2SE称…

容联云零代码平台容犀desk:重新定义坐席工作台

在数智化浪潮的推动下&#xff0c;企业亟待灵活适应市场变化、快速响应客户需求&#xff0c;同时还要控制成本并提升效率&#xff0c;传统的软件开发模式因开发周期长、成本高、更新迭代慢等问题&#xff0c;逐渐难以满足企业灵活多变的业务需求。 容犀Desk&#xff0c;观察到…

Linux网络编程——HTTP协议的理解与运用

目录 前言 一、认识URL 二、认识HTTP样例 三、HTTP的报头内容 1.url 2. Content-Type 3.Method 方法 1.GET方法 2.POST方法 4、状态码 5.cookie和session 前言 我们知道&#xff0c;协议就是一种约定&#xff0c;客户端与服务端统一的用这种约定进行传输数据。我们…

如何快速优雅的免费申请和搭建属于自己的服务器

今天来讲一下如何快速优雅的搭建属于自己的服务器&#xff0c;我们以阿里云的云服务器为例&#xff0c;新用户一般是有三个月使用期限。 首先我们进入官网&#xff0c;选择云服务器ecs 链接直达&#xff1a;https://cn.aliyun.com 打开网页后&#xff0c;往下滑&#xff0c;然…

「AIGC」Python实现tokens算法

本文主要介绍通过python实现tokens统计,避免重复调用openai等官方api,开源节流。 一、设计思路 初始化tokenizer使用tokenizer将文本转换为tokens计算token的数量二、业务场景 2.1 首次加载依赖 2.2 执行业务逻辑 三、核心代码 from transformers import AutoTokenizer imp…

ssrf初步

一&#xff0c;简介 全称&#xff1a;Server-Side Request Forgery&#xff08;中文&#xff1a;服务器端请求伪造&#xff09; 攻击者从服务端发起请求&#xff0c;让服务器连接任意外部系统&#xff0c;从而泄露敏感数据。主要利用各种协议的请求伪造&#xff0c;例如php协…

战网国际服怎么下载 暴雪战网一键下载安装图文教程

战网国际版&#xff0c;或称为Battle.net全球版&#xff0c;是暴雪娱乐构建的一项跨越国界的综合游戏交流平台&#xff0c;它无视地理限制&#xff0c;旨在服务全球每一个角落的游戏爱好者。不同于地区专属版本&#xff0c;国际版为玩家开启了一扇无门槛的大门&#xff0c;让每…

char x[]---char*---string---sizeof

字符串数组 #include <iostream>int main(){char c_str[]"abcd";char c_str1[]{a,b,c,d};std::cout<<sizeof(c_str)<<std::endl;std::cout<<sizeof(c_str1)<<std::endl;return 0; } char*存储的字符串个数 char*字符串所占字节大小 c…

Hadoop3:HDFS副本节点选择逻辑讲解

一、副本节点选择&#xff08;机架感知&#xff09; 说明 第一个副本&#xff0c;因为我们的client可能是web页&#xff0c;也可能是shell终端。 如果是web页&#xff0c;则随机选取一个节点&#xff0c;如果是shell终端&#xff0c;则选择当前shell终端所在的节点。 节点距离最…

【计算机毕业设计】springboot分类信息服务平台移动端的设计与实现

分类信息 服务平台设计的目的是为用户提供活动信息、活动记录等方面的平台。 与PC端应用程序相比&#xff0c;分类信息服务平台的设计主要面向于移动端&#xff0c;旨在为管理员和用户、商铺提供一个分类信息服务平台。用户可以通过Android及时查看活动信息等。 分类信息服务平…

Linux守护进程

进程组和会话在 UNIX 系统中是非常重要的概念&#xff0c;特别是在进行作业控制和终端会话管理时。下面是关于进程组和会话的详细解释&#xff1a; 进程组&#xff08;Process Group&#xff09; 定义与作用&#xff1a; 进程组是一个或多个进程的集合&#xff0c;这些进程通常…

Android系统不同版本存储权限

一、Android存储简介 Android系统分为内部存储和外部存储 从Android6.0开始不断在更新存储&#xff08;读写&#xff09;权限&#xff0c;除了在AndroidManifest.xml文件里声明&#xff0c;app运行时也要动态申请使用对应的权限 提醒&#xff1a;应用私有存储不需要动态申请权…

一种基于电场连续性的高压MOSFET紧凑模型,用于精确表征电容特性

来源&#xff1a;A Compact Model of High-Voltage MOSFET Based on Electric Field Continuity for Accurate Characterization of Capacitance&#xff08;TED 24年&#xff09; 摘要 本文提出了一种新的高压MOSFET&#xff08;HV MOS&#xff09;紧凑模型&#xff0c;以消…

【C++】:string类的基本使用

目录 引言一&#xff0c;string类对象的常见构造二&#xff0c;string类对象的容量操作三&#xff0c;string类对象的访问及遍历操作四&#xff0c;string类对象的修改操作五&#xff0c;string类非成员函数六&#xff0c;整形与字符串的转换 引言 string 就是我们常说的"…