Vue 中使用 Cesium 实现可拖拽点标记及坐标实时显示功能

在 Cesium 地图开发中,实现点标记的拖拽交互并实时显示坐标信息是一个常见的需求。本文将详细介绍如何在 Vue 框架中使用 Cesium 的 Primitive 方式创建点标记,并实现拖拽功能及坐标提示框跟随效果。

先看效果图

功能实现概述

我们将实现的功能包括:

  • 使用 Primitive 方式创建可交互的点标记
  • 实现点标记的拖拽功能(点击选中、跟随鼠标移动、释放确定位置)
  • 拖拽过程中实时显示坐标信息提示框
  • 拖拽时禁用地图默认交互,提升操作体验

核心技术点解析

1. 创建可拖拽点标记

使用PointPrimitiveCollection创建点集合,添加点标记并设置基本样式:

createPoint_primitives() {// 创建点集合const pointCollection = new Cesium.PointPrimitiveCollection();// 添加点this.draggablePoint = pointCollection.add({position: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 0), // 初始位置(北京坐标)color: Cesium.Color.RED, // 点颜色pixelSize: 30, // 点大小outlineColor: Cesium.Color.WHITE, // 轮廓颜色outlineWidth: 2, // 轮廓宽度id: 'draggable-point', // 唯一标识});// 将点集合添加到场景图元中this.viewer.scene.primitives.add(pointCollection);// 设置点的拖动功能this.setupPointDragging();
}

2. 实现拖拽交互逻辑

拖拽功能主要通过监听鼠标的三个事件实现:左键按下、鼠标移动和左键释放。

左键按下事件

