轉自原文 Cesium熱力圖實現javascript
生成熱力圖的算法我是用的一個熱力圖插件 heatmap.js。
heatmap中熱力圖生成原理:
heatmap中首先會根據輸入的漸進色參數,在內部生成一個0-255色值的調色板。
var _getColorPalette = function(config) { var gradientConfig = config.gradient || config.defaultGradient; var paletteCanvas = document.createElement('canvas'); var paletteCtx = paletteCanvas.getContext('2d'); paletteCanvas.width = 256; paletteCanvas.height = 1; var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1); for (var key in gradientConfig) { gradient.addColorStop(key, gradientConfig[key]); } paletteCtx.fillStyle = gradient; paletteCtx.fillRect(0, 0, 256, 1); return paletteCtx.getImageData(0, 0, 256, 1).data; }; 對於輸入的點數據,會根據點座標生成一個黑色圓陰影效果。 //生成一個陰影模板 var _getPointTemplate = function(radius, blurFactor) { var tplCanvas = document.createElement('canvas'); var tplCtx = tplCanvas.getContext('2d'); var x = radius; var y = radius; tplCanvas.width = tplCanvas.height = radius*2; if (blurFactor == 1) { tplCtx.beginPath(); tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false); tplCtx.fillStyle = 'rgba(0,0,0,1)'; tplCtx.fill(); } else { var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius); gradient.addColorStop(0, 'rgba(0,0,0,1)'); gradient.addColorStop(1, 'rgba(0,0,0,0)'); tplCtx.fillStyle = gradient; tplCtx.fillRect(0, 0, 2*radius, 2*radius); } 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] //根據value值設置陰影的alpha通道,後面能夠經過alpha值獲取value值 var templateAlpha = (value-min)/(max-min); // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData shadowCtx.globalAlpha = templateAlpha < .01 ? .01 : templateAlpha; 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; } }
首先呢,陰影是黑色的,因此接下來heatmap會進行一個像素點從新着色的過程,根據每一個點的alpha值*4(rgba步長)得出一個offset,而後從調色板上取顏色。由於上面設置了陰影透明度效果是遞減的,因此在獲取顏色的時候,就能得到一個平滑的漸變效果。這樣就獲得了熱力圖。html
_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]; },
採用這種利用透明度來對應獲取顏色值的好處就是這種漸變的過程比較柔和,漸變的效果更好。
so一開始爲何先繪製alpha漸變的黑點呢?是由於在純色圖像上方便計算它的alpha份量,這樣兩點相交的區域就會根據alpha份量進行疊加,在轉成彩圖的時候就能夠生成相應的值。
爲何不直接用彩色點疊加呢?是由於彩色點的RGBA並非簡單的線性疊加關係。
在Cesium上使用的原理比較簡單,就是根據輸入點的座標範圍計算一個包圍盒,建立一個rectangle geometry。而後呢,經過heatmap.js生成熱力圖,當作紋理貼到rectangle上面。在每一層級設置不一樣的radius,至關於在相機縮放的時候每一級都會生成一張熱力圖,而後更換紋理,實現縮放時的聚合離散效果。
這個過程須要注意的是如下幾點:
1. 如何將經緯度值映射到紋理上對應位置?
首先須要計算生成紋理的寬高像素,這裏我仿照了cesiumheatmap的算法,根據rectangle投影后的範圍和初始heatmap的設置的canvasSize參數來計算出一個寬高值。
2.用heatmap繪出的canvas貼到rectangle上會有黑色背景
這個能夠在shader裏面將黑色像素過濾掉便可。
'vec4 heightValue = texture2D(image, materialInput.st);' +
'if(heightValue.r<1.0/255.0) heightValue.a= 0.0; ' +
'if(heightValue.r<1.0/255.0) heightValue.a= 0.0; ' +
3.縮放實現聚合離散
上面提到過了,根據幾個層級範圍設置一個radius數組,相機縮放到哪一個層級就相應的改變它的radius進行重繪,而後替換紋理。
最終實現效果還能夠,可以平滑過渡。
在Openlayer中實現熱力圖起始是很方便的。具體可參考下面的幾篇文章。java
openlayers3 ol3熱力圖 jsongithub
進一步學習的參考資料算法
至於繪製的過程和原理、及完整代碼,能夠參考
http://www.wangshaoxing.com/blog/how-to-draw-a-heatmap.html
code
https://github.com/wshxbqq/WebGL-HeatMapjson