Cesium 三维热力图

要用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的博客-优快云博客_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

 

源码cesium三维热力图资源-优快云文库 

评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值