一种解决Leaflet中Divicon城市气温标注空间重叠的办法

news2024/10/27 22:52:06

目录

前言 

一、一些解决办法

1、marker的聚类

2、使用leaflet-canvas-label

3、使用Zoom和样式控制

二、基于rbush和Leaflet.LayerGroup.Collision的解决办法

1、关于rbush

2、Leaflet.LayerGroup.Collision.js

三、解决标签重叠的具体实现

1、添加元数据

2、添加到collisionLayer中

3、实现效果

 四、总结


前言 

        又是一年的1024,各位程序员朋友们又迎来了一年一度的程序员节。此刻的你又在做什么呢?是在工位上奋力敲击,用键盘创造作品亦或者处理bug呢。下午刚跟产品达成的一致,希望明天不要再变,希望验收要求不要太变态,哎。万一要变,除了响应又有什么办法呢。或者在进行测试,性能测试也好,功能测试也好。跟开发的同学关系还好吗?是不是又在撕扯一些问题究竟要不要改或者优先级要不要定这么高。是否在专注的进行产品设计,将用户的需求转换成产品文档。这又是第几稿了呢?还记得上次远程会议上,用户信誓旦旦的保证,坚决不会再加需求了。望着邮箱中又发过来的紧急邮件,朋友你还好吗?是否在机房紧张的排查问题呢?系统莫名的停服了半个小时,头顶为数不多的细发也在瑟瑟发抖,今晚或许又是一个通宵。哎,作为运维,不就是扮演救火队去专门灭火嘛?先抽根烟,稳稳神,慢慢排查吧。不是增长了经验就是增长了怒火。哎,1024。年年岁岁都有1024,一样的工作,一样的状态。有同学说,已经开始了滴滴和外卖半年了。体力在慢慢转好。调侃也罢,事实也罢。真心的祝所有的程序员朋友们开心快乐,祝大家生活、工作愉快。

        以上皆是赘言,算个起题。记得在之前的一篇博客中曾讲解如何在Leaflet中进行DivIcon的标注开发。虽然成功的实现了需求,但是不知道各位朋友们有没有实际运行过。在地图上进行点标注之后,如果进行缩放,很难免就会有标注的重叠,掌握空间可视化的朋友应该都知道。随着地图的不妨缩小,比例尺也在不断的变化。而我们的DivIcon由于是已经创建出来的html页面元素,正常情况它是不会变化的。因此在进行空间可视化时就会出现以下的情况。各个标签就完全重叠到了一起。密密麻麻的,特别不好看。以我国东南部一些城市的天气情况综合WebGIS可视化效果,来看下面的效果图:

        可以看到,这些网页的标注都重叠到了一起。虽然我们可以放大地图,以此来让这些标签分开。这样子的效果就比较清爽,关键的信息没有被遮挡。城市的气温信息很友好,看起来很直观。效果如下所示: 

        那么有没有一种合理的方式,就像之前我们分享过的标签自动避让的功能,基于Canvas的标注标签自动避让呢,在空间中碰撞之后就会有标签隐藏起来。但是很遗憾,在Leaflet官方的API中,是没有这种功能的,除了针对Marker有点聚合的功能,针对divIcon的聚合貌似没有。本文即重点介绍如何在Leaflet中进行DidIcon信息标注时,应对空间对象重叠的问题,如何去解决。文章首先介绍一些可能的解决办法,然后介绍如何基于rbush和Leaflet.LayerGroup.Collision.js这两个组件来实现,最后介绍具体的实现方法即实现成果。如果您对如何解决DivIcon的空间重叠问题有兴趣,不妨来这里看看。

一、一些解决办法

         在空间中进行大量的信息标注是一种很常见的需求,由空间的相邻特性所决定的其位置的相邻。由此带来的一些空间重叠的问题。也是很常见的一种现象。这里围绕重叠标注的问题,来寻找一些可行的解决方案。

