Cesium热力图实现

20 篇文章 5 订阅
生成热力图的算法我是用的一个热力图插件 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,然后从调色板上取颜色。因为上面设置了阴影透明度效果是递减的,所以在获取颜色的时候,就能获得一个平滑的渐变效果。这样就得到了热力图。

_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; ' +


3.缩放实现聚合离散
上面提到过了,根据几个层级范围设置一个radius数组,相机缩放到哪个层级就相应的改变它的radius进行重绘,然后替换纹理。


最终实现效果还可以,能够平滑过渡。




















  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搞GIS图形的sky.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值