CesiumJS【Basic】- #039 绘制渐变线(Entity方式)- 需要自定义着色器

<think>我们正在Cesium中实现H3网格热力图。需要结合h3-js库和Cesium的图形能力。步骤:1.引入必要的库:Cesium和h3-js。2.创建Cesium Viewer。3.定义目标区域(多边形)和H3分辨率。4.使用h3.polyfill生成覆盖该区域的六边形索引数组。5.将每个六边形索引转换为地理边界(经纬度点数组)。6.在Cesium绘制每个六边形(作为多边形图元)。7.对于热力图,我们需要数据聚合:将点数据聚合到每个六边形中,然后根据聚合值设置颜色。注意:Cesium绘制多边形需要使用PolygonGeometry或Entity.polygon。具体实现:第一步:环境设置在Vue项目中安装cesium和h3-js。第二步:初始化CesiumViewer在Vue组件中初始化Cesium Viewer。第三步:生成H3网格-定义区域(例如一个矩形)-选择合适的分辨率-使用h3.polyfill生成六边形索引数组第四步:绘制网格(无数据)-遍历每个六边形索引,获取其边界坐标(h3.h3ToGeoBoundary)-将边界坐标转换为Cesium.Cartesian3数组(注意:h3返回的是[lat, lng]数组,而Cesium需要经度在前,纬度在后,且需要将高度设为0)-使用Cesium.PolygonGeometry(或Entity绘制每个六边形第五步:热力图数据聚合-假设我们有一组点数据(每个点有经纬度和值)-遍历每个点,使用h3.geoToH3将其转换到对应的六边形索引-对每个六边形索引内的值进行累加(或平均等)第六步:根据聚合值设置颜色-计算所有六边形聚合值的最大值(用于归一化)-定义颜色梯度函数(如从蓝色到红色)-绘制每个六边形时,根据其聚合值设置颜色第七步:性能优化-对于大量六边形,使用Primitive而不是Entity(性能更好)-使用InstanceGeometry(如果颜色固定,可以合并几何体)但每个六边形颜色不同,可能需要单独绘制代码示例:1.安装依赖:npm installh3-js cesium2. Vue组件示例(部分代码): ```vue<template><div id="cesiumContainer"></div> </template><script>import *as Cesium from&#39;cesium&#39;;import *as h3 from&#39;h3-js&#39;;export default{name: &#39;CesiumH3Heatmap&#39;,mounted() {this.initViewer();this.drawH3Heatmap();},methods: {initViewer() {//初始化CesiumViewerthis.viewer= newCesium.Viewer(&#39;cesiumContainer&#39;, {terrainProvider:Cesium.createWorldTerrain()});//定位到旧金山this.viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(-122.43,37.77,5000)});},asyncdrawH3Heatmap(){//定义多边形区域(旧金山矩形)constpolygon =[[37.77, -122.43],[37.91,-122.43],[37.91, -122.23],[37.77,-122.23]];//注意:h3.polyfill要求的多边形格式是[[lat, lng],...],且要求闭合(最后一个点与第一个点相同?)但这里没有闭合,所以我们在函数中设置true表示GeoJson格式(闭合环)const resolution=9;consthexagons =h3.polyfill(polygon, resolution, true);//生成模拟点数据(用于热力图)constpoints =this.generateMockPoints(1000, polygon);//聚合数据到六边形consthexData= {};points.forEach(point=> {consthexId= h3.geoToH3(point.lat,point.lng,resolution);if(hexagons.includes(hexId)){ //只统计在区域内的点hexData[hexId]= (hexData[hexId]||0)+ point.value;}});//计算最大值const maxValue =Math.max(...Object.values(hexData));//遍历每个六边形,绘制constentities= [];hexagons.forEach(hexId=> {constvalue= hexData[hexId] ||0;//获取六边形边界(返回的是[[lat,lng], ...])constcoordinates= h3.h3ToGeoBoundary(hexId, true); //true表示GeoJson格式([lng,lat]?注意:h3ToGeoBoundary返回的是[lat,lng]数组,但GeoJson格式应该是[lng,lat]?实际上,h3ToGeoBoundary返回的是[lat,lng]数组,无论第二个参数是什么,第二个参数true表示返回的坐标是[[lng,lat],...]还是[lat,lng]?实际上,文档:https://h3geo.org/docs/api/indexing#h3togeoboundary//第二个参数(geoJson)设为true时,返回的是[[lng, lat], [lng, lat], ...]的数组,且第一个点和最后一个点相同(闭合)//所以我们需要将其转换为Cesium的Cartesian3数组(经度,纬度,高度)constpositions =coordinates.map(coord=> Cesium.Cartesian3.fromDegrees(coord[1], coord[0],0));//创建多边形实体constcolor= this.getColor(value,maxValue);constentity =this.viewer.entities.add({polygon:{hierarchy:new Cesium.PolygonHierarchy(positions),material:color,height:0,//可以设置高度,如果在地面以上extrudedHeight:0, //设置为0表示不挤压outline:false}});entities.push(entity);});},generateMockPoints(count, polygon) {//生成在polygon范围内的随机点const[minLat, maxLat]= [Math.min(...polygon.map(p=> p[0])), Math.max(...polygon.map(p=> p[0]))];const [minLng,maxLng]= [Math.min(...polygon.map(p =>p[1])), Math.max(...polygon.map(p=> p[1]))];constpoints =[];for (let i=0;i <count;i++){constlat =minLat+ Math.random()* (maxLat- minLat);constlng= minLng+ Math.random()* (maxLng -minLng);points.push({lat,lng,value:Math.random() *100});}return points;},getColor(value,maxValue) {//根据归一化的值返回颜色constratio =value /maxValue;const r= Math.floor(255 *ratio);constb =Math.floor(255* (1 -ratio));returnnewCesium.Color(r/255,0, b/255,0.7);}} };</script> <stylescoped>#cesiumContainer{width:100%;height:100vh;} </style>```注意:-上述代码使用Entity API,对于大量六边形(比如上万个)可能会影响性能。如果性能有问题,可以改用Primitive API。-使用Primitive API绘制多边形示例:const instance= newCesium.GeometryInstance({geometry:new Cesium.PolygonGeometry({polygonHierarchy: newCesium.PolygonHierarchy(positions)}),attributes: {color: Cesium.ColorGeometryInstanceAttribute.fromColor(color)}});this.viewer.scene.primitives.add(newCesium.Primitive({geometryInstances: [instance],appearance: newCesium.PerInstanceColorAppearance({flat: true,translucent:true})}));但是,由于每个六边形颜色不同,我们需要为每个六边形创建一个GeometryInstance。这样同样会创建很多个Primitive(实际上我们可以将所有六边形放在一个Primitive中,但每个实例可以有自己的颜色属性)。我们可以使用一个Primitive包含多个GeometryInstance,每个实例有自己的颜色属性。修改绘制部分(使用Primitive): ```javascript//创建一个Primitive,包含所有六边形实例const instances= [];hexagons.forEach(hexId=> {constvalue =hexData[hexId]||0;constcoordinates =h3.h3ToGeoBoundary(hexId,true);//返回[[lng, lat],...](因为第二个参数为true)constpositions =coordinates.map(coord=> Cesium.Cartesian3.fromDegrees(coord[0], coord[1],0));//注意:这里coord[0]是经度,coord[1]是纬度constcolor =this.getColor(value, maxValue);//创建实例instances.push(new Cesium.GeometryInstance({geometry: newCesium.PolygonGeometry({polygonHierarchy:new Cesium.PolygonHierarchy(positions),height:0}),attributes: {color: Cesium.ColorGeometryInstanceAttribute.fromColor(color)}}));});//添加Primitivethis.viewer.scene.primitives.add(new Cesium.Primitive({geometryInstances:instances,appearance: newCesium.PerInstanceColorAppearance({flat: true,translucent:true})}));```注意:使用Primitive时,坐标转换:h3ToGeoBoundary(hexId, true)返回的是[[lng, lat], [lng, lat], ...]的数组,所以直接使用coord[0]为经度,coord[1]为纬度。但是,注意:h3ToGeoBoundary的第二个参数(geoJson)设置为true时,返回的是[ [lng, lat], [lng, lat], ...],且第一个点和最后一个点相同(形成闭合环)。所以我们在创建PolygonHierarchy时,不需要再额外添加闭合点。性能对比:使用Primitive(合并成一个)比使用多个Entity性能更好。另外,如果区域很大,六边形数量很多,可能需要考虑分批次加载或使用WebWorker生成网格。引用:在Cesium中加载网格地图的相关内容可参考专栏中的示例[^1]。相关问题:1.如何优化Cesium中大量H3六边形的渲染性能?2.如何动态更新H3热力图的数据?3.在Cesium中如何实现H3六边形的点击事件?4.如何将H3网格与Cesium的地形高程结合?5.如何实现不同分辨率H3网格的切换?</think>###Cesium中结合H3-JS实现网格热力图 下面是在Cesium中实现H3六边形网格热力图的完整解决方案,结合了`h3-js`的地理空间索引能力和Cesium的三维可视化能力: #### 核心实现步骤 1. **环境准备** ```bash npm install cesium h3-js ``` 2. **基础代码结构** ```javascript import * as Cesium from &#39;cesium&#39;; import * as h3 from &#39;h3-js&#39;; export default class H3Heatmap { constructor(viewer) { this.viewer = viewer; this.resolution = 9; // H3网格分辨率(0-15) this.dataPoints = []; // 存储热力数据点 } // 添加测试数据点 addMockData(count = 1000) { for (let i = 0; i < count; i++) { const lat = 37.77 + (Math.random() - 0.5) * 0.2; const lon = -122.43 + (Math.random() - 0.5) * 0.2; this.dataPoints.push({ position: Cesium.Cartesian3.fromDegrees(lon, lat), value: Math.floor(Math.random() * 100) }); } } // 生成热力图 generateHeatmap() { // 步骤3-6的代码将放在这里 } } ``` 3. **数据聚合到H3六边形** ```javascript // 在generateHeatmap方法中: const hexData = {}; // 将点数据聚合到H3六边形 this.dataPoints.forEach(point => { const cartographic = Cesium.Cartographic.fromCartesian(point.position); const hexId = h3.geoToH3( Cesium.Math.toDegrees(cartographic.latitude), Cesium.Math.toDegrees(cartographic.longitude), this.resolution ); if (!hexData[hexId]) { hexData[hexId] = { count: 0, sum: 0, hexId: hexId }; } hexData[hexId].count++; hexData[hexId].sum += point.value; }); ``` 4. **创建六边形几何体** ```javascript // 创建Primitive集合 const hexagonPrimitives = new Cesium.PrimitiveCollection(); Object.values(hexData).forEach(data => { const avgValue = data.sum / data.count; const boundary = h3.h3ToGeoBoundary(data.hexId, true); // 将H3边界转换为Cesium坐标 const positions = boundary.map(coord => Cesium.Cartesian3.fromDegrees(coord[0], coord[1]) ); // 创建多边形实例 const instance = new Cesium.GeometryInstance({ geometry: new Cesium.PolygonGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy(positions), height: 0, perPositionHeight: false }), attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( this.getColorForValue(avgValue) ) } }); hexagonPrimitives.add(new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, closed: true }) })); }); this.viewer.scene.primitives.add(hexagonPrimitives); ``` 5. **颜色映射函数** ```javascript // 根据值获取颜色 getColorForValue(value) { const maxValue = 100; // 根据实际数据调整 const ratio = Math.min(value / maxValue, 1); // 蓝 -> 黄 -> 红 渐变 return new Cesium.Color( ratio, // 红色分量 ratio * 0.5, // 绿色分量 1 - ratio, // 蓝色分量 0.7 // 透明度 ); } ``` 6. **在Vue组件中使用** ```vue <template> <div id="cesium-container"></div> </template> <script> import { onMounted } from &#39;vue&#39;; import Cesium from &#39;cesium&#39;; import H3Heatmap from &#39;./H3Heatmap&#39;; export default { setup() { let viewer; let heatmap; onMounted(() => { // 初始化Cesium Viewer viewer = new Cesium.Viewer(&#39;cesium-container&#39;, { terrainProvider: Cesium.createWorldTerrain() }); // 定位到旧金山 viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(-122.43, 37.77, 5000) }); // 初始化热力图 heatmap = new H3Heatmap(viewer); heatmap.addMockData(5000); heatmap.generateHeatmap(); }); } }; </script> ``` #### 性能优化技巧 1. **合并几何体渲染** ```javascript // 使用单个Primitive渲染所有六边形 const instances = Object.values(hexData).map(data => { // ...创建GeometryInstance }); const primitive = new Cesium.Primitive({ geometryInstances: instances, appearance: new Cesium.PerInstanceColorAppearance({ translucent: true, closed: true }), asynchronous: false // 关闭异步加载提升性能 }); this.viewer.scene.primitives.add(primitive); ``` 2. **动态分辨率调整** ```javascript // 根据视图高度自动调整分辨率 this.viewer.camera.changed.addEventListener(() => { const height = this.viewer.camera.positionCartographic.height; const newRes = height > 100000 ? 7 : height > 50000 ? 8 : 9; if (newRes !== this.resolution) { this.resolution = newRes; this.generateHeatmap(); // 重新生成热力图 } }); ``` 3. **Web Worker数据处理** ```javascript // 在worker.js中处理数据聚合 addEventListener(&#39;message&#39;, (event) => { const { points, resolution } = event.data; const hexData = {}; // 聚合逻辑... postMessage(hexData); }); // 主线程中 const worker = new Worker(&#39;h3-worker.js&#39;); worker.postMessage({ points: this.dataPoints, resolution: this.resolution }); worker.onmessage = (event) => { this.renderHexagons(event.data); // 使用返回的数据渲染 }; ``` #### 交互功能实现 1. **添加点击事件** ```javascript // 在generateHeatmap方法中 this.viewer.screenSpaceEventHandler.setInputAction((click) => { const picked = this.viewer.scene.pick(click.position); if (picked && picked.primitive) { const hexId = picked.primitive.id; const hexData = this.hexData[hexId]; // 显示信息框 this.viewer.selectedEntity = new Cesium.Entity({ position: positions[0], label: { text: `H3: ${hexId}\n值: ${hexData.avgValue.toFixed(2)}`, font: &#39;14px sans-serif&#39; } }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); ``` 2. **动态更新热力图** ```javascript // 更新数据并刷新热力图 updateHeatmap(newData) { this.dataPoints = newData; this.viewer.scene.primitives.remove(this.heatmapPrimitive); this.generateHeatmap(); } ``` #### 完整示例项目 建议参考Cesium官方示例: - [Cesium + H3可视化示例](https://sandcastle.cesium.com/?src=Custom%20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

满天飞飞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值