Article
解读百度的Heatmap
前面通过Map的学习,了解到了瓦片的一些知识点。地图里面热图是一个比较典型的功能。通过对聚集数据不同颜色显示,直观形象的洞察数据的规律,比如说高危区等的热点分析,有点类似于arcgis的核密度。接下来结合百度里面的热图分析下它的实现。
var points =[
{"lng":116.418261,"lat":39.921984,"count":50},
...
]
//详细的参数,可以查看heatmap.js的文档 https://github.com/pa7/heatmap.js/blob/master/README.md
heatmapOverlay = new BMapLib.HeatmapOverlay({"radius":20});
map.addOverlay(heatmapOverlay);
heatmapOverlay.setDataSet({data:points,max:100});
# setDataSet
把经纬度数据先转成界面的坐标(不在界面bounds内的点会被忽略掉),然后调用setData
HeatmapOverlay.prototype.setDataSet = function(data) {
this.data = data;
...
var currentBounds = this._map.getBounds();
var mapdata = {
max: data.max,
data: []
};
var d = data.data,
dlen = d.length;
while (dlen--) {
...
if (!currentBounds.containsPoint(latlng)) {
continue;
}
...
mapdata.data.push({
x: point.x,
y: point.y,
count: d[dlen].count
});
}
this.heatmap.setData(mapdata);
}
# setData
计算最大最小,合并(对同一坐标的对应的count值求和),其中 _organiseData 根据坐标构建一个稀疏矩阵,最后emit给renderall
setData: function(data) {
var dataPoints = data.data;
var pointsLen = dataPoints.length;
// reset data arrays
this._data = [];
this._radi = [];
for(var i = 0; i < pointsLen; i++) {
this._organiseData(dataPoints[i], false);
}
this._max = data.max;
this._min = data.min || 0;
this._onExtremaChange();
this._coordinator.emit('renderall', this._getInternalData());
return this;
},
_organiseData: function(dataPoint, forceRender) {
var x = dataPoint[this._xField];
var y = dataPoint[this._yField];
var radi = this._radi;
var store = this._data;
var max = this._max;
var min = this._min;
var value = dataPoint[this._valueField] || 1;
var radius = dataPoint.radius || this._cfgRadius || defaultRadius;
...
if (!store[x][y]) {
store[x][y] = value;
radi[x][y] = radius;
} else {
store[x][y] += value;
}
...
_getInternalData: function() {
return {
max: this._max,
min: this._min,
data: this._data,
radi: this._radi
};
},
# renderall 渲染
这个是重点,下面一个步骤一个步骤的讲。
renderAll: function(data) {
// reset render boundaries
this._clear();
this._drawAlpha(_prepareData(data));
this._colorize();
},
# _prepareData
把上面合并数据创建的稀疏矩阵,再转回成对象 { x: ,y: ,value: , radius: } ,然后交给 _drawAlpha 进行画图。
var _prepareData = function(data) {
var renderData = [];
var min = data.min;
var max = data.max;
var radi = data.radi;
var data = data.data;
var xValues = Object.keys(data);
var xValuesLen = xValues.length;
while(xValuesLen--) {
var xValue = xValues[xValuesLen];
var yValues = Object.keys(data[xValue]);
var yValuesLen = yValues.length;
while(yValuesLen--) {
var yValue = yValues[yValuesLen];
var value = data[xValue][yValue];
var radius = radi[xValue][yValue];
renderData.push({
x: xValue,
y: yValue,
value: value,
radius: radius
});
}
}
return {
min: min,
max: max,
data: renderData
};
};
# _drawAlpha
然后根据处理整合后的数据画alpha的圆(由于透明度可以进行叠加处理,shadowCtx.globalAlpha = (value-min)/(max-min); ),同时统计会有数据的最大边界rect。
特定半径的密度衰减圆通过 _getPointTemplate 获得,每个数据以其x,y的坐标为圆心,根据count的百分比叠加模板密度圆的透明度进行绘制。由于透明度的叠加,起到 被影响的点 密度相加的效果。
_drawAlpha: function(data) {
var min = this._min = data.min;
var max = this._max = data.max;
var data = data.data || [];
var dataLen = data.length;
// on a point basis?
var blur = 1 - this._blur;
while(dataLen--) {
var point = data[dataLen];
var x = point.x;
var y = point.y;
var radius = point.radius;
// if value is bigger than max
// use max as value
var value = Math.min(point.value, max);
var rectX = x - radius;
var rectY = y - radius;
var shadowCtx = this.shadowCtx;
var tpl;
if (!this._templates[radius]) {
this._templates[radius] = tpl = _getPointTemplate(radius, blur);
} else {
tpl = this._templates[radius];
}
// value from minimum / value range
// => [0, 1]
shadowCtx.globalAlpha = (value-min)/(max-min);
shadowCtx.drawImage(tpl, rectX, rectY);
// update renderBoundaries
if (rectX < this._renderBoundaries[0]) {
this._renderBoundaries[0] = rectX;
}
if (rectY < this._renderBoundaries[1]) {
this._renderBoundaries[1] = rectY;
}
if (rectX + 2*radius > this._renderBoundaries[2]) {
this._renderBoundaries[2] = rectX + 2*radius;
}
if (rectY + 2*radius > this._renderBoundaries[3]) {
this._renderBoundaries[3] = rectY + 2*radius;
}
}
},
# _colorize
最后根据rect的边界范围,然后结合palette的颜色条进行染色(palette 是一个 256 * 4(rgba) 的数组)。
_colorize: function() {
var x = this._renderBoundaries[0];
var y = this._renderBoundaries[1];
var width = this._renderBoundaries[2] - x;
var height = this._renderBoundaries[3] - y;
var maxWidth = this._width;
var maxHeight = this._height;
var opacity = this._opacity;
var maxOpacity = this._maxOpacity;
var minOpacity = this._minOpacity;
var useGradientOpacity = this._useGradientOpacity;
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (x + width > maxWidth) {
width = maxWidth - x;
}
if (y + height > maxHeight) {
height = maxHeight - y;
}
var img = this.shadowCtx.getImageData(x, y, width, height);
var imgData = img.data;
var len = imgData.length;
var palette = this._palette;
for (var i = 3; i < len; i+= 4) {
var alpha = imgData[i];
var offset = alpha * 4;
if (!offset) {
continue;
}
var finalAlpha;
if (opacity > 0) {
finalAlpha = opacity;
} else {
if (alpha < maxOpacity) {
if (alpha < minOpacity) {
finalAlpha = minOpacity;
} else {
finalAlpha = alpha;
}
} else {
finalAlpha = maxOpacity;
}
}
imgData[i-3] = palette[offset];
imgData[i-2] = palette[offset + 1];
imgData[i-1] = palette[offset + 2];
imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
}
img.data = imgData;
this.ctx.putImageData(img, x, y);
this._renderBoundaries = [1000, 1000, 0, 0];
},
最终绘制到canvas上,呈现热图效果。
–END
Related
Related posts
-
从使用者到创造者:用 AI 构建你的专属 VS Code 工具链
2026-02-27
-
深入剖析Carota源码:从问题出发
2025-09-25
-
富文本编辑器开发学习笔记:跟踪canvas-editor有感,是金子终会发光
2025-09-07
-
深入解析 Nano Banana:Google 技术博客四篇精华翻译
2025-08-30