Cesium 使用自定义几何实现立体热力图

该文介绍了如何使用Cesium结合heatmap.js生成立体热力图,包括创建热力图、转换经纬度、生成几何实例等步骤,提供了详细的代码示例,并指出了可能的优化方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Cesium 立体热力图

cesium做热力图网上资料不少,大多是贴地的,立体热力图多数文章只有示例。这里我把代码贴出来,跟着一步一步写就能实现了。

效果图如下在这里插入图片描述

思路

1、用heatmap.js生成热力图;
2、用自定义的geometry创建primitive,根据坐标点位置对应的热力值设置高度;
3、给primitive贴上热力图;

heatmap.js例子挺多的,如果想学习可以自行搜资料。
自定义geometry的话可以看看这里 https://github.com/CesiumGS/cesium/wiki/Geometry-and-Appearances,实际上就是三个点构成一个面,很多连续的面就组成了几何。那么我们要做的就是在热力图的经纬度范围,插入很多中间点,每个点计算高度,然后每3个点组成一个面,就能得到一个不平整的几何面了。

需要引入heatmap.js和cesium,大家自己引一下。

创建热力图

cesium初始化的部分就不贴了

// 用到的全局变量
let viewer = null, heatmap = null,
            height = 1080, width = 1920,//热力图尺寸
            bounds = [113.5, 22, 114.5, 23]//覆盖经纬度范围.
function createHeatmap(data) {
   var domElement = document.createElement("div");
   domElement.setAttribute("style", "width: " + width + "px; height: " + height + "px; margin: 0px; display: none;");
   document.body.appendChild(domElement);
   heatmap = h337.create({
       container: domElement,
       radius: 100,
       maxOpacity: 1,
       minOpacity: 0.1,
       blur: 0.85,
       gradient: {
           '.3': 'blue',
           '.45': 'green',
           '.65': 'yellow',
           '.8': 'orange',
           '.95': 'red'
       },
   })
   heatmap.setData({
       min: 0,
       max: 100,
       data: data
   })
}

data是热力图的渲染数据,我们需要从经纬度转换为像素。

// 随机生成点
let data = [];
for (let i = 0; i < 200; i++) {
    let lon = Math.random() * (bounds[2] - bounds[0]) + bounds[0];
    let lat = Math.random() * (bounds[3] - bounds[1]) + bounds[1];
    let value = Math.random() * 100;
    let x = Math.round((lon - bounds[0]) / (bounds[2] - bounds[0]) * width)
    let y = height - Math.round((lat - bounds[1]) / (bounds[3] - bounds[1]) * height)
    data.push({ x: x, y: y, value: value });
}
//创建热力图
createHeatmap(data)

创建primitive

使用image材质把热力图贴上去。

function createPrimitive() {
   let material = new Cesium.ImageMaterialProperty({
       image: heatmap._renderer.canvas,//热力图的canvas直接拿来做材质
   });
   let instance = generateGeometryInstance()//primitive的geometry是生成立体图的关键
   let appearance = new Cesium.MaterialAppearance({
       material: Cesium.Material.fromType(Cesium.Material.ImageType, material.getValue(new Cesium.JulianDate())),
   })
   let opt = {
       geometryInstances: instance,
       appearance: appearance,
       allowPicking: false,
   }

   let primitive = viewer.scene.primitives.add(new Cesium.Primitive(opt));
}

generateGeometryInstance方法我们要插入点。

function generateGeometryInstance() {
    const dWidth = bounds[2] - bounds[0], dHeight = bounds[3] - bounds[1], left = bounds[0], bottom = bounds[1]
    const dx = 0.005, dy = 0.005, h = 0, dh = 100 // 这里配置了插入间隔和起始高度、高度间隔
    let r = Math.floor(dWidth / dx),
        l = Math.floor(dHeight / dy)
    const grids = []
    for (let i = 0; i < l + 1; i++) {
        let row = []
        for (let u = 0; u < r + 1; u++) {
            let x = left + (u == r ? dWidth : u * dx), y = bottom + (i == l ? dHeight : i * dy)
            let screen = {
                x: Math.round((x - left) / dWidth * width),
                y: height - Math.round((y - bottom) / dHeight * height),
            }
            let v = heatmap.getValueAt(screen)
            let color = heatmap._renderer.ctx.getImageData(screen.x, screen.y, 1, 1).data;
            row.push([x, y, h + v * dh, color.map(c => c / 255), [(x - left) / dWidth, (y - bottom) / dHeight]])
        }
        grids.push(row)
    }

    const wgs84Positions = []
    const indices = []
    const colors = []
    const sts = []
    let vtxCursor = 0
    let idxCursor = 0
    for (let i = 0; i < l; i++) {
        for (let u = 0; u < r; u++) {
            let p1 = grids[i][u]
            let p2 = grids[i][u + 1]
            let p3 = grids[i + 1][u + 1]
            let p4 = grids[i + 1][u]

            addVertices(p1, wgs84Positions, colors, sts)
            addVertices(p2, wgs84Positions, colors, sts)
            addVertices(p3, wgs84Positions, colors, sts)
            addVertices(p1, wgs84Positions, colors, sts)
            addVertices(p3, wgs84Positions, colors, sts)
            addVertices(p4, wgs84Positions, colors, sts)
            indices.push(idxCursor + 0, idxCursor + 1, idxCursor + 2, idxCursor + 3, idxCursor + 4, idxCursor + 5)
            idxCursor += 6
        }
    }
    return new Cesium.GeometryInstance({
    	// computeNormal会自动帮我们计算法向量
        geometry: Cesium.GeometryPipeline.computeNormal(generateGeometry(wgs84Positions, colors, indices, sts)),
    })
}
// 把信息写入点,可以在顶点着色器中取到
function addVertices(p, positions, colors, sts) {
    const c3Position = Cesium.Cartesian3.fromDegrees(p[0], p[1], p[2])
    positions.push(c3Position.x, c3Position.y, c3Position.z)
    colors.push(p[3][0], p[3][1], p[3][2], p[3][3])
    sts.push(p[4][0], p[4][1])
}
function generateGeometry(positions, colors, indices, sts) {
    let attributes = new Cesium.GeometryAttributes({
        position: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.DOUBLE,
            componentsPerAttribute: 3,
            values: new Float64Array(positions),
        }),
        color: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.FLOAT,
            componentsPerAttribute: 4,
            values: new Float32Array(colors),
        }),
        st: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.FLOAT,
            componentsPerAttribute: 2,
            values: new Float32Array(sts),
        }),
    })
    // 计算包围球
    const boundingSphere = Cesium.BoundingSphere.fromVertices(positions, new Cesium.Cartesian3(0.0, 0.0, 0.0), 3)
    //
    const geometry = new Cesium.Geometry({
        attributes: attributes,
        indices: indices,
        primitiveType: Cesium.PrimitiveType.TRIANGLES,
        boundingSphere: boundingSphere,
    })
    return geometry
}

调用一下createPrimitive就好了。

最后

照着写基本就能实现了,当然这个效果肯定是需要优化的,比如边缘的热力点突然截断了、渲染到cesium里的热力点不是正圆的。挺好解决的,大家自己扩展一下。

demo下载

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值