热力图实现原理

1 使用方法

1)实例化一个热力图图层,传入图层ID号,并将其添加到地图上:

1
2
var heatmapLayer = new gEcnu.Layer.Heatmap('heatmapLayer');
map.addLayer(heatmapLayer);

2)为热力图添加数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
var point = {
x: worldPoint.x, //点的x坐标
y: worldPoint.y, //点的y坐标
value: val, //数值大小,最后表现为该点的颜色值
radius: radius //该点的半径大小
};

var data = {
max: max, //数据中的最大值
data: [point,point,point...] //其中point为每一个数据点
};

this.heatmapLayer.setData(data);

2 基本思路

利用 HTML5 提供的 Canvas API的createRadialGradient方法对每个点绘制出渐变圆形,再使用drawImage方法绘制到一个画布上;创建一个宽为256px,高为1px的矩形,利用createLinearGradient方法对其进行填色;

最后根据画布上每个点的透明度判读该点使用的颜色,透明度的值越大,颜色越红,值越小,颜色越蓝。

3 热力图的实现

3.1 插件形式的写法

3.1.1 支持模块化环境

通过写成自执行匿名函数的形式,并将变量名、作用域以及函数主体以参数的形式传入。判读当前处于什么模式,如果符合CMD规范,就使用module.exports的形式导出;如果符合AMD规范,就使用define()的形式导出;若都不满足,则在传入的作用域中添加一个以传入的变量名为键名的属性,值为传入的函数体的执行结果。

3.1.2 闭包的写法

将不必对外暴露的函数和变量放在闭包中,转变为私有变量。通过这种方式,外部无法改变私有变量的值,保证变量不会被篡改。

3.1.3 在文件开头加上分号

保证文件在被合并压缩时不会发生错误。因为文件的开头就是一对括号,在文件合并时,若上一个文件的最后是一个函数,就会产生直接执行该函数的结果,导致意想不到的错误,因此在文件的最开头初加上分号,防止错误的发生。

3.2 数据管理

3.2.1 数据存储

改变传入数据的形式,将每个点的值value和半径radius都保存在一个二维数组中,第一维为点的x左边,第二维为点的y坐标,这样便于比较传入的数据与已经存储的数据,若传入的数据点中有点的x坐标和y坐标都相同的情况,则将该点的值累加。

3.2.2 追加数据

可以向Store中追加数据,若数据点不止一个,而是以数组的形式传入,则递归处理数据。若传入数据的值小于原来数据的最大值,则在Canvas中追加绘制该点的图形;反之若大于最大值,则需要重新绘制整个Canvas的内容。

3.3 数据渲染

3.3.1 预渲染

由于数据量较大,将每个点绘制到画布时会导致频繁的重绘,因此考虑使用一个与目标Canvas画布同等大小且不可见的画布。由于离屏Canvas是不可见的,在DOM解析时,并不会对其进行渲染,只有将其再绘制到可见的目标画布上,才会解析样式进行渲染,因此绘制每一个点时都将其绘制到离屏画布中。

3.3.2 绘制每个数据点的黑白渐变圆

创建一个临时的Canvas 画布,画布的宽和高都设置为半径值的2倍。根据数据点的半径值,以及模糊数,绘制一个矩形,矩形的大小等于画布的大小。并利用Canvas API的createRadialGradient方法绘制渐变圆,设置渐变圆的起点颜色为黑色,终点颜色为白色,对矩形进行填充,填充的结果如图所示。对于不同的模糊数,生成的渐变圆效果有所不同,如图左边的渐变圆模糊数blur为0.15,左边的渐变圆模糊数blur为0.8,在系统中我们将其设置为0.15。

根据每个点的value在最小值与最大值之间的百分比,设置预渲染画布的透明度,并使用drawImage方法将每一个点的数据绘制到预渲染画布中,最后得到如下的效果:

1
2
shadowCtx.globalAlpha = (value-min)/(max-min);
shadowCtx.drawImage(tpl, rectX, rectY);

3.3.3 创建颜色模板

在一个临时的Canvas 画布中,将画布的宽设置为256px,高设置为1px,同时绘制一个同等大小的矩形,利用createLinearGradient方法创建一条线性颜色渐变对其进行填色,使用Canvas API的getImageData方法得到ImageData对象,该对象的data属性中存放着每个点的R(红色)G(绿色)B(蓝色)A(透明度)信息,因此我们得到的data的长度为256*4=1024,每四个元素对应一个点的信息。如图前四个元素对应第一个蓝色像素点的信息。

3.3.4 着色

对预渲染画布着色,并将其绘制到可见Canvas画布中。同样使用getImageData方法得到预渲染画布的ImageData对象,取得data属性中每隔4个元素的值,即每个点的透明度。依据该值,以及颜色模板,对该点着色,若该点的值为0则赋为第一个点的颜色蓝色,若该点的值为255,则赋为最后一个点的颜色红色。将该点的RGBA属性设置为对应颜色的RGBA属性。
修改完成后,使用Canvas API的putImageData方法,将预渲染画布中的内容绘制到可见画布上。

4 与ccgis平台的对接

4.1 热力图层

构造热力图层,继承自要素图层,初始化时将该图层的oClass属性设置为heatmapLayer

1
gEcnu.Layer.Heatmap = gEcnu.Layer.Feature.extend();

当将其添加到地图时,使用gHeatmap.create创建热力图。添加数据时,先对数据进行预处理,将传入的世界坐标转为屏幕坐标。缩放地图时,每一个数据点的半径也要相应缩放,因此对半径乘以缩放系数。
传入要素集时,首先取得视窗范围内的要素,并将每个要素的坐标转换为屏幕坐标,最后使用setData方法添加到图层中。

5 改进计划

目前使用随机数作为模拟数据,进一步可以考虑应用到真实数据中。

热评文章