基于canvas和ol的点标注的避让实现

2024-03-07 23:30
文章标签 实现 canvas 标注 ol 避让

本文主要是介绍基于canvas和ol的点标注的避让实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

在做地图的时候,点的标注展示是一个非常常见的功能,但是十几种点在某些区域比较密集是非常常见的,但是业务表达中却需要将之展示出来。基于此需求,本文结合canvas和ol做一简单的实现。

效果

image.png

image.png

实现:

  1. 密集区点的标注通过牵引线的方式引出展示;
  2. 地图放大的时候更新展示;

思路

image.png

实现代码

const points = [{ "properties": {"name":"测试名称应该"}, "geometry": { "type": "Point", "coordinates": [ 110.15558, 19.91038 ] } },{ "properties": {"name":"测试名称应"}, "geometry": { "type": "Point", "coordinates": [ 113.52309, 22.21177 ] } },{ "properties": {"name":"测试名称应该"}, "geometry": { "type": "Point", "coordinates": [ 114.23454, 22.21177 ] } },{ "properties": {"name":"测该很长"}, "geometry": { "type": "Point", "coordinates": [ 119.35695, 26.06293 ] } },{ "properties": {"name":"测试名称应该称应该"}, "geometry": { "type": "Point", "coordinates": [ 120.11582, 30.07927 ] } },{ "properties": {"name":"测试名称应"}, "geometry": { "type": "Point", "coordinates": [ 121.49129, 31.14058 ] } },{ "properties": {"name":"测试名称应该"}, "geometry": { "type": "Point", "coordinates": [ 117.03289, 23.5228 ] } }
]
let canvasFunction = function (extent, resolution, pixelRatio, size, projection) {const canvasWidth = size[0]const canvasHeight = size[1]const [w, h] = map.getSize()const xoff = canvasWidth - w,yoff = canvasHeight - hconst canvas = document.createElement('canvas');canvas.width = canvasWidthcanvas.height = canvasHeightconst context = canvas.getContext('2d');// 数据聚类处理,根据上下和左右的距离进行判断function clusterData(data) {let res = {}let clusterTest = function (pixel, tolrance = [200, 30]) {let r = pixel.join(',')const [x, y] = pixelfor (let key in res) {const [_x, _y] = key.split(',').map(Number)const dx = Math.abs(x - _x),dy = Math.abs(y - _y)if(dx < tolrance[0] && dy < tolrance[1]) {r = keybreak}}return r}for (let i = 0; i < data.length; i++) {const d = data[i]const coords = ol.proj.fromLonLat(d.geometry.coordinates)let pixel = map.getPixelFromCoordinate(coords)pixel = [pixel[0]  + xoff / 2, pixel[1] + yoff / 2].map(p => Math.round(p))d.pixel = pixellet key = pixel.join(',')const width = 26 * 2 + 6 + context.measureText(d.properties.name).widthkey = clusterTest(pixel, [width, 30])if(!res[key]) res[key] = []res[key].push(d)}return res}// 绘制两边为圆的矩形function drawRoundRect (ctx, x, y, width, height = 24, fillStyle = 'rgba(14,77,137,0.75)') {const r = height / 2ctx.fillStyle = fillStylectx.beginPath()ctx.moveTo(x + r, y)ctx.lineTo(x + width - r, y)ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 0.5)ctx.lineTo(x + r, y + height)ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI * 1.5)ctx.closePath()ctx.fill()}// 绘制featurefunction drawFeature (ctx, x = 10, y = 10, text, notCluster = true, index = 0) {let height = 26, width = height * 2 + 6,  r = height / 2if(notCluster) width += ctx.measureText(text).widthctx.save()// 如果有聚类,则避让绘制文字,放在前面是为了让指引线在下面if(!notCluster && map.getView().getZoom() > 4) {const radius = 60const ang  = (-index * 40 - 115) / 180 * Math.PI ;const cx = x + r, cy = y + height - rconst px = cx + Math.sin(ang) * radius,py = cy + Math.cos(ang) * radius// 绘制牵引线ctx.beginPath()ctx.strokeStyle = 'rgba(14,77,137,0.75)'ctx.lineWidth = 2ctx.moveTo(cx, cy)ctx.lineTo(px, py)ctx.stroke()// 绘制牵引线终点小圆圈ctx.beginPath()ctx.fillStyle = 'rgba(14,77,137,1)'ctx.arc(px, py, 2, 0, Math.PI * 2)ctx.fill()// 绘制矩形const h = 18const w = ctx.measureText(text).width + 12drawRoundRect(ctx, px - w - 2, py - h / 2 - 1, w, h, 'rgba(14,77,137,0.5)')// 绘制文字ctx.fillStyle = 'rgb(255,255,255)'ctx.beginPath()ctx.textAlign = 'right'ctx.textBaseline = 'middle'ctx.fillText(text, px - 7, py)}// 绘制矩形drawRoundRect(ctx, x, y, width, height)// 绘制左边的图标ctx.beginPath()const radialLeft = ctx.createRadialGradient(x + r, y + height - r, 0, x + r, y + height - r, r)radialLeft.addColorStop(0, '#fff')radialLeft.addColorStop(1, 'rgba(255,255,255,0)')ctx.fillStyle = radialLeftctx.arc(x + r, y + height - r, r, 0, Math.PI * 2)ctx.fill()// 绘制右边的图标ctx.beginPath()const radialRight = ctx.createRadialGradient(x + width - r, y + r, 0, x + width - r, y + r, r)radialRight.addColorStop(0, '#fff')radialRight.addColorStop(1, 'rgba(255,255,255,0)')ctx.fillStyle = radialRightctx.arc(x + width - r, y + r, r, 0, Math.PI * 2)ctx.fill()// 如果没有聚类,则绘制文字if(notCluster) {ctx.fillStyle = '#fff'ctx.beginPath()ctx.textAlign = 'left'ctx.textBaseline = 'middle'ctx.fillText(text, x + height + 3, y + height - r)}ctx.restore()}const cluster = clusterData(points)for (let key in cluster) {const data = cluster[key]const showText = data.length === 1data.forEach((d, index) => {const [x, y] = d.pixeldrawFeature(context, x, y, d.properties.name, showText, index)})}return canvas;
}
const layer = new ol.layer.Image({source: new ol.source.ImageCanvas({canvasFunction: canvasFunction})
});
map.addLayer(layer);
script>

这篇关于基于canvas和ol的点标注的避让实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/785195

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S