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);
		        //同时设置中心位置和级别,一般省份设置为7
		        mymap.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问题当中的一些思路和方法。在没有找到具体的问题前,首先检查代码的问题,然后检查相应的组件是否有什么缺陷,最后再梳理解决办法,大致的思路如此。本文在此背景下诞生,博文首先详细介绍了风景区展示时具体的问题,然后分析排查思路和方向,接着使用不同的方案验证思路,最后给出最终的解决方案。行文仓促,定有不足之处,案例的排查方法和经验都不足,欢迎有经验的大佬基于批评指正,万分感谢。

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

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

相关文章

扑克游戏程序代码,使用QT,C++ (price 600 不包含文档)

wx:help-assignment code price: 600 &#xff08;不包含文档&#xff01;不包含文档&#xff01;不包含文档&#xff01;&#xff09; 扑克游戏程序代码&#xff0c;使用QT&#xff0c;C 1.作业的目的是 在这个作业中&#xff0c;你将得到一组要求&#xff0c;使用本模块中涵…

java项目之英语知识应用网站源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的英语知识应用网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 英语知识应用网站的主要…

kafka安装及收发消息

kafka需要与zookeeper配合使用&#xff0c;但是从2.8版本kafka引入kraft&#xff0c;也就是说在2.8后&#xff0c;zookeeper和kraft都可以管理kafka集群&#xff0c;这里我们依然采用zookeeper来配合kafka。 1、首先我们下载zookeeper 下载地址为 https://zookeeper.apache.org…

macOS12安装 php8.1和apache

1. 安装php 8.1 macOS12不再自带php brew tap shivammathur/php 查看可安装版本 brew search php 安装指定版本 brew install php8.1 环境配置 vim ~/.zshrc export PATH"/usr/local/opt/php8.1/bin:$PATH" export PATH"/usr/local/opt/php8.1/sbin:$PAT…

《工具分享-整合功能网页》标星5.3k⭐开发人员的在线工具集:it-tools

IT Tools - 为方便开发人员提供的在线工具 部署自己的it-tools: 有两个版本&#xff0c;目前有中文支持。 直接部署使用docker指令获取出来的是英文的&#xff1a; 英文版&#xff1a; docker run -d --name it-tools --restart unless-stopped -p 8080:80 corentinth/it-…

第81天: 代码审计-PHP 项目MVC 注入CNVD 拿 1daySQL监控动态调试

目录 前置知识 正则表达式挖掘思路(通用漏洞思路) 功能点挖掘思路(通用漏洞思路) 案例一&#xff1a;数据库监控-QQ 业务源码系统-(无过滤) 案例二&#xff1a;正则表达式-Bluecms 源码系统-(无过滤) 案例三&#xff1a; CNVD 拿 1DAY-梦想 CMS 源码系统-(有过滤) 梦想 …

二、使用插件一键安装HybridCLR

预告 本专栏将介绍如何使用这个支持热更的AR开发插件&#xff0c;快速地开发AR应用。 专栏&#xff1a; Unity开发AR系列 插件简介 通过热更技术实现动态地加载AR场景&#xff0c;简化了AR开发流程&#xff0c;让用户可更多地关注Unity场景内容的制作。 热更方案 基于Hybri…

STM32系统架构

以下是STM32系统架构中的各个重要组件和功能&#xff1a; 组件描述Cortex-M内核ARM Cortex-M系列内核&#xff0c;如M0、M0、M3、M4、M7等Flash存储器存储程序代码和数据SRAM存储程序运行时数据和堆栈外设接口GPIO、SPI、I2C、UART、TIM、ADC、DAC、USB、CAN、Ethernet等时钟和…

Android动画(四):PathMeasure实现路径动画

文章概览 1 PathMeasure概述2 实现路径加载动画3 实现箭头加载动画4 实现操作成功动画 本系列将介绍以下内容&#xff1a; Android动画 1 PathMeasure概述 PathMeasure是一个单独的类&#xff0c;其全部源码如下&#xff08;请详细研读注释&#xff09;&#xff1a; package…