// 左键按下事件 - 开始拖动
handler.setInputAction((click) => {// 拾取鼠标点击位置的对象const pickedObject = this.viewer.scene.pick(click.position);// 检查是否拾取到了我们创建的点if (Cesium.defined(pickedObject) &&  // 确保拾取对象存在pickedObject.primitive === this.draggablePoint  // 确认是目标点) {isDragging = true;currentPoint = pickedObject.primitive;currentPoint.color = Cesium.Color.BLUE; // 改变颜色表示选中状态// 显示坐标提示框并更新位置this.updateTooltip(currentPoint.position);this.showCoordinates = true;this.disableMapInteraction(); // 禁用地图默认交互}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

这里的Cesium.defined(pickedObject)是 Cesium 提供的工具函数,用于检查一个值是否 "已定义且非空",避免后续操作出现空指针错误。

鼠标移动事件

在鼠标移动时,实时更新点的位置和坐标提示框:

// 鼠标移动事件 - 更新点位置
handler.setInputAction((movement) => {if (!isDragging || !currentPoint) return;// 将鼠标位置转换为地理坐标const ray = this.viewer.camera.getPickRay(movement.endPosition);const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);if (Cesium.defined(position)) {// 更新点的位置currentPoint.position = position;// 移动时实时更新提示框位置this.updateTooltip(position);}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
左键释放事件

拖拽结束时,恢复点的样式并保存最终位置:

// 左键释放事件 - 结束拖动
handler.setInputAction(() => {if (isDragging && currentPoint) {// 恢复点的原始颜色currentPoint.color = Cesium.Color.RED;// 获取最终位置的经纬度const cartographic = Cesium.Cartographic.fromCartesian(currentPoint.position);const longitude = Cesium.Math.toDegrees(cartographic.longitude);const latitude = Cesium.Math.toDegrees(cartographic.latitude);console.log(`点位置已更新至: 经度 ${longitude.toFixed(4)}, 纬度 ${latitude.toFixed(4)}`);this.savePointPosition(longitude, latitude);}// 恢复地图的默认鼠标交互this.enableMapInteraction();isDragging = false;currentPoint = null;
}, Cesium.ScreenSpaceEventType.LEFT_UP);

3. 坐标提示框跟随功能

实现坐标提示框跟随点移动的核心是updateTooltip方法,该方法完成两件事:将三维坐标转换为经纬度,以及将三维坐标转换为屏幕坐标用于定位提示框。

updateTooltip(position) {// 1. 计算经纬度信息const cartographic = Cesium.Cartographic.fromCartesian(position);this.coordinate = {lng: Cesium.Math.toDegrees(cartographic.longitude),lat: Cesium.Math.toDegrees(cartographic.latitude),};// 2. 计算屏幕坐标(兼容不同Cesium版本)const screenPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates? Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene,position): Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene,position);// 3. 设置提示框位置(偏移20px避免遮挡点)if (screenPosition) {this.tooltipPosition = {x: screenPosition.x + 20,y: screenPosition.y - 20,};}
}

4. 地图交互控制

为了提升拖拽体验,在拖拽过程中需要禁用地图的默认交互,拖拽结束后再恢复:

// 禁用地图交互的方法
disableMapInteraction() {// 禁用鼠标左键拖动地图this.viewer.scene.screenSpaceCameraController.enableRotate = false;// 禁用鼠标右键缩放this.viewer.scene.screenSpaceCameraController.enableZoom = false;// 禁用中键平移this.viewer.scene.screenSpaceCameraController.enableTranslate = false;// 禁用倾斜this.viewer.scene.screenSpaceCameraController.enableTilt = false;// 禁用旋转this.viewer.scene.screenSpaceCameraController.enableLook = false;
}// 恢复地图交互的方法
enableMapInteraction() {// 恢复所有鼠标交互this.viewer.scene.screenSpaceCameraController.enableRotate = true;this.viewer.scene.screenSpaceCameraController.enableZoom = true;this.viewer.scene.screenSpaceCameraController.enableTranslate = true;this.viewer.scene.screenSpaceCameraController.enableTilt = true;this.viewer.scene.screenSpaceCameraController.enableLook = true;
}

完整代码实现

下面是完整的 Vue 组件代码,包含了上述所有功能:

高德地图三个地址
export const mapConfig = {gaode: {url1: 'http://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8', //'高德路网中文注记'url2: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}', //高德影像url3: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', //高德矢量},
};
 封装初始化地图实例,组件直接引用
/*** 初始化地图实例* @param {string} url - 地图瓦片服务的URL模板* @param {boolean} is3d - 是否启用3D地图模式*/
export default function initMap(url, is3d) {// 初始化地图// 创建一个Cesium Viewer实例,绑定到ID为'cesiumContainer'的DOM元素const viewer = new Cesium.Viewer('cesiumContainer', {animation: false, // 隐藏动画控件baseLayerPicker: false, // 隐藏基础图层选择器fullscreenButton: false, // 隐藏全屏按钮geocoder: false, // 隐藏地理编码搜索框homeButton: false, // 隐藏主页按钮infoBox: false, // 隐藏信息框sceneModePicker: false, // 隐藏场景模式选择器scene3DOnly: is3d ? true : false, // 是否启用3D-only模式优化性能,若is3d为true则启用sceneMode: is3d ? Cesium.SceneMode.SCENE3D : Cesium.SceneMode.SCENE2D, // 场景模式,is3d为true时使用3D场景,否则使用2D场景selectionIndicator: false, // 隐藏选择指示器timeline: false, // 隐藏时间轴navigationHelpButton: false, // 隐藏导航帮助按钮navigationInstructionsInitiallyVisible: false, // 初始时不显示导航说明shouldAnimate: true, // 启用场景动画projection: new Cesium.WebMercatorProjection(), // 地图投影,使用Web墨卡托投影});// 移除默认影像图层viewer.scene.imageryLayers.remove(viewer.scene.imageryLayers.get(0));// 去除版权信息viewer._cesiumWidget._creditContainer.style.display = 'none';// 向地图的影像图层添加一个自定义的瓦片影像服务viewer.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({url: url, // 瓦片服务的URL模板subdomains: ['0', '1', '2', '3'], // 子域名列表maximumLevel: 18, // 最大缩放级别}));// 初始定位viewer.camera.setView({destination: Cesium.Cartesian3.fromDegrees(118.006, 39.7128, 1500000), // (lng, lat, height)});return viewer;
}
地图组件代码
<template><div id="cesiumContainer" style="width: 100%; height: 100vh"><!-- 坐标信息提示框 --><divv-if="showCoordinates"class="coordinate-tooltip":style="{ left: tooltipPosition.x + 'px', top: tooltipPosition.y + 'px' }">经度: {{ coordinate.lng.toFixed(6) }}<br />纬度: {{ coordinate.lat.toFixed(6) }}</div></div>
</template><script>
import initMap from '@/config/initMap.js';
import { mapConfig } from '@/config/mapConfig';
export default {data() {return {viewer: null,showCoordinates: false,coordinate: { lng: 0, lat: 0 },tooltipPosition: { x: 0, y: 0 },};},mounted() {this.viewer = initMap(mapConfig.gaode.url3, false);this.$nextTick(async () => {await this.createPoint_primitives(); // primitives方式创建});},methods: {// 创建点标记createPoint_primitives() {// 创建点集合const pointCollection = new Cesium.PointPrimitiveCollection();// 添加点this.draggablePoint = pointCollection.add({position: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 0),color: Cesium.Color.RED,pixelSize: 30,outlineColor: Cesium.Color.WHITE,outlineWidth: 2,id: 'draggable-point',});// 将点集合添加到场景图元中this.viewer.scene.primitives.add(pointCollection);// 设置点的拖动功能this.setupPointDragging();},setupPointDragging() {const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);let isDragging = false;let currentPoint = null;// 左键按下事件 - 开始拖动handler.setInputAction((click) => {const pickedObject = this.viewer.scene.pick(click.position); // 拾取鼠标点击位置的对象// 检查是否拾取到了目标点if (Cesium.defined(pickedObject) &&  // 确保拾取对象存在pickedObject.primitive === this.draggablePoint  // 确认是我们创建的点) {isDragging = true;currentPoint = pickedObject.primitive;currentPoint.color = Cesium.Color.BLUE; // 改变点的外观以指示正在拖动// 初始显示提示框并更新位置this.updateTooltip(currentPoint.position);this.showCoordinates = true;this.disableMapInteraction(); // 禁用地图的默认鼠标交互}}, Cesium.ScreenSpaceEventType.LEFT_DOWN);// 鼠标移动事件 - 更新点位置handler.setInputAction((movement) => {if (!isDragging || !currentPoint) return;// 将鼠标位置转换为地理坐标const ray = this.viewer.camera.getPickRay(movement.endPosition);const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);if (Cesium.defined(position)) {// 更新点的位置currentPoint.position = position;// 移动时实时更新提示框位置this.updateTooltip(position);}}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);// 左键释放事件 - 结束拖动handler.setInputAction(() => {if (isDragging && currentPoint) {// 恢复点的原始颜色currentPoint.color = Cesium.Color.RED;// 获取最终位置的经纬度const cartographic = Cesium.Cartographic.fromCartesian(currentPoint.position);const longitude = Cesium.Math.toDegrees(cartographic.longitude);const latitude = Cesium.Math.toDegrees(cartographic.latitude);console.log(`点位置已更新至: 经度 ${longitude.toFixed(4)}, 纬度 ${latitude.toFixed(4)}`);this.savePointPosition(longitude, latitude);}// 恢复地图的默认鼠标交互this.enableMapInteraction();isDragging = false;currentPoint = null;}, Cesium.ScreenSpaceEventType.LEFT_UP);},// 更新提示框位置和坐标信息updateTooltip(position) {// 1. 计算经纬度信息const cartographic = Cesium.Cartographic.fromCartesian(position);this.coordinate = {lng: Cesium.Math.toDegrees(cartographic.longitude),lat: Cesium.Math.toDegrees(cartographic.latitude),};// 2. 计算屏幕坐标(兼容不同Cesium版本)const screenPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates? Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene,position): Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene,position);// 3. 设置提示框位置(偏移20px避免遮挡点)if (screenPosition) {this.tooltipPosition = {x: screenPosition.x + 20,y: screenPosition.y - 20,};}},// 禁用地图交互的方法disableMapInteraction() {// 禁用鼠标左键拖动地图this.viewer.scene.screenSpaceCameraController.enableRotate = false;// 禁用鼠标右键缩放this.viewer.scene.screenSpaceCameraController.enableZoom = false;// 禁用中键平移this.viewer.scene.screenSpaceCameraController.enableTranslate = false;// 禁用倾斜this.viewer.scene.screenSpaceCameraController.enableTilt = false;// 禁用旋转this.viewer.scene.screenSpaceCameraController.enableLook = false;},// 恢复地图交互的方法enableMapInteraction() {// 恢复所有鼠标交互this.viewer.scene.screenSpaceCameraController.enableRotate = true;this.viewer.scene.screenSpaceCameraController.enableZoom = true;this.viewer.scene.screenSpaceCameraController.enableTranslate = true;this.viewer.scene.screenSpaceCameraController.enableTilt = true;this.viewer.scene.screenSpaceCameraController.enableLook = true;},// 保存点位置的方法(可根据实际需求实现)savePointPosition(longitude, latitude) {// 示例:可以将位置发送到服务器或保存到本地存储console.log(`保存点位置: 经度 ${longitude}, 纬度 ${latitude}`);// 实际应用中可能会调用API// fetch('/api/save-point', {//   method: 'POST',//   body: JSON.stringify({ longitude, latitude }),// });},},beforeDestroy() {// 组件销毁时释放资源if (this.viewer) {this.viewer.destroy();}},
};
</script><style lang="scss" scoped>
#cesiumContainer {width: 100%;height: 100vh;touch-action: none; /* 优化移动端交互 */position: relative; /* 确保提示框定位正确 */overflow: hidden;
}
.coordinate-tooltip {position: absolute;background-color: rgba(0, 0, 0, 0.7);color: white;padding: 8px 12px;border-radius: 4px;font-size: 12px;pointer-events: none; /* 确保鼠标事件能穿透提示框 */z-index: 1000; /* 确保提示框显示在最上层 */animation: fadeIn 0.3s ease-out; /* 淡入动画 */
}@keyframes fadeIn {from {opacity: 0;transform: translateY(10px);}to {opacity: 1;transform: translateY(0);}
}
</style>

总结

本文详细介绍了在 Vue 中使用 Cesium 实现可拖拽点标记及坐标实时显示的功能。通过 Primitive 方式创建点标记,结合 Cesium 的事件处理机制实现拖拽交互,并通过坐标转换实现提示框跟随效果。

这种实现方式具有较好的性能表现,适用于需要在地图上进行点位调整和标记的场景。你可以根据实际需求扩展功能,如添加拖拽范围限制、保存历史位置、添加更多样式的视觉反馈等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88204.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88204.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML 插件:构建网页的强大工具

HTML 插件:构建网页的强大工具 引言 HTML 插件是网页设计中不可或缺的一部分,它们为网页增添了丰富的交互性和动态效果。本文将深入探讨 HTML 插件的概念、类型、应用及其在网页开发中的重要性。 什么是 HTML 插件? HTML 插件,也称为 HTML 组件或 HTML 控件,是指嵌入到…

NeRF、3DGS、2DGS下三维重建相关方法介绍及以及在实景三维领域的最新实践

一、引言 在计算机视觉与图形学领域&#xff0c;三维重建技术正经历从传统几何建模向智能化神经表征的范式转变。近年来&#xff0c;随着深度学习算法的迭代、传感器技术的进步及计算硬件的升级&#xff0c;以神经辐射场&#xff08;NeRF&#xff09;和高斯泼溅&#xff08;2D…

rt thread studio 和 KEIL对于使用rt thread 的中间件和组件,哪个更方便

下面我从中间件/组件集成和开发体验两个角度&#xff0c;详细对比 RT-Thread Studio 和 Keil MDK 的便利性&#xff1a;1. 中间件和组件集成 RT-Thread Studio 集成RT-Thread生态&#xff1a;内置RT-Thread的包管理器&#xff08;RT-Thread Package Manager&#xff09;&#x…

Spring Boot 项目开发实战:入门应用部分原理示例讲解

前言Spring Boot 作为当前 Java 开发领域最流行的框架之一&#xff0c;以其 "约定优于配置" 的理念极大简化了企业级应用的开发流程。本文将基于《Spring Boot 项目开发教程&#xff08;慕课版&#xff09;》中的资产管理系统项目&#xff0c;深入解析 Spring Boot 的…

ByteBrain x 清华 VLDB25|时序多模态大语言模型 ChatTS

资料来源&#xff1a;火山引擎-开发者社区 近年来&#xff0c;多模态大语言模型&#xff08;MLLM&#xff09;发展迅速&#xff0c;并在图像、视频、音频等领域取得了突破性成果。然而&#xff0c;相较于这些研究较为成熟的模态&#xff0c;时间序列这一类型的数据与大模型结合…

WPF学习笔记(25)MVVM框架与项目实例

MVVM框架与项目实例一、MVVM框架1. 概述2. 核心组件与优势一、MVVM项目1.普通项目2. MVVM架构3. MVVM项目实例1. 项目准备2. LoginViewModel与Login2. MainWindowViewModel4. MVVM项目优化1. BaseViewModel2. RealyCommand3. 效果展示总结一、MVVM框架 1. 概述 官方文档&…

MySQL实操

## 基于MySQL#先启动MySQL服务#第一次登录[rootlocalhost ~]# mysql -uroot -P3306#密码登录[rootlocalhost ~]# mysql -uroot -pEnter password: Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 9Server version: 8.0.41 Source dist…

ez_rust_writeup

一道简单的[[rust逆向]] #rust逆向 #位运算 题目信息 文件名&#xff1a;ezrust.exe 题目附件&#xff1a;https://wwfj.lanzoul.com/iczMR30k5j4h 密码:bueq 题目分析 1. 初步分析 这是一道Rust编写的逆向题目。通过IDA分析可以看到&#xff0c;这是一个典型的flag验证程序。 …

【QT】-隐式转换 explicit用法

通俗易懂的解释:隐式转换 vs 显式转换 什么是隐式转换? 隐式转换就是编译器偷偷帮你做的类型转换,你甚至都没意识到它发生了。 例子: cpp 运行 double x = 5; // 隐式:int → double(5 变成 5.0) int y = x * 2.5; // 隐式:double → int(截断小数部分) 构造函数的隐…

Django核心知识点详解:JSON、AJAX、Cookie、Session与用户认证

1. JSON数据格式详解1.1 什么是JSON&#xff1f;JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;具有以下特点&#xff1a;独立于语言&#xff0c;几乎所有编程语言都支持易于人阅读和编写易于机器解析和生成基于文本&#xff…

[特殊字符] Python 实战 | 批量统计中文文档词频并导出 Excel

本文展示如何用 Python 脚本&#xff1a; 批量读取文件夹中的多篇中文文档&#xff1b; 用 jieba 分词并统计词频&#xff08;过滤停用词与单字符&#xff09;&#xff1b; 将各文档词频输出为对应 Excel 文件&#xff1b; 是文本分析、内容审查、报告编写中的实用技巧。 &…

共享打印机(详细操作+常见问题:需输入用户名密码、无法连接等)

文章目录一、设置打印机共享的准备工作二、Windows系统下打印机共享设置1. 启用主机打印机共享2. 客户端添加共享打印机三、我所遇到的问题及解决方法客户机遇到输入用户名、密码错误代码 0x0000011b一、错误代码 0x0000011b 的含义二、解决方法添加打印机没成功其他问题此次打…

在 Windows 系统上配置 [go-zero](https://go-zero.dev) 开发环境教程

&#x1f4bb; 在 Windows 系统上配置 go-zero 开发环境教程 本教程将详细介绍如何在 Windows 系统上配置 go-zero 微服务框架的开发环境&#xff0c;包括依赖安装、路径配置、常见问题等。 &#x1f9f1; 一、前置环境安装 1. 安装 Go 下载地址&#xff1a;https://go.dev/…

开源=白嫖?

国内有一个非常浓重的思想&#xff0c;开源&#xff0c;开源就是免费&#xff0c;就是白嫖&#xff0c;就是不花钱&#xff0c;白给。那么什么是开源&#xff1f;“源代码”是软件中大多数计算机用户从未见过的部分;它是计算机程序员可以操纵的代码&#xff0c;以改变一个软件(…

2048-控制台版本

2048控制台版 文章目录2048控制台版实现效果&#xff1a;在这里插入图片描述库函数使用&#xff1a;初始化变量功能函数实现&#xff1a;状态判断函数int Judge&#xff08;&#xff09;&#xff1b;数字生成函数 bool CtreateNumber&#xff08;&#xff09;打印游戏界面 void…

提取出Wallpaper Engine壁纸的mpkg类静态壁纸

github 地址 https://github.com/notscuffed/repkg先下载软件2853…26目录这样获取有的直接mp4格式&#xff0c;就不能用这方法准备好后 cmd 进入repkg目录 执行 repkg extract ./294...333/scene.pkg

AI健康小屋“15分钟服务圈”:如何重构社区健康生态?

AI健康小屋作为“15分钟服务圈”的核心载体&#xff0c;通过技术赋能与场景重构&#xff0c;正推动社区健康生态从被动治疗向主动预防、从单一服务向全周期管理转型。那我们应该如何重构社区健康生态呢&#xff1f;服务模式创新1.全时段覆盖AI健康小屋通过分时段服务满足不同群…

[netty5: WebSocketFrame]-源码分析

WebSocketFrame WebSocketFrame 是 Netty 中用于表示 WebSocket 消息帧的抽象基类&#xff0c;封装了帧的内容、分片标志和扩展位信息&#xff0c;供各类具体帧&#xff08;如文本、二进制、控制帧&#xff09;继承使用。 public abstract class WebSocketFrame extends Buffer…

【加解密与C】非对称加解密(三)ECC椭圆曲线

ECC椭圆曲线的基本概念椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff0c;ECC&#xff09;是一种基于椭圆曲线数学的公钥密码体制。与传统的RSA相比&#xff0c;ECC在相同安全级别下使用更短的密钥&#xff0c;计算效率更高&#xff0c;适用于资源受限的环境。…

力扣网编程150题:加油站(贪心解法)

一. 简介 前面一篇文章使用暴力解法来解决力扣网150 题目&#xff1a;加油站。文章如下&#xff1a; 力扣网编程150题&#xff1a;加油站&#xff08;暴力解法&#xff09;-CSDN博客 暴力解法就是遍历了所有元素作为起始点的可能&#xff0c;算法时间复杂度为 O(n*n)&#x…