1、marker的聚类

        对marker进行聚类是一种常见的操作。markercluster是将这些点先聚合成一起,然后伴随着地图的方法按照位置来进行展示。如下图所示:

        这其实也是一种解决办法,即将这些空间标注使用聚类的方式来实现,对于聚集在一起的位置信息,就组合在一起,都不进行展示, 但是这样聚类的实现效果带来的就是信息都不展示。只有地图放大后才会进行展示。

2、使用leaflet-canvas-label

        如果想把点信息都展示出来,但是对于中文标注又可以实现一个避让的另一种方法就是使用leaflet-canvas-label。在前面的博客中也介绍过这些知识,实现的效果如下:

        使用leaflet-canvas-label的标签避让实现方式,虽然可以把信息都展示出来,但是其内部是基于Canvas来实现的,因此想实现自定义的div,如divicon的实现方式就不行,因为在canvas中,它的实现原理是在绘制标签时进行宽度判断。在其源码中可以看到如下代码:

//筛选需要碰撞检测的标签图层并安装zIndex排序
let collisionLayers = labelLayers.filter((layer) => {
    var collisionFlg =
        layer.options.labelStyle.collisionFlg != undefined
              ? layer.options.labelStyle.collisionFlg
              : this.options.defaultLabelStyle.collisionFlg;
     return collisionFlg == true;
 });


let textBounds = { minX, minY, maxX, maxY, layer };
if (
   !(
      labelStyle.collisionFlg == true &&
      this._textBounds.collides(textBounds)
   )
) {
   //绘制标注
   ctx.strokeText(labelStyle.text, 0, 0);
   ctx.fillText(labelStyle.text, 0, 0);
   this._textBounds.insert(textBounds);
}

        使用leaflet.canvaslabel来实现标注避让虽然可以实现需求,但是很明显的是。不支持我们的divicon的标绘方式,因此这个方案暂且搁置。

3、使用Zoom和样式控制

        在web页面中,我们可以对样式进行控制,比如我们可以控制在不同的zoom中,我们的标签不显示出来,在zoom=10级的情况下,将标签的display设置为true,将标签展示出来。而在zoom< 10的情况下,调用样式的display为false,这样就可以实现divicon标注的显示与隐藏。同样,缺点也是非常明显的,不能精确的控制,而且不好预估zoom,对于后期的扩展和修复是比较麻烦的。因此我们需要一种更加灵活的方式。既能实现divicon的动态展示,也能实现重叠对象的问题解决。

二、基于rbush和Leaflet.LayerGroup.Collision的解决办法

        既然上面的方案或多或少都有一些缺陷,那么如何来实现这种需求呢?这里分享一种方法。即使用rbush和Leaflet.LayerGroup.Collision.js来进行支持。本节就对这两个外部的组件来进行说明。

1、关于rbush

        RBush 是一个高性能的 JavaScript 库,用于点和矩形的 2D 空间索引。它基于具有批量插入支持的优化 R-tree 数据结构。空间索引是点和矩形的特殊数据结构,它允许你非常有效地执行诸如 “此边界框内的所有项目” 之类的查询(例如,比遍历所有项目快数百倍)。它最常用于地图和数据可视化。RBush,作为一款专门为JavaScript设计的空间索引库,以其卓越的性能和灵活性,在众多开发者中赢得了广泛的好评。它不仅能够高效地处理二维空间中的点和矩形数据,更因其采用了优化的R树数据结构而具备了处理大规模数据集的能力。RBush的一个显著优势在于它的批量插入功能,这使得在处理大量数据时,无需逐个插入即可实现高效的数据索引建立,极大地提高了数据处理的速度与效率。对于那些需要频繁进行数据查询、插入或删除操作的应用场景来说,RBush无疑是一个理想的选择。

        在这里采用rbush,是为了在前端构建一个2d的Rtree来进行空间数据的查询。关于rbush的要求大家可以在官网网站上查询。rbush分享镜像。

        这里注意一下,在进行空间重叠数据的分析时,rbush的版本不能使用太高。需要的朋友可以在博客下面留言,看到会发送可用的版本。 关于rbush的核心方法,这里不细细展开,后面可以找时间来进行深入讲解。

