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里的热力点不是正圆的。挺好解决的,大家自己扩展一下。