在 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/88685.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88685.shtml
英文地址,请注明出处:http://en.pswp.cn/web/88685.shtml

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

相关文章

Anthropic:从OpenAI分支到AI领域的领军者

自2021年由前OpenAI高管Dario和Daniela Amodei创立以来&#xff0c;Anthropic已迅速崛起为人工智能&#xff08;AI&#xff09;领域的重要力量。 公司专注于开发安全、可控且具备深度推理能力的AI系统&#xff0c;其Claude系列模型在生成式AI领域取得了显著成就。 此外&#xf…

前端开发中的输出问题

前端开发中的输出问题&#xff1a;console.log输出[object Object]在前端开发中&#xff0c;一个常见问题是使用console.log输出对象时显示为[object Object]&#xff0c;而不是对象的详细内容。这通常发生在开发者试图直接打印对象时&#xff0c;浏览器默认只显示对象的字符串…

DSSA(Domain-Specific Software Architecture)特定领域架构

DSSA&#xff08;Domain-Specific Software Architecture&#xff09; 定义&#xff1a;针对特定应用领域设计的可复用软件架构&#xff0c;为领域内产品族提供统一基础。 目标&#xff1a; ✅ 最大化复用&#xff08;需求/设计/代码&#xff09;✅ 保证系统一致性✅ 降低开发成…

单调栈单调队列【算法进阶】

这周学完之后最大的收获就是单调栈和单调队列了&#xff01;&#xff01;&#xff01;感觉好厉害能把时间复杂度瞬间压缩为O(N)&#xff0c;不行我必须再纪念一下这么美妙的算法&#xff01;&#xff01;&#xff01; 单调栈问题&#xff1a; 如果题目要求一个元素左边或右边…

C++编程基础

编程题一问题分析 题目要求使用 n 根小木棒&#xff0c;按照特定的方式排列&#xff0c;形成一个数字。具体规则如下&#xff1a; 每个数字由小木棒组成&#xff0c;例如&#xff1a; 1 需要 2 根小木棒。0 需要 6 根小木棒。其他数字&#xff08;如 2, 3, 4, 5, 6, 7, 8, 9&am…

张量拼接操作

一.前言本章节来介绍一下张量拼接的操作&#xff0c;掌握torch.cat torch.stack使⽤&#xff0c;张量的拼接操作在神经⽹络搭建过程中是⾮常常⽤的⽅法&#xff0c;例如: 在后⾯将要学习到的残差⽹络、注意⼒机 制中都使⽤到了张量拼接。二.torch.cat 函数的使用torch.cat 函数…

Dify 连接本地 SpringAI MCP Server

Dify 连接本地 SpringAI MCP server 连接 MCP server 的方式大致有两种&#xff0c;一种是基于 stdio&#xff0c;一种是基于 sse&#xff0c;如果对于稳定和性能好的方案的话&#xff0c;sse 要比 stdio 好的多&#xff0c;所以本文采用的是基于 sse 和 Spring AI 部署本地 MC…

基于 Python 的数据分析技术综述

先说一点个人的看法“”MDX、OLAP&#xff08;Mondrian&#xff09;技术更适合构建面向业务用户的标准化分析产品&#xff0c;尤其当产品需要满足以下特点时&#xff1a;分析维度固定&#xff08;如时间、区域、产品类别&#xff09;&#xff1b;需支持高并发查询&#xff08;如…

Live555-RTSP服务器

RTSP Server创建 RTSP服务器初始化&#xff1a; RTSPServer::createNew->new RTSPServer::RTSPServer->GenericMediaServer::GenericMediaServer->turnOnBackgroundReadHandling(IPV4sock/IPV6sock,incomingConnectionHandlerIPv4)如上流程&#xff0c;创建RTSP服务器…

Redis Stack扩展功能

Redis JSONRedisJSON是Redis的一个扩展模块&#xff0c;它提供了对JSON数据的原生支持。常用操作&#xff1a;-- 设置一个JSON数据JSON.SET user $ {"name":"loulan","age":18}## key是user&#xff0c;value就是一个JSON数据。其中$表示JSON数据…

Takebishi旗下智能硬件网关产品devicegateway详细介绍