2、Leaflet.LayerGroup.Collision.js

        Leaflet.LayerGroup.Collision.js组件与marker的聚类组件差不多,主要作用就是生成前端聚类。Leaflet.LayerGroup.Collision.js的官方地址如下Leaflet.LayerGroup.Collision:

        在src中,包含了具体的组件,感兴趣的可以自己打开源码进行学习。 到这里,对使用的rbush和Leaflet.LayerGroup.Collision进行了简单的介绍,下面将结合实际的例子来进行说明如何进行代码的开发。

三、解决标签重叠的具体实现

        在对涉及的技术进行了简单介绍后,下面来对如何使用rbush和Leaflet.LayerGroup.Collision来进行实例开发,以解决标签重叠的问题。实现标签的碰撞检测,满足用户的需求。

1、添加元数据

        介绍完我们的用户场景后,我们来准备用户的基础元数据。这里以城市天气气温信息的展示。首先包括城市的国籍、城市具体信息、时间和温度。当然,这些数据,您可以从自己的后台接口或者从互联网公开的接口来获取。这里我们仅演示如何进行标绘,对如何获取接口不进行赘述。如果接口中的字段有所修改,请在代码实践过程中进行合理调整相关字段。下面给大家分享具体的元数据,代码如下:

var dataJson = [
		{lat:24.251973,lon:123.873596,c_name:"日本",p_name:"丰原",date:"2024-10-18 22:30:15",temp:"23"},
		{lat:24.497146,lon:117.91626,c_name:"中国",p_name:"福建省漳州市",date:"2024-10-19 22:30:15",temp:"27"},
		{lat:24.116675,lon:120.695801,c_name:"中国",p_name:"台湾省台中市",date:"2024-10-16 22:30:15",temp:"21"},
		{lat:22.634293,lon:120.300293,c_name:"中国",p_name:"台湾省高雄市",date:"2024-10-15 22:30:15",temp:"19"},
		{lat:26.046913,lon:119.245605,c_name:"中国",p_name:"福建省福州市",date:"2024-10-13 22:30:15",temp:"23"},
		{lat:23.58979,lon:120.880508,c_name:"中国",p_name:"台湾省同富村",date:"2024-10-12 22:30:15",temp:"24.5"},
		{lat:22.745789,lon:115.378418,c_name:"中国",p_name:"广东省汕尾市",date:"2024-10-11 22:30:15",temp:"23"},
		{lat:22.748322,lon:121.153107,c_name:"中国",p_name:"台湾省台东县",date:"2024-10-19 09:30:15",temp:"26"},
		{lat:23.989076,lon:121.619339,c_name:"中国",p_name:"台湾省花莲县",date:"2024-10-18 09:30:15",temp:"28"},
		{lat:21.965335,lon:120.813732,c_name:"中国",p_name:"台湾省垦丁公园",date:"2024-10-17 09:30:15",temp:"21"},
		{lat:24.284523,lon:116.122742,c_name:"中国",p_name:"广东省梅州市",date:"2024-10-17 09:30:15",temp:"23"},
		{lat:25.078136,lon:117.007141,c_name:"中国",p_name:"广东省龙岩市",date:"2024-10-16 09:30:15",temp:"22"}
	];

2、添加到collisionLayer中

        与前面介绍过的divicon标注方法一致,在这里还是要根据坐标点生成marker,然后在marker中设置divicon元素信息。关键代码如下:

var collisionLayer = L.LayerGroup.collision({margin:5});

for(var i=0;i<dataJson.length;i++){
	var marker = L.marker([dataJson[i].lat, dataJson[i].lon], {
		icon: L.divIcon({
			iconSize: null,
			className: '',
			popupAnchor:[5,5],
			shadowAnchor:[5,5],
			html: "<div class='marsBlackPanel'  animation-spaceInDown><div class='marsBlackPanel-text' style=''>国家名称:"+dataJson[i].c_name +"</div>"
			+"<div class='marsBlackPanel-text' style=''>城市名称:" + dataJson[i].p_name + "</div>"
			+"<div class='marsBlackPanel-text' style=''>采集时间:" + dataJson[i].date + "</div>"
			+"<div class='marsBlackPanel-text' style=''>当前温度:<span class='temperature'>" +dataJson[i].temp+ "</span> ℃</div></div>"
		})
	}).addTo(collisionLayer);
}

