赞
踩
要用cesium做个三维热力图的效果,但在网上没找到现成的方案,摸索了很久,最终效果如下:
三维热力图
实现思路如下:
1、通过heatmapjs库生成二维热力图,拿到canvas;
- addHeatMap() {
- let width = 500
- let height = 500
- this.heatMap = h337.create(this.getDefaultData(width, height))
- let bounds = [113.5, 22, 114.5, 23]
-
- this.bounds = {
- west: 113.5,
- south: 22,
- east: 114.5,
- north: 23,
- }
- let data = [];
- let max = 0
- for (let i = 0; i < 200; i++) {
- let value = Math.floor(Math.random() * 100)
- max = Math.max(max, value)
- let x = Math.floor(Math.random() * width)
- let y = Math.floor(Math.random() * height)
- data.push({ x: x, y: y, value: value });
- }
-
-
- this.heatMap.setData({
- // min: 0,
- max: max,
- data: data
- })
-
- this.addcanvas(bounds, true)
- }
2、canvas的rgb像素值转hsl,将h分量作为该像素点的高度值的参考(即越红高度越高);
先转灰度图
- // 图片转灰度图
- getGrayMap(img) {
- const canvas = document.createElement('canvas')
- canvas.width = img.width
- canvas.height = img.height
- const ctx = canvas.getContext('2d')
- ctx.drawImage(img, 0, 0)
- const c = ctx.getImageData(0, 0, img.width, img.height)
- for (let i = 0; i < img.width; i++) {
- for (let j = 0; j < img.height; j++) {
- const x = (j * 4) * c.width + (i * 4)
- const r = c.data[x]
- const g = c.data[x + 1]
- const b = c.data[x + 2]
- this.colors.push([r / 255, g / 255, b / 255, 1], [r / 255, g / 255, b / 255, 1])
- const gray = 240 - this.rgbtohsl(r, g, b)[0]
- c.data[x] = c.data[x + 1] = c.data[x + 2] = gray
- }
- }
- ctx.putImageData(c, 0, 0, 0, 0, c.width, c.height)
- return canvas.toDataURL()
- }
灰度转出来是一维数组,转为rgba四通道
- getGrayValues(img) {
- const ch = new CanvasHeightmap()
- ch.use(img).then(() => {
- ch.draw()
- return ch.getRgbaArray()
- }).then((data) => { this.createTerrain(data) })
- }
3、将整个canvas划分,获取每个顶点的坐标值(经纬度+通过2中得到的高度);
- getPos(rgbaArray, bounds) {
- const arr = []
- const row = rgbaArray.length
- const col = rgbaArray[0].length
- const everyLon = (bounds.east - bounds.west) / col
- const everyLat = (bounds.north - bounds.south) / row
- for (let i = 0; i <= row ; i++) {
- for (let j = 0; j <= col ; j++) {
- if ((i * col + j + 1) % (col + 1) !== 0 && (i * col + j) < (col + 1) * (row)) {
- this.indices.push(i * col + j, i * col + j + 1, i * col + j + 1 + col + 1, i * col + j + 1 + col + 1, i * col + j, i * col + j + col + 1)
- }
-
- const lon1 = bounds.west + j * everyLon
- const lat1 = bounds.north - i * everyLat
- // let height1 = rgbaArray[i][j][0]
- let height1 = this.getHeight(i, j, col, row, rgbaArray)
- const position = Cesium.Cartesian3.fromDegrees(
- lon1,
- lat1, height1 * 10
- )
- arr.push(position.x, position.y, position.z)
- this.sts.push((j / rgbaArray[0].length), (1 - i / rgbaArray.length))
- }
- }
- return arr
- }
-
- getHeight(i, j, col, row ,rgbaArray) {
- let height1
- if (i === 0 && j === 0) {
- height1 = rgbaArray[i][j][0] }
- if (i === 0 && j !== 0 && j !== col) { height1 = (rgbaArray[i][j][0] + rgbaArray[i][j - 1][0]) / 2 }
- if (i === 0 && j === col) { height1 = rgbaArray[i][j - 1][0] }
- if (i !== 0 && i !== row && j === 0) { height1 = (rgbaArray[i][j][0] + rgbaArray[i - 1][j][0]) / 2 }
- if (i !== 0 && i !== row && j !== 0 && j !== col) { height1 = (rgbaArray[i][j][0] + rgbaArray[i - 1][j][0] + rgbaArray[i - 1][j - 1][0] + rgbaArray[i][j - 1][0]) / 4 }
- if (i !== 0 && i !== row && j === col) { height1 = (rgbaArray[i - 1][j - 1][0] + rgbaArray[i][j - 1][0]) / 2 }
- if (i !== 0 && i !== row && j !== 0 && j !== col) { height1 = (rgbaArray[i][j][0] + rgbaArray[i - 1][j][0] + rgbaArray[i - 1][j - 1][0] + rgbaArray[i][j - 1][0]) / 4 }
- if (i !== 0 && i !== row && j === col) { height1 = (rgbaArray[i - 1][j - 1][0] + rgbaArray[i][j - 1][0]) / 2 }
- if (i === row && j === 0) { height1 = rgbaArray[i - 1][j][0] }
- if (i === row && j !== 0 && j !== col) { height1 = (rgbaArray[i - 1][j][0] + rgbaArray[i - 1][j - 1][0]) / 2 }
- if (i === row && j === col) { height1 = rgbaArray[i - 1][j - 1][0] }
- return height1
- }
4、通过3中的坐标创建三角网,参考:
cesium 绘制自定义geometry、三角面_liuqing0.0的博客-CSDN博客_cesium geometry
- createGeometry(realPos) {
- const attributes = new Cesium.GeometryAttributes({
- position: new Cesium.GeometryAttribute({
- componentDatatype: Cesium.ComponentDatatype.DOUBLE,
- componentsPerAttribute: 3,
- values: new Float64Array(realPos)
- }),
- st: new Cesium.GeometryAttribute({
- componentDatatype: Cesium.ComponentDatatype.FLOAT,
- componentsPerAttribute: 2,
- values: new Float32Array(this.sts)
- })
- })
- const boundingSphere = Cesium.BoundingSphere.fromVertices(realPos,
- new Cesium.Cartesian3(0.0, 0.0, 0.0), 3)
- const geometry = new Cesium.Geometry({
- attributes: attributes,
- indices: this.indices,
- primitiveType: Cesium.PrimitiveType.TRIANGLES,
- boundingSphere: boundingSphere
- })
- return new Cesium.GeometryInstance({ geometry })
- }
-
- addGeometry(instance) {
- this.primitive = this.viewer.scene.primitives.add(
- new Cesium.Primitive({
- geometryInstances: instance,
- appearance: new Cesium.Appearance({
- material: new Cesium.Material({
- fabric: {
- uniforms: {
- image: this.heatMap._renderer.canvas.toDataURL()
- },
- source: this.getMS()
- }
- }),
- aboveGround: true,
- faceForward: true,
- flat: true,
- translucent: false,
- renderState: {
- blending: Cesium.BlendingState.PRE_MULTIPLIED_ALPHA_BLEND,
- depthTest: {
- enabled: true,
- },
- depthMask: true,
- },
- fragmentShaderSource: this.getFS(),
- vertexShaderSource: this.getVS()
- }),
- asynchronous: false
-
- }))
- }
主要需要处理position的values、st的values、和indices这三个属性的值。
我的思路是求出每个像素块的四个顶点的经纬度坐标,高度为周围的像素的h分量值的平均值;
每个像素块绘制两个三角形,如:左下--左上--右上 + 右上--左下--右下,当然顺序可以自己定义,只要保持每个三角形的起点与上一个三角形的终点连续就行;
5、将二维热力图作为三角网的材质贴图,并将三角网添加进场景;
贴图参考:
Cesium学习笔记-工具篇20-PrimitiveTexture自定义渲染-贴图【转】 | PNG
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。