一、产品概述 DeviceGateway是由日本Takebishi公司研发的一款专业工业物联网&#xff08;IIoT&#xff09;硬件网关产品&#xff0c;专为实现现场工业设备与云端平台、IT系统之间的高效、安全数据传输而设计。作为一款可靠的硬件网关&#xff0c;DeviceGateway具有即插即用、稳…

单向链表反转 如何实现

单向链表反转的实现方法 ​ https://www.zhihu.com/question/441865393/answer/3208578798 ​ 单向链表反转是数据结构中的经典问题&#xff0c;在面试和实际开发中经常遇到。以下是 多种实现方式&#xff08;包括递归和迭代&#xff09;&#xff0c;以 Go 语言为例。1. 单向链…

php+vue+Laravel音乐媒体播放及周边产品运营平台-nodejs-计算机毕业设计

目录具体实现截图课程项目技术路线开发技术介绍设计思路流程PHP核心代码部分展示详细视频演示/源码获取##项目介绍网络技术的广泛应用显著地推动了生活服务的信息化进程。结合音乐流媒体与周边产品的运营需求&#xff0c;构建一套音乐媒体播放及周边产品运营平台&#xff0c;成…

Python爬虫实战:研究xlwt 和 xlrd 库相关技术

1. 引言 1.1 研究背景与意义 随着电子商务的快速发展,电商平台积累了海量的商品数据。如何从这些数据中提取有价值的信息,为商家提供决策支持,成为电商领域的重要研究方向。传统人工采集和分析数据的方式效率低下,且容易出现错误。自动化数据采集与分析系统能够通过爬虫技…

【QGC】深入解析 QGC 配置管理

引言 在软件开发中&#xff0c;配置管理是一项至关重要的任务&#xff0c;它能帮助我们灵活地管理应用程序的各种参数和设置。QGroundControl&#xff08;QGC&#xff09;作为一款强大的开源无人机地面站软件&#xff0c;其配置管理系统设计精巧&#xff0c;值得我们深入学习。…

ChatGPT,从规则到强化学习

要了解 ChatGPT&#xff08;Chat Generative Pre-training Transformer&#xff09;&#xff0c;我们不得不先看看 NLP 自然语言处理&#xff08;Natural Language Processing&#xff09;。因为 ChatGPT 属于 NLP 领域&#xff0c;而 NLP 则又是人工智能的一个分支。 那么什么…

【目标检测之Ultralytics预测框颜色修改】

在 Ultralytics YOLOv8 中修改预测框颜色为红色&#xff0c;以下是三种实用方案&#xff1a;方案 1&#xff1a;直接修改 plot() 方法的 colors 参数 在调用 results.plot() 时直接指定颜色参数&#xff1a; from ultralytics import YOLO# 加载模型 model YOLO("yolov8n…

让 VSCode 调试器像 PyCharm 一样显示 Tensor Shape、变量形状、变量长度、维度信息

文章目录&#x1f3af; 目标&#xff1a;在 VS Code 调试器中自动显示这些变量信息&#x1f50d; 原理简介⚠️ 其他方案的局限性❌ 方案一&#xff1a;重写 __repr__❌ 方案二&#xff1a;向 debugpy 注册自定义变量显示器&#xff08;StrPresentationProvider&#xff09;✅ …

pip国内镜像源一览

以下是2025年主流pip国内镜像源完整清单及配置指南&#xff0c;综合多个权威来源整理的最新数据&#xff1a;一、核心镜像源推荐&#xff08;2025年稳定可用&#xff09;‌阿里云镜像‌https://mirrors.aliyun.com/pypi/simple/优势&#xff1a;依托阿里云CDN&#xff0c;全国平…

当大模型遇见毫米波:用Wi-Fi信号做“透视”的室内语义SLAM实践——从CSI到神经辐射场的端到端开源方案

作者 | Blossom.118 2025-07-12 关键词&#xff1a;CSI-SLAM、神经辐射场、毫米波、Transformer、数字孪生、开源 ---- 1. 为什么要“无摄像头”语义SLAM&#xff1f; • 隐私红线&#xff1a;欧盟GDPR 2024修订版把“摄像头点云”列入高风险生物特征&#xff0c;落地成本高。…