collisionLayer.addTo(map);

        首先我们会生成一个包含对象margin距离的图层组,通过图层组来进行marker数据绑定。在实际创建marker对象时,会根据这个margin来进行范围创建,代码如下:

_positionBox: function(offset, box) {
	return [
		box[0] + offset.x - this._margin,
		box[1] + offset.y - this._margin,
		box[2] + offset.x + this._margin,
		box[3] + offset.y + this._margin,
	]
}

        很明显,在这里将我们传入的组件的宽度加上本身组件的偏移量来生成bbox,然后将bbox添加到rbush中进行判断。将当前图层添加到rbush建立的rtree中,见下面的_maybeAddLayerToRBush方法。

addLayer: function(layer) {
		if ( !('options' in layer) || !('icon' in layer.options)) {
			this._staticLayers.push(layer);
			parentClass.prototype.addLayer.call(this, layer);
			return;
		}

		this._originalLayers.push(layer);
		if (this._map) {
			this._maybeAddLayerToRBush( layer );
		}
	}
_maybeAddLayerToRBush: function(layer) {
	var z    = this._map.getZoom();
	var bush = this._rbush;

	var boxes = this._cachedRelativeBoxes[layer._leaflet_id];
	var visible = false;
	if (!boxes) {
		// Add the layer to the map so it's instantiated on the DOM,
		//   in order to fetch its position and size.
		parentClass.prototype.addLayer.call(this, layer);
		var visible = true;
// 		var htmlElement = layer._icon;
		var box = this._getIconBox(layer._icon);
		boxes = this._getRelativeBoxes(layer._icon.children, box);
		boxes.push(box);
		this._cachedRelativeBoxes[layer._leaflet_id] = boxes;
	}

	boxes = this._positionBoxes(this._map.latLngToLayerPoint(layer.getLatLng()),boxes);

	var collision = false;
	for (var i=0; i<boxes.length && !collision; i++) {
		collision = bush.search(boxes[i]).length > 0;
	}

	if (!collision) {
		if (!visible) {
			parentClass.prototype.addLayer.call(this, layer);
		}
		this._visibleLayers.push(layer);
		bush.load(boxes);
	} else {
		parentClass.prototype.removeLayer.call(this, layer);
	}
}

3、实现效果

        实践是检验真理的唯一标准,在经过上面的编程后我们基本实现了对divicon的标注重叠。下面来看一下具体的效果。

        大家在上图的效果中就可以看到,在地图上重叠的divicon数据已经完全分开了。目前只展示了两个城市的信息。接着我们使用鼠标滚轮来进行地图的方法,看随着地图的放大,其它隐藏的标注是否会展示出来。 

        可以很明显的看到,随着地图的放大,原先隐藏的标签现在都展示出来了。进一步放大后看一下实际的效果如何?

         可以直观的看到,经过上面的开发步骤,我们已经成功的实现DivIcon标注的重叠隐藏和展示。非常完美的实现了我们的需求。

 四、总结

        以上就是本文的主要内容,本文即重点介绍如何在Leaflet中进行DidIcon信息标注时,应对空间对象重叠的问题,如何去解决。文章首先介绍一些可能的解决办法,然后介绍如何基于rbush和Leaflet.LayerGroup.Collision.js这两个组件来实现,最后介绍具体的实现方法即实现成果。当然,在本文的实现过程中,我们还可以结合数据检索,将marker进行空间查询过滤,减少数据量的返回,提高渲染的速度。

        当然,rbush这个组件不仅可以在我们这个场景中进行使用,可以在更多的空间查询中来使用更高效的算法,辅助前端进行数据的高效检索。更多好的使用方式,欢迎大家去探索。行文仓促,定有许多不足之处,还肯定各位朋友和专家在评论区留下批评意见,不生感激。

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

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

相关文章

理解OAuth2与用户账户与授权UAA的关系

