将大量数据以热力图的方式呈现在地图上,需要解决很多问题。
背景
要求在地图上任意选择一个点为圆心,基于这个点,半径为 X 公里渲染热力图。有一份 Excel 数据,该表格每一行数据代表基于这个点的角度,每一列代表该角度上距离圆心的距离。交叉值为某个角度上距离圆心 N 公里的值。
基于已经渲染的热力图框选某个区域,改变其值。
最终使用的开源技术
- 地图:高德
- 热力图:Aatv-HeatmapLayer
- excel 数据处理:sheetjs
- 数据存储:indexDB
- 多线程:webWorker
需求分析与技术难点
前期需求和技术难点
首选我拿到这个需求的时候,做了简单的需求和技术分析:
- 数据中没有直接的经纬度坐标,只有距离和角度,怎么渲染?
- 文件处理的时候页面卡顿问题怎么解决?
- 采用渲染真实地图+经纬度渲染数据点?还是渲染地图背景图片,基于角度和距离计算 xy 坐标,在 canvas 根据值的大小渲染对应颜色?
- 怎么解决框选问题?
- 怎么知道哪些渲染点被框选了?
- 数据怎么存储?
- 怎么将角度转换为经纬度?
调研过程
一开始呢,我以为这个需求解决了上述问题,就没问题了。看起来还是一个比较简单的需求,但是事实上没那么简单。基于上述问题,我快速的给出了我的答案。
我花了一点时间去做技术调研,首先就看了高德地图 的热力图渲染。有一下结论:
- 地图接入简单。
- 支持热力图渲染。
- 支持矢量工具框选。
- 支持查询点是否在框选区域。
- 在渲染热力图的时候,数据量很大时,渲染很慢,这个和内部的实现原理有关系,高德的实现交互交过更好。渲染效果和 heatMapjs 差不多。
也看了 Cesium-Heatmap 和 googleMap-Heatmap,我的结论如下:
- googleMap 需求开通 google Cloud,大陆地区很麻烦,需求香港信用卡
- Cesium 自带的地理数据需要用户翻墙访问,可以考虑接入高德地图数据,但文档全英文对后续的功能接入可能产生一些麻烦。
- Heatmapjs 是基本的热力图技术,他的渲染速度比较快,但放大后有明显卡顿,如果想和高德地图结合使用,需要使用 Cesium 为桥梁。
- 如果对渲染及时性要求很高,可以考虑自己在通过高德地图的覆盖物/图层管理 API ,融合一下 heatMapjs
- AntV HeatmapLayer 是一个很好的开源项目,可以实现热力图的渲染。几乎零成本接入高德,渲染超级快
实现过程
最终决定使用高德的技术,在实现过程中:
-
使用 sheetjs 对 Excel 进行内容解析,解析过程放在 webWorker 中操作,避免产生卡顿,和 postMessage 进度。
-
通过角度、初始经纬度坐标、距离,使用球型三角函数,计算 Excel 中所有点的坐标。
-
将所有点计算完成后,放入 indexDB 中。
- 为了减少等待时间,这里可以使用多 webWorker 线程处理,将 excel 的所有数据切片,然后启动多个 webworker 处理这些数据。请看代码切片:
使用球型三角函数处理数据
如果使用平面三角函数处理数据会导致,地理数据误差特别大
1 | import Decimal from "decimal.js"; |
文件多线程解析 多 webWorker
下面的代码是将一个上百万的数据,分成 threads 份,threads 根据用户电脑的性能自己输入,同时调用 threads 个 webWorker,启动对数据的解析。
1 | const run = () => { |
indexDB 封装
相关数据的存储都放入本地。
1 | // 定义存储数据的接口 |
使用原始数据渲染
通过以上对数据的解析处理后,使用高德的 AMap.HeatMap 的 setDataSet 方法和 addDataPoint 方法,渲染每一度上的数据得到了下面的效果。
这里主要采用了两个办法:
-
所有数据使用同一个 HeatMap 渲染。
- 缺点:超级慢
- 优点:数据协调
-
每 1° 数据使用一个 HeatMap 进行渲染。
- 缺点:数据不平滑,两个 heatMap 之间会存在明显的图层痕迹。
- 优点:快
原始数据效果图
使用高德地图加多个 HeatMap 叠加实现
使用高德地图 + 一个 HeatMap 实现
使用 Google+heatMapjs 实现
antv/HeatmapLayer
这个热力图渲染超级快,是项目写完后最后才发现的(文章总结写完后才发现的),底层本接入高德地图
优化实现
为什么原始数据渲染效果很差
在上面的效果中,可以说效果特别差。为什么呢?
因为数据是集中在圆心的,导致圆心的数据在热力图上显现的效果值就很大。这就违背了整个需求,也就是说 可能圆心都没有什么值,但是看起来特别红。
怎么解决?
这个问题的原因呢,总结出来就一个问题,在 canvas 画布中,坐标点不平均,也就是说,任意多选择的两个点距离不相等。所以我们接下来就需要做一件事:重新构建数据,根据权重和原始数据值平均化分布点。
实现思路
- 将整个地球网格化: 比如以 0.001 经纬度为一个网格单位,将地球画成 N 个格子
- 那么所有的坐标点都会坐落于属于自己的那个网格。
- 根据原始数据的最小经度和最小维度开始向最大经度和维度循环每一个网格。
- 在当前的格子代表为中心,拿出四周的八个格子和自己格子的数据进行权重比计算。
- 将着九个格子的数据一一循环,将小于设定距离的点进行权重累加和值累加,最后:代表点的值=值累加/权重累加
实现代码切片
代码中 gridSpacing 代表网格的间隔大小,也会影响代表点的权重取值范围,gridSpacing 也就是说值越小,渲染的点就越多,渲染压力就更大,渲染越细腻。
1 | import Decimal from "decimal.js"; |
效果
最终优化渲染速度 AntV/HeatmapLayer
其实这篇文章,之前写完了还是不太满足于高德地图的热力图渲染速度,之后想着自己使用 heatmapjs 叠加高德地图图层根据比例实现,写了一点发现没那么简单,后面发现了 antV 的 HeatmapLayer,基于高德地图的地图底层实现,而且速度很快,接入层本很低,不影响调用高德地图的 API,特别是这个渲染速度,简直眼前一亮。
- 标题: 将大量数据以热力图的方式呈现在地图上,需要解决很多问题。
- 作者: 兰涛
- 创建于 : 2024-11-27 09:24:28
- 更新于 : 2024-12-02 17:50:12
- 链接: https://lands.work/447dad1f/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。