吃透前端文件上传与文件相关操作

最近在学文件上传的操作,所以想把学习到东西写成一文章 这片文章是我以小白视角 慢慢学习并熟悉前端文件相关操作的流程总结出来的 前端文件上传 我首先想到是 <input type"file">选择文件</input>如果我们想限制上传文件的格式,大小或进行裁剪分片上传…

ssm121开放式教学评价管理系统+vue

开放式教学评价管理系统vue 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对开放式教学评价管理系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了开放式开放…

C++类与对象的一些练习

1.设计一个名为Rectangle的矩形类&#xff0c;其属性为矩形的长和宽&#xff0c;能计算和输出矩形的周长和面积。 class Rectangle { public:Rectangle(int c0,int k0):m_c(c),m_k(k){}int length()//周长{return 2 * (m_c m_k);}int area()//面积{return m_c * m_k;} privat…

SM131,SM130机笼和利时

SM131,SM130机笼和利时。设计系统 控制过程组态&#xff1a;针对过程控制站进行的控制逻辑和策略的组态 人机界面设计&#xff1a;SM131,SM130机笼和利时。针对操作员站进行的数据库组态模件外观、模件接线&#xff09;算法符号算法表示符号、SM131,SM130机笼和利时。输入输出端…

南京信工一班IP(2)

第六章&#xff0c;BGP—边界网关协议 自治系统—AS ​ 定义&#xff1a;由一个单一的机构或组织所管理的一系列IP网络及其设备所构成的集合。 ​ AS的来源&#xff1a; 整个网络规模过大&#xff0c;会导致路由信息收敛速度过慢&#xff0c;设备对相同目标认知不同。AS之间…

基于openEuler22.03 LTS环境的容器项目实训——分布式微服务项目部署

一、说明 本文配置环境为VMware虚拟机&#xff08;2核CPU&#xff0c;4 GB内存&#xff0c;40GB磁盘&#xff09;&#xff0c;OS为openEuler 22.03 LTS &#xff0c;虚拟机要求能联网。 二、安装docker环境 2.1 安装docker相关软件包 [rootnode01 ~]# dnf -y install docker…

python如何单步调试

Python怎么单步调试&#xff1f;下面给大家介绍一下单步调试&#xff1a; 方法一&#xff1a;执行 python -m pdb myscript.py (Pdb) 会自己主动停在第一行。等待调试&#xff0c;这时你能够看看帮助。 方法二&#xff1a;在所调试程序的开头中&#xff1a;import pdb 并在你…

R语言数据分析案例-股票可视化分析

一、数据整合的对象 # Loading necessary libraries library(readxl) library(dplyr)# Reading the data from Excel files data_1 <- read_excel("云南白药.xlsx") data_2 <- read_excel("冰山.xlsx")二、数据整合的代码 # Reading the data from…

Android的NDK开发中Cmake报缺少对应的x86的so文件

需要实现一个串口操作的命令。 供应商提供了2个so文件。 分别是 armeabi-v7a 和 arm64-v8a 添加到对应的cpp下。 在CMakeLists.txt里添加so文件 # 添加预编译的库 add_library(libxxx SHARED IMPORTED)# 设置库的路径 set_target_properties(libxxx PROPERTIES IMPORTED_…

重写muduo之TcpConnection

目录 1、 TcpConnection.h 2、 TcpConnection.cc 1、 TcpConnection.h TcpConnection底层绑定&#xff08;管理&#xff09;了一个Channel&#xff0c;Channel有事件被Poller通知后&#xff0c;会调用相应的回调&#xff0c;这些回调也是TcpConnection中包含的方法&#xff0c…

牛客Java面试题【面试】

牛客Java面试题【面试】 前言推荐牛客Java面试题【面试】第2章 Java笔面试高频考点&解题技巧1. Java基础[2.1 一、java-基础-1](https://www.nowcoder.com/study/live/689/2/1)1.1 为什么Java代码可以实现一次编写、到处运行&#xff1f;1.2 一个Java文件里可以有多个类吗&…