目录 关于OAuth2OAuth2的核心组件授权流程授权模式使用场景优点与缺点 关于UAA技术解释 UAA与OAuth2的关系 关于OAuth2 ‌‌OAuth2&#xff08;开放授权2.0&#xff09;是一个开放标准&#xff0c;用于授权第三方应用程序访问用户资源&#xff0c;而无需共享用户的用户名和密码…

c语言中值调用(call by value)方式和引用调用(call by reference)

在C语言中参数传递主要有两种方式&#xff1a;通过值调用&#xff08;call by value&#xff09;和通过引用调用&#xff08;call by reference&#xff09;。 通过值调用&#xff08;Call by Value&#xff09; 说明&#xff1a;当使用值调用时&#xff0c;函数接收到的是参数…

(三)第一个Qt程序“Qt版本的HelloWorld”

一、随记 我们在学习编程语言的时候&#xff0c;各种讲解编程语言的书籍中通常都会以一个非常经典的“HelloWorld”程序展开详细讲解。程序虽然简短&#xff0c;但是“麻雀虽小&#xff0c;五脏俱全”&#xff0c;但是却非常适合用来熟悉程序结构、规范&#xff0c;快速形成对编…

Linux--学习笔记

第一章、简单使用Linux 1. Linux系统基本概念 多用户的系统&#xff1a;允许同时有很多个用户登录系统&#xff0c;使用系统里的资源多任务的系统&#xff1a;允许同时执行多个任务严格区分大小写&#xff1a;命令&#xff0c;选项&#xff0c;参数&#xff0c;文件名&#x…

从零实现数据结构:一文搞定所有排序!(下集)

1.快速排序 思路框架&#xff1a; 在有了前面冒泡选择插入希尔排序之后&#xff0c;人们就在想能不能再快一点&#xff0c;我们知道排序算法说人话就是把大的往后放小的往前放&#xff0c;问题就在于如何更快的把大的挪到数组队尾小的挪到数组前面。这里我们先总结一下上集前…

jenkins 自动化部署Springboot 项目

一、安装docker 1.更新yum命令 yum -y update2.查看机器有残留的docker服务&#xff0c;有就卸载干净 查看docker 服务 rpm -qa |grep docker卸载docker sudo yum remove docker-ce docker-ce-cli containerd.io sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/contai…

算法的学习笔记—二叉树的深度(牛客JZ55)

&#x1f600;前言 在二叉树的相关操作中&#xff0c;计算树的深度是一个非常基础但十分重要的操作。本文将详细解析如何计算一棵二叉树的深度&#xff0c;并通过代码实现来展示具体的解决方案。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1f49d;二叉树的深度…

了解 .NET 8 中的定时任务或后台服务:IHostedService 和 BackgroundService

IHostedService.NET 8 引入了使用和管理后台任务的强大功能BackgroundService。这些服务使长时间运行的操作&#xff08;例如计划任务、后台处理和定期维护任务&#xff09;可以无缝集成到您的应用程序中。本文探讨了这些新功能&#xff0c;并提供了实际示例来帮助您入门。您可…

HarmonyOS开发 - 本地持久化之实现LocalStorage实例

用户首选项为应用提供Key-Value键值型的数据处理能力&#xff0c;支持应用持久化轻量级数据&#xff0c;并对其修改和查询。数据存储形式为键值对&#xff0c;键的类型为字符串型&#xff0c;值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。 说明&#x…

同步电机不同电流参考方向下的功率计算

同步电机的功率计算有时候会看见两种表达方式&#xff1a; 当以发电机惯例&#xff0c;即电流方向输出时&#xff0c;功率计算式为&#xff1a; { P s 3 2 ( u s d i s d u s q i s q ) Q s 3 2 ( u s q i s d − u s d i s q ) \left\{\begin{array}{l} P_{\mathrm{s}}\fr…

PostgreSQL(十三)pgcrypto 扩展实现 AES、PGP 加密,并自定义存储过程

