当前位置: 首页 > news >正文

鼠标获取坐标 vs 相机获取坐标

Cesium鼠标点击获取坐标 vs 相机视角获取坐标

鼠标点击获取坐标流程图

鼠标点击事件发生
获取屏幕坐标
执行射线拾取Pick
是否击中物体?
获取实体位置坐标
获取地球表面坐标
转换为经纬度坐标
使用获取的坐标

相机视角获取坐标流程图

获取相机当前状态
获取相机位置坐标
获取相机朝向向量
转换为经纬度坐标
计算相机视线与地球表面交点
获取相机视角坐标
使用获取的坐标

一、鼠标点击事件获取坐标 ⭐⭐⭐

1. 基本实现方法

// 创建事件处理器
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);// 注册鼠标左键点击事件
handler.setInputAction(function(click) {// 获取点击的屏幕坐标const screenPosition = click.position;// 方法1:拾取实体 - 如果点击到了模型或实体const pickedObject = viewer.scene.pick(screenPosition);if (Cesium.defined(pickedObject)) {// 获取实体的世界坐标const entity = pickedObject.id;if (entity && entity.position) {const position = entity.position.getValue(viewer.clock.currentTime);// 转换为经纬度坐标const cartographic = Cesium.Cartographic.fromCartesian(position);const longitude = Cesium.Math.toDegrees(cartographic.longitude);const latitude = Cesium.Math.toDegrees(cartographic.latitude);const height = cartographic.height;console.log(`点击实体坐标: 经度=${longitude}, 纬度=${latitude}, 高度=${height}`);}} // 方法2:射线拾取 - 如果点击到地球表面else {// 从相机位置创建一条射线,穿过点击的屏幕位置const ray = viewer.camera.getPickRay(screenPosition);// 计算射线与地球表面的交点const position = viewer.scene.globe.pick(ray, viewer.scene);if (Cesium.defined(position)) {// 转换为经纬度坐标const cartographic = Cesium.Cartographic.fromCartesian(position);const longitude = Cesium.Math.toDegrees(cartographic.longitude);const latitude = Cesium.Math.toDegrees(cartographic.latitude);const height = cartographic.height;console.log(`点击地表坐标: 经度=${longitude}, 纬度=${latitude}, 高度=${height}`);}}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

2. 高级用法 - 处理各种特殊情况

// 更完整的点击坐标获取函数
function getClickPosition(click, includeTerrainHeight = true) {// 点击的屏幕坐标const screenPosition = click.position;// 1. 首先尝试拾取3D瓦片或模型const pickedFeature = viewer.scene.pick(screenPosition);if (Cesium.defined(pickedFeature)) {// 如果点击到3D Tilesif (pickedFeature instanceof Cesium.Cesium3DTileFeature) {const cartesian = viewer.scene.pickPosition(screenPosition);if (Cesium.defined(cartesian)) {const cartographic = Cesium.Cartographic.fromCartesian(cartesian);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,type: '3DTiles',feature: pickedFeature};}}// 如果点击到Entityelse if (pickedFeature.id instanceof Cesium.Entity) {const entity = pickedFeature.id;if (entity.position) {const position = entity.position.getValue(viewer.clock.currentTime);const cartographic = Cesium.Cartographic.fromCartesian(position);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,type: 'Entity',entity: entity};}}// 如果点击到Primitiveelse if (pickedFeature.primitive) {const cartesian = viewer.scene.pickPosition(screenPosition);if (Cesium.defined(cartesian)) {const cartographic = Cesium.Cartographic.fromCartesian(cartesian);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,type: 'Primitive',primitive: pickedFeature.primitive};}}}// 2. 如果没有拾取到对象,尝试获取地球表面坐标const ray = viewer.camera.getPickRay(screenPosition);const position = viewer.scene.globe.pick(ray, viewer.scene);if (Cesium.defined(position)) {// 基本的经纬度坐标const cartographic = Cesium.Cartographic.fromCartesian(position);let height = cartographic.height;// 如果需要包含地形高度if (includeTerrainHeight && viewer.terrainProvider) {// 获取地形高度(异步)const promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]);Cesium.when(promise, function(updatedCartographics) {height = updatedCartographics[0].height;});}return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: height,type: 'Surface',position: position};}// 3. 如果前两种方法都失败,返回空值return null;
}

二、相机视角获取坐标 ⭐⭐⭐

1. 获取相机当前位置

// 获取相机当前位置
function getCameraPosition() {// 获取相机位置(世界坐标)const cameraPosition = viewer.camera.position;// 转换为经纬度坐标const cartographic = Cesium.Cartographic.fromCartesian(cameraPosition);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,type: 'Camera'};
}

2. 获取相机视线与地球的交点

// 获取相机视线中心与地球的交点
function getCameraViewTarget() {// 屏幕中心点const windowPosition = new Cesium.Cartesian2(viewer.canvas.clientWidth / 2,viewer.canvas.clientHeight / 2);// 从屏幕中心点创建射线const ray = viewer.camera.getPickRay(windowPosition);// 计算射线与地球表面的交点const position = viewer.scene.globe.pick(ray, viewer.scene);if (Cesium.defined(position)) {// 转换为经纬度坐标const cartographic = Cesium.Cartographic.fromCartesian(position);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,type: 'ViewTarget'};}return null;
}

3. 获取相机完整状态信息

// 获取相机完整状态(位置、朝向、视线等)
function getCameraState() {// 相机位置const position = viewer.camera.position;const positionCartographic = Cesium.Cartographic.fromCartesian(position);// 相机朝向const heading = Cesium.Math.toDegrees(viewer.camera.heading);const pitch = Cesium.Math.toDegrees(viewer.camera.pitch);const roll = Cesium.Math.toDegrees(viewer.camera.roll);// 相机视线方向const direction = viewer.camera.direction;// 屏幕中心视线与地球交点const windowPosition = new Cesium.Cartesian2(viewer.canvas.clientWidth / 2,viewer.canvas.clientHeight / 2);const ray = viewer.camera.getPickRay(windowPosition);const viewTarget = viewer.scene.globe.pick(ray, viewer.scene);let targetCartographic = null;if (Cesium.defined(viewTarget)) {targetCartographic = Cesium.Cartographic.fromCartesian(viewTarget);}return {// 相机位置(经纬度)position: {longitude: Cesium.Math.toDegrees(positionCartographic.longitude),latitude: Cesium.Math.toDegrees(positionCartographic.latitude),height: positionCartographic.height},// 相机姿态角orientation: {heading: heading,  // 航向角(正北为0,顺时针为正)pitch: pitch,      // 俯仰角(水平为0,向上为正)roll: roll         // 翻滚角},// 相机视线方向(笛卡尔坐标)direction: {x: direction.x,y: direction.y,z: direction.z},// 视线目标点(如果有)target: targetCartographic ? {longitude: Cesium.Math.toDegrees(targetCartographic.longitude),latitude: Cesium.Math.toDegrees(targetCartographic.latitude),height: targetCartographic.height} : null};
}

三、两种方法对比 ⭐⭐

特性鼠标点击获取坐标相机视角获取坐标
用户交互需要用户主动点击无需用户交互,可自动获取
适用场景精确选点、查询信息、标记位置视角记录、飞行定位、路径规划
精度高(取决于点击精度)中(视线中心点)
获取位置任意点击位置固定为屏幕中心或相机位置
实现复杂度中等简单
拾取对象可拾取实体、模型、地表主要获取地表或视线方向

四、应用场景示例

1. 鼠标点击坐标应用

// 创建标记点应用示例
const markerPoints = [];
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);// 注册点击事件
handler.setInputAction(function(click) {// 获取点击坐标const position = getClickPosition(click);if (position) {// 创建标记点实体const pointEntity = viewer.entities.add({name: `标记点 ${markerPoints.length + 1}`,position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, position.height),point: {pixelSize: 10,color: Cesium.Color.RED,outlineColor: Cesium.Color.WHITE,outlineWidth: 2},label: {text: `${markerPoints.length + 1}: ${position.longitude.toFixed(6)}, ${position.latitude.toFixed(6)}`,font: '14px sans-serif',horizontalOrigin: Cesium.HorizontalOrigin.LEFT,verticalOrigin: Cesium.VerticalOrigin.TOP,pixelOffset: new Cesium.Cartesian2(10, 10)}});// 添加到标记点数组markerPoints.push({id: markerPoints.length,entity: pointEntity,position: position});console.log(`已添加标记点: ${position.longitude.toFixed(6)}, ${position.latitude.toFixed(6)}, ${position.height.toFixed(2)}`);}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

2. 相机视角坐标应用

// 视角书签功能实现
const viewBookmarks = [];// 添加当前视角到书签
function addCurrentViewToBookmarks(name) {// 获取当前相机状态const cameraState = getCameraState();// 创建书签对象const bookmark = {id: viewBookmarks.length,name: name || `视角 ${viewBookmarks.length + 1}`,timestamp: new Date().toISOString(),cameraState: cameraState};// 添加到书签数组viewBookmarks.push(bookmark);console.log(`已保存视角: ${bookmark.name}`);return bookmark;
}// 飞行到指定书签视角
function flyToBookmark(bookmarkId) {const bookmark = viewBookmarks.find(b => b.id === bookmarkId);if (!bookmark) return;// 飞行到书签位置viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(bookmark.cameraState.position.longitude,bookmark.cameraState.position.latitude,bookmark.cameraState.position.height),orientation: {heading: Cesium.Math.toRadians(bookmark.cameraState.orientation.heading),pitch: Cesium.Math.toRadians(bookmark.cameraState.orientation.pitch),roll: Cesium.Math.toRadians(bookmark.cameraState.orientation.roll)},duration: 3.0});
}// 实时相机位置监控
function startCameraMonitoring() {// 每秒更新一次相机信息return setInterval(() => {const cameraPos = getCameraPosition();const target = getCameraViewTarget();// 更新UI显示document.getElementById('cameraInfo').textContent = `相机: ${cameraPos.longitude.toFixed(6)}, ${cameraPos.latitude.toFixed(6)}, ${cameraPos.height.toFixed(2)}米\n` +`视点: ${target ? `${target.longitude.toFixed(6)}, ${target.latitude.toFixed(6)}, ${target.height.toFixed(2)}` : '无'}`;}, 1000);
}

五、高级技巧与优化 ⭐⭐

1. 拾取优化

// 提高拾取精度与性能
function optimizedPick(screenPosition) {// 如果点击位置有多个对象重叠,首先用drillPick获取所有const pickedObjects = viewer.scene.drillPick(screenPosition);if (pickedObjects.length > 0) {// 按照优先级排序(可根据需求调整)pickedObjects.sort((a, b) => {// 优先返回Entityconst aIsEntity = a.id instanceof Cesium.Entity;const bIsEntity = b.id instanceof Cesium.Entity;if (aIsEntity && !bIsEntity) return -1;if (!aIsEntity && bIsEntity) return 1;// 其次是3DTilesconst aIs3DTiles = a.content && a.content.tileset;const bIs3DTiles = b.content && b.content.tileset;if (aIs3DTiles && !bIs3DTiles) return -1;if (!aIs3DTiles && bIs3DTiles) return 1;return 0;});// 返回排序后的第一个return pickedObjects[0];}return null;
}

2. 射线交点优化

// 提高射线拾取精度
function getTerrainIntersection(screenPosition) {// 创建射线const ray = viewer.camera.getPickRay(screenPosition);if (!ray) return null;// 地球表面交点let groundIntersection = viewer.scene.globe.pick(ray, viewer.scene);// 如果找不到交点(可能是视线方向与地球没有交点)if (!groundIntersection) {// 创建一个射线-球体交点计算const ellipsoid = viewer.scene.globe.ellipsoid;const intersection = Cesium.IntersectionTests.rayEllipsoid(ray, ellipsoid);if (intersection) {// 计算交点位置groundIntersection = Cesium.Ray.getPoint(ray, intersection.start);}}return groundIntersection;
}

3. 视线距离计算

// 计算两点间视线距离
function calculateLineOfSightDistance(pointA, pointB) {// 转换为Cartesian坐标const cartesianA = Cesium.Cartesian3.fromDegrees(pointA.longitude, pointA.latitude, pointA.height);const cartesianB = Cesium.Cartesian3.fromDegrees(pointB.longitude, pointB.latitude, pointB.height);// 计算直线距离const distance = Cesium.Cartesian3.distance(cartesianA, cartesianB);// 检查两点之间是否有遮挡const direction = Cesium.Cartesian3.subtract(cartesianB, cartesianA, new Cesium.Cartesian3());Cesium.Cartesian3.normalize(direction, direction);const ray = new Cesium.Ray(cartesianA, direction);// 检查射线是否与地形相交const results = viewer.scene.globe.pick(ray, viewer.scene);let hasObstacle = false;if (results) {// 计算交点与目标点的距离const intersectionDistance = Cesium.Cartesian3.distance(cartesianA, results);// 如果交点距离小于两点距离,说明有遮挡hasObstacle = intersectionDistance < distance * 0.99;}return {distance: distance,hasLineOfSight: !hasObstacle};
}

六、两种方法结合的最佳实践

// 结合两种方法的通用坐标获取工具
class CesiumCoordinateHelper {constructor(viewer) {this.viewer = viewer;this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);this.callbacks = {click: [],move: [],cameraChanged: []};// 初始化事件监听this.initEventListeners();}// 初始化事件监听initEventListeners() {// 点击事件this.handler.setInputAction((event) => {const position = this.getClickPosition(event.position);this.callbacks.click.forEach(callback => callback(position, event));}, Cesium.ScreenSpaceEventType.LEFT_CLICK);// 鼠标移动事件this.handler.setInputAction((event) => {const position = this.getClickPosition(event.endPosition);this.callbacks.move.forEach(callback => callback(position, event));}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);// 相机变化事件this.viewer.camera.changed.addEventListener(() => {const cameraState = this.getCameraState();this.callbacks.cameraChanged.forEach(callback => callback(cameraState));});}// 获取点击位置坐标getClickPosition(screenPosition) {// 首先尝试拾取对象const pickedObject = this.viewer.scene.pick(screenPosition);if (Cesium.defined(pickedObject)) {// 对象拾取成功if (pickedObject.id instanceof Cesium.Entity) {return this.getEntityPosition(pickedObject.id);} else {return this.getPickedPosition(screenPosition);}} else {// 获取地表坐标return this.getTerrainPosition(screenPosition);}}// 获取Entity位置getEntityPosition(entity) {if (entity && entity.position) {const position = entity.position.getValue(this.viewer.clock.currentTime);const cartographic = Cesium.Cartographic.fromCartesian(position);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,cartesian: position,type: 'entity',entity: entity};}return null;}// 获取精确拾取位置getPickedPosition(screenPosition) {const position = this.viewer.scene.pickPosition(screenPosition);if (Cesium.defined(position)) {const cartographic = Cesium.Cartographic.fromCartesian(position);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,cartesian: position,type: 'picked'};}return null;}// 获取地表位置getTerrainPosition(screenPosition) {const ray = this.viewer.camera.getPickRay(screenPosition);const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);if (Cesium.defined(position)) {const cartographic = Cesium.Cartographic.fromCartesian(position);return {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,cartesian: position,type: 'terrain'};}return null;}// 获取相机状态getCameraState() {const camera = this.viewer.camera;const position = camera.position;const cartographic = Cesium.Cartographic.fromCartesian(position);// 视线中心点const centerPosition = this.getCameraViewTarget();return {position: {longitude: Cesium.Math.toDegrees(cartographic.longitude),latitude: Cesium.Math.toDegrees(cartographic.latitude),height: cartographic.height,cartesian: position},orientation: {heading: Cesium.Math.toDegrees(camera.heading),pitch: Cesium.Math.toDegrees(camera.pitch),roll: Cesium.Math.toDegrees(camera.roll)},viewTarget: centerPosition,direction: camera.direction.clone()};}// 获取相机视线中心目标点getCameraViewTarget() {const windowPosition = new Cesium.Cartesian2(this.viewer.canvas.clientWidth / 2,this.viewer.canvas.clientHeight / 2);return this.getTerrainPosition(windowPosition);}// 注册点击事件回调onLeftClick(callback) {this.callbacks.click.push(callback);return this;}// 注册鼠标移动事件回调onMouseMove(callback) {this.callbacks.move.push(callback);return this;}// 注册相机变化事件回调onCameraChanged(callback) {this.callbacks.cameraChanged.push(callback);return this;}// 销毁事件处理器destroy() {this.handler.destroy();this.callbacks = {click: [],move: [],cameraChanged: []};}
}// 使用示例
const coordinateHelper = new CesiumCoordinateHelper(viewer);// 点击事件获取坐标
coordinateHelper.onLeftClick((position) => {if (position) {console.log(`点击坐标: ${position.longitude.toFixed(6)}, ${position.latitude.toFixed(6)}, ${position.height.toFixed(2)}`);// 在此处理坐标...}
});// 监听相机变化
coordinateHelper.onCameraChanged((cameraState) => {const pos = cameraState.position;const target = cameraState.viewTarget;console.log(`相机位置: ${pos.longitude.toFixed(6)}, ${pos.latitude.toFixed(6)}, ${pos.height.toFixed(2)}`);if (target) {console.log(`视点位置: ${target.longitude.toFixed(6)}, ${target.latitude.toFixed(6)}, ${target.height.toFixed(2)}`);}
});

总结对比

鼠标点击获取坐标

  • 优点: 精确定位、可选择任意位置、可交互获取对象信息
  • 缺点: 需要用户主动点击、无法自动采集

相机视角获取坐标

  • 优点: 自动采集、无需用户交互、可记录视角状态
  • 缺点: 固定中心点、不适合精确选点、难以拾取特定对象

在实际应用中,两种方法各有优势,通常需要结合使用:视角坐标用于导航定位和状态记录,点击坐标用于交互式操作和信息查询。最佳实践是开发一个综合坐标工具类,同时支持两种坐标获取方式。

http://www.xdnf.cn/news/193249.html

相关文章:

  • SpringBoot应用原生或docker镜像容器集成Skywalking
  • 数据要素与居民就业的深层联结 数字化转型下的劳动力市场变革
  • 项目上线流程梳理(Linux宝塔面板)
  • 基于Springboot + vue + 爬虫实现的高考志愿智能推荐系统
  • Web基础与HTTP协议
  • 第二章、Isaaclab强化学习包装器(1)
  • 研究:大模型输出一致性:确定性与随机性的场景化平衡
  • 【Android】SettingsPreferenceService
  • (002)Excel 使用图表,统计
  • conda和bash主环境的清理
  • 【优秀三方库研读】【性能优化点滴】odygrd/quill 解决伪共享
  • AcWing 885:求组合数 I ← 杨辉三角
  • vs2022解决 此项目需要MFC库。从visual studio安装程序(单个组件选项卡)为正在使用的任何工具和体系结构安装他们问题
  • JQ6500语音模块详解(STM32)
  • C++ 之 【模拟实现 list(节点、迭代器、常见接口)】(将三个模板放在同一个命名空间就实现 list 啦)
  • 电子电器架构 -- 汽车零部件DV试验与PV试验的定义及关键差异
  • [ 问题解决 ] sqlite3.ProgrammingError: SQLite objects created in a thread can ...
  • mybatis的xml ${item}总是更新失败
  • npm init、换源问题踩坑
  • 【Python数据驱动决策】数据分析与可视化全流程实战指南
  • 论文导读 - 基于边缘计算、集成学习与传感器集群的便携式电子鼻系统
  • Vue基础(7)_计算属性
  • C++核心编程:类与对象全面解析
  • Infrared Finance:Berachain 生态的流动性支柱
  • 车载软件架构 --- AUTOSAR的方法论
  • SwiftUI 8.List介绍和使用
  • 零基础制作Freertos智能小车(教程非常简易)持续更新中....
  • DeepSeek创始人梁文峰是个什么样的人?
  • LLM - Large Language Model
  • Android Studio 中使用 SQLite 数据库开发完整指南(Kotlin版本)