【Cesium Sandcastle 研究2】- Camera

本文详细介绍了如何在Cesium中禁用系统默认的相机事件,如旋转和平移,并通过ScreenSpaceEventHandler和document.addEventListener绑定自定义的鼠标和键盘事件,实现对相机的精确控制。同时,文章还展示了如何利用viewer.clock.onTick进行定时操作,更新camera状态。
效果

访问地址:

http://localhost:8080/Apps/Sandcastle/index.html?src=Camera%20Tutorial.html&label=Tutorials

在这里插入图片描述
通过键盘及鼠标控制相机

知识点
// Gets an ellipsoid describing the shape of this globe.
var ellipsoid = scene.globe.ellipsoid;

禁用系统的默认事件: 例如 旋转,平移,缩放等

// disable the default event handlers
scene.screenSpaceCameraController.enableRotate = false;
scene.screenSpaceCameraController.enableTranslate = false;
scene.screenSpaceCameraController.enableZoom = false;
scene.screenSpaceCameraController.enableTilt = false;
scene.screenSpaceCameraController.enableLook = false;

相机句柄
scene.screenSpaceCameraController
Gets the controller for camera input handling.

处理用户输入事件。可以添加自定义功能,以便在用户输入输入时执行。

var handler = new Cesium.ScreenSpaceEventHandler(canvas);


// 获取鼠标开始按下的位置
handler.setInputAction(function(movement) {
    flags.looking = true;
    mousePosition = startMousePosition = Cesium.Cartesian3.clone(movement.position);
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

// 鼠标开始移动
handler.setInputAction(function(movement) {
    mousePosition = movement.endPosition;
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

// 鼠标左键抬起时,关闭查看视角
handler.setInputAction(function(position) {
    flags.looking = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);

绑定按键按下和抬起的动作。

很优雅:
typeof flagName !== ‘undefined’

document.addEventListener('keydown', function(e) {
    var flagName = getFlagForKeyCode(e.keyCode);
    if (typeof flagName !== 'undefined') {
        flags[flagName] = true;
    }
}, false);

document.addEventListener('keyup', function(e) {
    var flagName = getFlagForKeyCode(e.keyCode);
    if (typeof flagName !== 'undefined') {
        flags[flagName] = false;
    }
}, false);

定时执行相关代码

// Gets the camera.
var camera = viewer.camera;


// 计时器,定时刷新,执行自定义函数处理
viewer.clock.onTick.addEventListener(function(clock) {



var width = canvas.clientWidth;
var height = canvas.clientHeight;


// 获取移动鼠标的坐标位置
// Coordinate (0.0, 0.0) will be where the mouse was clicked.
var x = (mousePosition.x - startMousePosition.x) / width;
var y = -(mousePosition.y - startMousePosition.y) / height;

// 将提供的笛卡尔转换为制图表示。笛卡尔在椭圆体的中心未定义。
var cameraHeight = ellipsoid.cartesianToCartographic(camera.position).height;
var moveRate = cameraHeight / 100.0;


相机移动的相关代码:

camera.lookRight(moveRate);
camera.lookUp(moveRate);

camera.moveForward(moveRate);
camera.moveBackward(moveRate);
camera.moveUp(moveRate);
camera.moveDown(moveRate);
camera.moveLeft(moveRate);
camera.moveRight(moveRate);
总结
  1. 禁用系统事件
  2. 通过 ScreenSpaceEventHandler 绑定鼠标事件处理函数; 通过 document.addEventListener 绑定键盘事件处理函数
  3. viewer.clock.onTick 更新 camera 相关操作
<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'cesium';import *as h3 from'h3-js';export default{name: 'CesiumH3Heatmap',mounted() {this.initViewer();this.drawH3Heatmap();},methods: {initViewer() {//初始化CesiumViewerthis.viewer= newCesium.Viewer('cesiumContainer', {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 'cesium'; import * as h3 from 'h3-js'; 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 'vue'; import Cesium from 'cesium'; import H3Heatmap from './H3Heatmap'; export default { setup() { let viewer; let heatmap; onMounted(() => { // 初始化Cesium Viewer viewer = new Cesium.Viewer('cesium-container', { 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('message', (event) => { const { points, resolution } = event.data; const hexData = {}; // 聚合逻辑... postMessage(hexData); }); // 主线程中 const worker = new Worker('h3-worker.js'); 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: '14px sans-serif' } }); } }, 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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值