目录 一、pgcrypto 简介1.1 安装 pgcrypto 扩展1.2 pgcrypto 包含的函数 二、用法①&#xff1a;对称加密&#xff08;使用 AES、Blowfish 算法&#xff09;2.1 密钥2.2 密钥偏移量 三、用法②&#xff1a;PGP加解密3.1 什么是PGP算法&#xff1f;3.2 使用 GPG 生成密钥对3.3 列…

【AI大模型】深入解析 存储和展示地理数据(.kmz)文件格式:结构、应用与项目实战

文章目录 1. 引言2. 什么是 .kmz 文件&#xff1f;2.1 .kmz 文件的定义与用途2.2 .kmz 与 .kml 的关系2.3 常见的 .kmz 文件使用场景 3. .kmz 文件的内部结构3.1 .kmz 文件的压缩格式3.2 解压缩 .kmz 文件的方法3.3 .kmz 文件的典型内容3.4 .kml 文件的结构与主要元素介绍 4. 深…

豆包MarsCode Agent 登顶 SWE-bench Lite 评测集

大语言模型&#xff08;LLM&#xff09;能力正在迅速提升&#xff0c;对包括软件工程在内的诸多行业产生了深远影响。GPT-4o、Claude3.5 等 LLM 已经逐步展现出胜任复杂任务的能力&#xff0c;例如文本总结、智能客服、代码生成&#xff0c;甚至能够分析和解决数学问题。在这一…

为什么在网络中不能直接传输数据

为什么在网络中不能直接传输数据 原因 在网络中不能直接传输原始数据形式&#xff0c;主要有以下几方面原因&#xff1a; 数据表示的多样性&#xff1a;不同的计算机系统、编程语言和应用程序对数据的表示方式可能各不相同。例如&#xff0c;整数在不同的编程语言中可能有不同…

了解Java开发中的会话层

在现代Web应用开发中&#xff0c;会话管理是一个至关重要的概念。它涉及到如何在客户端和服务器之间保持用户状态信息&#xff0c;从而提供个性化、连续的用户体验。Java作为一种广泛使用的编程语言&#xff0c;在Web开发中扮演着重要角色&#xff0c;特别是在企业级应用中。了…

基于neo4j的课程资源生成性知识图谱

你是不是还在为毕业设计苦恼&#xff1f;又或者想在课堂中进行知识的高效管理&#xff1f;今天给大家分享一个你一定会感兴趣的技术项目——基于Neo4j的课程资源生成性知识图谱&#xff01;&#x1f4a1; 这套系统通过知识图谱的形式&#xff0c;将课程资源、知识点和学习路径…

一文掌握异步web框架FastAPI(五)-- 中间件(测试环境、访问速率限制、请求体解析、自定义认证、重试机制、请求频率统计、路径重写)

接上篇:一文掌握异步web框架FastAPI(四)-CSDN博客 目录 七、中间件 15、测试环境中间件 16、访问速率限制中间件,即限制每个IP特定时间内的请求数(基于内存,生产上要使用数据库) 1)限制单ip访问速率 2)增加限制单ip并发(跟上面的一样,也是限制每个IP特定时间内的请…

vue2结合echarts实现数据排名列表——前端柱状进度条排行榜

写在前面&#xff0c;博主是个在北京打拼的码农&#xff0c;工作多年做过各类项目&#xff0c;最近心血来潮在这儿写点东西&#xff0c;欢迎大家多多指教。 数据排名列表——图表开发&#xff0c;动态柱状图表&#xff0c;排名图 UI 直接搜到类似在线代码&#xff08;数据列表…

事务的原理、MVCC的原理

事务特性 数据库事务具有以下四个基本特性&#xff0c;通常被称为 ACID 特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务被视为不可分割的最小工作单元&#xff0c;要么全部执行成功&#xff0c;要么全部失败回滚。这意味着如果事务执行过程中发生…

交换机:端口安全与访问控制指南

为了实现端口安全和访问控制&#xff0c;交换机通常通过以下几种机制和配置来保护网络&#xff0c;防止未经授权的访问和恶意攻击。 01-端口安全 定义及功能 端口安全功能允许管理员限制每个交换机端口可以学习的MAC地址数量。 通过绑定特定的MAC地址到交换机的某一端口上&a…