5、Vue中使用Cesium实现交互式折线绘制详解

引言

Cesium是一款强大的开源3D地理信息可视化引擎,广泛应用于数字地球、地图可视化等领域。在Vue项目中集成Cesium可以快速构建高性能的地理信息应用。本文将详细介绍如何在Vue项目中实现交互式折线绘制功能,包括顶点添加、临时绘制、距离计算等核心功能,并为新手提供详细的代码注释和学习资源。

Cesium核心概念速览

在开始之前,我们先了解几个Cesium的核心概念:

  • Viewer:Cesium的核心实例,用于创建和管理3D场景
  • Entity:高层次的对象封装,用于创建和管理可视化对象(如点、线、面)
  • Primitive:低层次的渲染对象,比Entity更高效,适合大量数据渲染
  • Cartesian3:三维笛卡尔坐标,Cesium中表示位置的基本方式
  • ScreenSpaceEventHandler:用于处理用户输入事件(如点击、鼠标移动)

环境准备

假设已完成Cesium 2D地图初始化,需要安装以下依赖:

npm install cesium lodash

核心功能实现

折线绘制的核心流程如下:

  1. 点击地图添加顶点
  2. 鼠标移动时更新临时折线
  3. 双击结束绘制并保存折线
  4. 右键取消绘制

代码解析

1. 数据属性定义

data() {return {viewer: null, // Cesium Viewer实例isDrawing: false, // 是否处于绘制状态currentPositions: [], // 当前折线的顶点坐标数组tempPrimitive: null, // 临时折线Primitive对象allPolylines: [], // 保存所有已绘制的折线handler: null, // 屏幕空间事件处理器isFirstClick: true, // 是否是首次点击(用于开始绘制)vertexMarkers: [], // 顶点标记Entity数组};
}

2. 初始化事件处理器

initDrawLine() {// 创建屏幕空间事件处理器,用于监听用户在地图上的交互this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);// 移除默认的双击事件,避免与自定义双击结束绘制冲突this.viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);// 显示操作提示this.showToast('点击添加顶点,双击结束绘制(至少需要3个顶点)');// 绑定左键单击事件 - 添加折线顶点this.handler.setInputAction((event) => {// 禁用地图交互,防止绘制时误操作(如旋转、缩放地图)this.disableMapInteraction();// 将鼠标点击位置转换为地球表面坐标const position = this.getPositionFromMouse(event.position);if (!position) return;// 检测与上一顶点的距离,避免过近的重复顶点if (this.currentPositions.length > 0) {const lastPosition = this.currentPositions[this.currentPositions.length - 1];const distance = Cesium.Cartesian3.distance(position, lastPosition);const DISTANCE_THRESHOLD = 1.0; // 距离阈值(米)if (distance < DISTANCE_THRESHOLD) {this.showToast(`点击位置与上一顶点距离过近(${distance.toFixed(2)}米),已忽略`);this.enableMapInteraction(); // 重新启用地图交互return;}}// 首次点击时标记开始绘制if (this.isFirstClick) {this.isDrawing = true;this.isFirstClick = false;}// 添加顶点坐标并显示标记this.currentPositions.push(position);this.addVertexMarker(position);// 绘制临时折线(此时鼠标未移动,传入null)this.drawTempLine(null);}, Cesium.ScreenSpaceEventType.LEFT_DOWN);// 鼠标移动 - 更新临时折线this.handler.setInputAction((event) => {if (!this.isDrawing || this.currentPositions.length === 0) return;const position = this.getPositionFromMouse(event.endPosition);if (position) {this.throttledDrawTempLine(position); // 使用节流优化性能}}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);// 左键双击 - 结束绘制this.handler.setInputAction((event) => {if (!this.isDrawing) return;const position = this.getPositionFromMouse(event.position);if (position) {this.currentPositions.push(position);this.addVertexMarker(position);}// 验证顶点数量,至少需要3个顶点才能形成闭合区域if (this.currentPositions.length < 3) {this.showToast('折线至少需要3个顶点,请继续添加');return;}this.savePolyline(); // 保存折线this.clearTempLine(); // 清除临时折线this.resetDrawingState(); // 重置绘制状态this.enableMapInteraction(); // 重新启用地图交互}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);// 右键单击 - 取消绘制this.handler.setInputAction(() => {if (this.isDrawing) {this.clearTempLine();this.resetDrawingState();this.enableMapInteraction();this.showToast('已取消绘制');}}, Cesium.ScreenSpaceEventType.RIGHT_DOWN);

3. 坐标转换

getPositionFromMouse(mousePosition) {// 创建从相机到鼠标位置的射线const ray = this.viewer.camera.getPickRay(mousePosition);if (!ray) return null;// 计算射线与地球表面的交点(获取地理坐标)const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);if (!position) {this.showToast('请在地球表面点击');}return position;
}

4. 临时折线绘制

drawTempLine(currentMousePosition) {this.clearTempLine(); // 清除已有临时折线if (this.currentPositions.length === 0 || !currentMousePosition) return;// 创建包含已有顶点和当前鼠标位置的临时坐标数组const tempPositions = [...this.currentPositions, currentMousePosition];// 创建临时折线Primitivethis.tempPrimitive = new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.PolylineGeometry({positions: tempPositions, // 折线顶点坐标width: 5, // 折线宽度vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT,}),}),appearance: new Cesium.PolylineMaterialAppearance({material: Cesium.Material.fromType('Color', {color: Cesium.Color.RED.withAlpha(0.8), // 临时折线为半透明红色}),}),});// 将临时折线添加到场景中this.viewer.scene.primitives.add(this.tempPrimitive);

5. 折线保存与长度计算

savePolyline() {// 创建最终折线Primitiveconst polyline = new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.PolylineGeometry({positions: this.currentPositions,width: 5,vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT,}),}),appearance: new Cesium.PolylineMaterialAppearance({material: Cesium.Material.fromType('Color', {color: Cesium.Color.BLUE.withAlpha(0.8), // 最终折线为半透明蓝色}),}),});// 添加到场景并保存引用this.viewer.scene.primitives.add(polyline);this.allPolylines.push(polyline);// 计算并显示折线总长度const totalLength = this.calculatePolylineLength(this.currentPositions);this.showToast(`折线绘制完成!顶点数: ${this.currentPositions.length}, 总长度: ${totalLength.toFixed(2)}米`);
}// 计算折线总长度
calculatePolylineLength(positions) {let totalLength = 0;// 遍历所有顶点,累加相邻顶点间的距离for (let i = 0; i < positions.length - 1; i++) {// 使用Cesium提供的Cartesian3距离计算方法,单位为米totalLength += Cesium.Cartesian3.distance(positions[i], positions[i + 1]);}return totalLength;
}

性能优化

  1. 节流处理:使用Lodash的throttle函数限制鼠标移动时的重绘频率
created() {// 节流处理临时绘制,50ms内最多执行一次,优化性能this.throttledDrawTempLine = throttle(this.drawTempLine, 50);
}
  1. 顶点去重:通过距离检测避免添加过近的重复顶点

  2. 资源销毁:组件销毁时清理Cesium资源,避免内存泄漏

beforeDestroy() {if (this.viewer) this.viewer.destroy(); // 销毁Viewer实例if (this.handler) this.handler.destroy(); // 销毁事件处理器
}

常见问题与调试

  1. 地图初始化失败:检查Cesium资源是否正确加载,确保API密钥有效
  2. 坐标获取不到:确保点击位置在地球表面,而非天空盒
  3. 折线不显示:检查坐标数组是否为空,材质颜色是否可见
  4. 性能问题:使用viewer.scene.debugShowFramesPerSecond = true监控帧率

扩展功能建议

  1. 折线编辑:添加顶点拖拽、删除功能
  2. 样式自定义:允许用户修改折线颜色、宽度、材质
  3. 数据导出:将折线坐标导出为GeoJSON或其他格式
  4. 面积计算:对于闭合折线,添加面积计算功能

学习资源汇总

  1. Cesium官方文档:Index - Cesium Documentation
  2. Cesium Sandcastle示例:Cesium Sandcastle
  3. Vue-Cesium组件库:A Vue 3 based component library of CesiumJS for developers | Vue for Cesium
  4. Lodash文档:Lodash Documentation
  5. Cesium中文社区:https://cesiumcn.org/

完整代码(带详细备注)

<template><div id="cesiumContainer" style="width: 100%; height: 100vh"></div>
</template><script>
// 导入地图初始化配置和工具函数
import initMap from '@/config/initMap.js'; // 地图初始化函数
import { mapConfig } from '@/config/mapConfig'; // 地图配置项(包含高德地图URL等)
import { throttle } from 'lodash'; // 导入节流函数用于性能优化export default {data() {return {viewer: null, // Cesium Viewer实例,地图的核心控制器isDrawing: false, // 绘制状态标志:是否正在绘制折线currentPositions: [], // 存储当前折线的顶点坐标数组(Cartesian3类型)tempPrimitive: null, // 临时折线的Primitive对象,随鼠标移动更新allPolylines: [], // 存储所有已完成绘制的折线对象handler: null, // 屏幕空间事件处理器,用于监听鼠标交互isFirstClick: true, // 首次点击标志:用于判断是否开始绘制vertexMarkers: [], // 存储顶点标记的Entity对象数组};},created() {// 节流处理临时绘制函数,限制50ms内最多执行一次,优化鼠标移动时的性能this.throttledDrawTempLine = throttle(this.drawTempLine, 50);},mounted() {// 初始化Cesium地图,使用高德地图瓦片服务// initMap参数:地图瓦片URL,是否开启3D模式(false表示2D)this.viewer = initMap(mapConfig.gaode.url3, false);// 初始化折线绘制功能this.initDrawLine();},methods: {/*** 初始化折线绘制相关的事件处理器* 绑定鼠标点击、移动、双击等事件,实现交互式绘制逻辑*/initDrawLine() {// 创建屏幕空间事件处理器,监听canvas上的鼠标事件this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.canvas);// 移除Cesium默认的左键双击事件(默认是放大地图),避免与我们的双击结束绘制冲突this.viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);// 显示操作提示信息this.showToast('点击添加顶点,双击结束绘制(至少需要3个顶点)');// 绑定左键单击事件 - 添加折线顶点this.handler.setInputAction((event) => {// 绘制过程中禁用地图默认交互(旋转、缩放等),防止误操作this.disableMapInteraction();// 将鼠标点击位置转换为地球表面的地理坐标(Cartesian3)const position = this.getPositionFromMouse(event.position);if (!position) return; // 如果获取坐标失败,直接返回// 重复顶点检测:如果不是第一个顶点,检查与上一个顶点的距离if (this.currentPositions.length > 0) {const lastPosition =this.currentPositions[this.currentPositions.length - 1];// 计算两点之间的直线距离(单位:米)const distance = Cesium.Cartesian3.distance(position, lastPosition);const DISTANCE_THRESHOLD = 1.0; // 距离阈值(米),可根据需求调整// 如果距离小于阈值,忽略本次点击if (distance < DISTANCE_THRESHOLD) {this.showToast(`点击位置与上一顶点距离过近(${distance.toFixed(2)}米),已忽略`);this.enableMapInteraction(); // 重新启用地图交互return;}}// 首次点击时,标记开始绘制状态if (this.isFirstClick) {this.isDrawing = true;this.isFirstClick = false;}// 添加顶点坐标到数组,并在地图上显示顶点标记this.currentPositions.push(position);this.addVertexMarker(position);// 绘制临时折线(此时鼠标未移动,传入null)this.drawTempLine(null);}, Cesium.ScreenSpaceEventType.LEFT_DOWN);// 绑定鼠标移动事件 - 更新临时折线this.handler.setInputAction((event) => {// 只有在绘制状态且已有顶点时才更新临时折线if (!this.isDrawing || this.currentPositions.length === 0) return;// 获取鼠标当前位置对应的地理坐标const position = this.getPositionFromMouse(event.endPosition);if (position) {// 使用节流后的方法更新临时折线this.throttledDrawTempLine(position);}}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);// 绑定左键双击事件 - 结束折线绘制this.handler.setInputAction((event) => {if (!this.isDrawing) return; // 非绘制状态不处理// 获取双击位置的坐标并添加为最后一个顶点const position = this.getPositionFromMouse(event.position);if (position) {this.currentPositions.push(position);this.addVertexMarker(position);}// 验证顶点数量,至少需要3个顶点才能形成有效的折线if (this.currentPositions.length < 3) {this.showToast('折线至少需要3个顶点,请继续添加');return;}// 保存最终折线、清理临时对象、重置状态、恢复地图交互this.savePolyline();this.clearTempLine();this.resetDrawingState();this.enableMapInteraction();}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);// 绑定右键单击事件 - 取消绘制this.handler.setInputAction(() => {if (this.isDrawing) {this.clearTempLine(); // 清除临时折线this.resetDrawingState(); // 重置绘制状态this.enableMapInteraction(); // 恢复地图交互this.showToast('已取消绘制');}}, Cesium.ScreenSpaceEventType.RIGHT_DOWN);},/*** 将鼠标屏幕坐标转换为地球表面的地理坐标* @param {Cesium.Cartesian2} mousePosition - 鼠标屏幕坐标* @returns {Cesium.Cartesian3|null} 地球表面的地理坐标,失败时返回null*/getPositionFromMouse(mousePosition) {// 创建从相机位置到鼠标位置的射线const ray = this.viewer.camera.getPickRay(mousePosition);if (!ray || !this.viewer.scene) return null;// 计算射线与地球表面的交点(获取地理坐标)const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);if (!position) {this.showToast('请在地球表面点击'); // 如果点击了天空盒等非地球表面位置}return position;},/*** 绘制临时折线(随鼠标移动更新)* @param {Cesium.Cartesian3} currentMousePosition - 当前鼠标位置的地理坐标*/drawTempLine(currentMousePosition) {this.clearTempLine(); // 先清除已有的临时折线// 如果没有顶点或鼠标位置无效,不绘制if (this.currentPositions.length === 0 || !currentMousePosition) return;// 创建临时坐标数组:已有顶点 + 当前鼠标位置const tempPositions = [...this.currentPositions, currentMousePosition];// 创建临时折线Primitivethis.tempPrimitive = new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.PolylineGeometry({positions: tempPositions, // 折线顶点坐标数组width: 5, // 折线宽度(像素)vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT, // 指定顶点格式}),}),appearance: new Cesium.PolylineMaterialAppearance({material: Cesium.Material.fromType('Color', {color: Cesium.Color.RED.withAlpha(0.8), // 临时折线为半透明红色}),}),});// 将临时折线添加到场景中显示this.viewer.scene.primitives.add(this.tempPrimitive);},/*** 清除临时折线*/clearTempLine() {if (this.tempPrimitive) {// 从场景中移除临时折线并释放资源this.viewer.scene.primitives.remove(this.tempPrimitive);this.tempPrimitive = null;}},/*** 保存最终绘制的折线*/savePolyline() {// 创建最终折线Primitiveconst polyline = new Cesium.Primitive({geometryInstances: new Cesium.GeometryInstance({geometry: new Cesium.PolylineGeometry({positions: this.currentPositions, // 使用当前所有顶点坐标width: 5,vertexFormat: Cesium.PolylineMaterialAppearance.VERTEX_FORMAT,}),}),appearance: new Cesium.PolylineMaterialAppearance({material: Cesium.Material.fromType('Color', {color: Cesium.Color.BLUE.withAlpha(0.8), // 最终折线为半透明蓝色}),}),});// 添加到场景并保存引用this.viewer.scene.primitives.add(polyline);this.allPolylines.push(polyline);// 计算并显示折线总长度const totalLength = this.calculatePolylineLength(this.currentPositions);this.showToast(`折线绘制完成!顶点数: ${this.currentPositions.length}, 总长度: ${totalLength.toFixed(2)}米`);},/*** 计算折线总长度* @param {Cesium.Cartesian3[]} positions - 折线顶点坐标数组* @returns {number} 折线总长度(米)*/calculatePolylineLength(positions) {let totalLength = 0;// 遍历所有相邻顶点对,累加距离for (let i = 0; i < positions.length - 1; i++) {totalLength += Cesium.Cartesian3.distance(positions[i],positions[i + 1]);}return totalLength;},/*** 在地图上添加顶点标记* @param {Cesium.Cartesian3} position - 顶点坐标*/addVertexMarker(position) {const marker = this.viewer.entities.add({position: position,point: {pixelSize: 8, // 像素大小color: Cesium.Color.YELLOW, // 黄色标记outlineColor: Cesium.Color.BLACK, // 黑色轮廓outlineWidth: 2, // 轮廓宽度heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, // 贴地显示},});this.vertexMarkers.push(marker); // 保存标记引用,便于后续清理},/*** 重置绘制状态*/resetDrawingState() {this.isDrawing = false; // 退出绘制状态this.isFirstClick = true; // 重置首次点击标志this.currentPositions = []; // 清空顶点数组// 如需保留顶点标记,注释掉下面两行// this.vertexMarkers.forEach(marker => this.viewer.entities.remove(marker));// this.vertexMarkers = [];},/*** 显示临时提示信息* @param {string} message - 提示文本*/showToast(message) {const toast = document.createElement('div');// 设置提示框样式toast.style.cssText = `position: absolute;bottom: 20px;left: 50%;transform: translateX(-50%);padding: 8px 16px;background-color: rgba(0, 0, 0, 0.7);color: white;border-radius: 4px;font-size: 14px;z-index: 9999;`;toast.textContent = message;document.body.appendChild(toast);// 3秒后自动移除提示框setTimeout(() => document.body.removeChild(toast), 3000);},/*** 禁用地图交互(旋转、缩放等)*/disableMapInteraction() {const controller = this.viewer.scene.screenSpaceCameraController;controller.enableRotate = false; // 禁用旋转controller.enableZoom = false; // 禁用缩放controller.enableTranslate = false; // 禁用平移controller.enableTilt = false; // 禁用倾斜controller.enableLook = false; // 禁用环顾},/*** 启用地图交互*/enableMapInteraction() {const controller = this.viewer.scene.screenSpaceCameraController;controller.enableRotate = true;controller.enableZoom = true;controller.enableTranslate = true;controller.enableTilt = true;controller.enableLook = true;},},/*** 组件销毁前清理资源*/beforeDestroy() {if (this.viewer) this.viewer.destroy(); // 销毁Cesium Viewer实例if (this.handler) this.handler.destroy(); // 销毁事件处理器},
};
</script><style lang="scss" scoped>
#cesiumContainer {width: 100%;height: 100vh;touch-action: none;
}
</style>

点点关注,有需求或问题可以在评论留言

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

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

相关文章

mysql实战之主从复制

原理图理论&#xff1a;一、配置准备每台主机都安装mysql对每台主机都进行对时操作&#xff0c;减少时间误差[rooteveryone ~]# timedatectl set-timezone Asia/Shanghai [rooteveryone ~]# systemctl restart chronyd.service 对每台主机都进行关闭防火墙、上下文等&#xff0…

中望CAD2026亮点速递(5):【相似查找】高效自动化识别定位

本文为CAD芯智库整理&#xff0c;未经允许请勿复制、转载&#xff01;原文转自&#xff1a;www.xwzsoft.com/h-nd-594.html CAD的相似查找功能主要应用于需要重复操作、标准化控制、一致性检查或复杂模式识别的场景&#xff0c;通过图形相似度算法&#xff0c;快速找到匹配的图…

国产化条码类库Spire.Barcode教程:使用 C# 读取二维码(QR Code)——从图片或数据流解析

二维码已成为现代应用的常见组成部分&#xff0c;广泛应用于用户身份验证、移动支付、商品包装和活动票务等场景。很多使用 C# 开发的系统需要从图像或扫描件中提取二维码信息&#xff0c;因此掌握二维码识别技术显得尤为重要。 为满足这类需求&#xff0c;开发者需要一种既可…

IPSAN 共享存储详解:架构、优化与落地实践指南

一、IPSAN 技术定位与核心价值核心价值对比矩阵&#xff1a;维度IPSANFC-SAN实现方案成本端口成本$500端口成本$2000复用IP网络设备传输距离跨地域&#xff08;VPN/专线&#xff09;≤10公里两地三中心架构运维效率SNMP/CLI管理Zone/ALPA管理自动化运维工具链协议标准IETF RFC …

【卫星语音】基于神经网络的低码率语音编解码(ULBC)方案架构分析:以SoundStream为例

摘要 随着深度学习技术的快速发展&#xff0c;基于神经网络的音频编解码技术已成为下一代音频压缩的重要研究方向。本文以Google提出的SoundStream为核心分析对象&#xff0c;深入探讨其在低码率语音编解码领域的创新架构设计和关键技术突破。SoundStream通过全卷积编解码器网络…

技术面试问题总结一

MySQL的几种锁机制一、从锁的粒度角度划分表级锁机制&#xff1a;它是对整张表进行锁定的一种锁。当一个事务对表执行写操作时&#xff0c;会获取写锁&#xff0c;在写锁持有期间&#xff0c;其他事务无法对该表进行读写操作&#xff1b;而当事务执行读操作时&#xff0c;会获取…

Python(一)

基本语法&#xff1a;变量&#xff0c;语法变量类型&#xff1a;不同于Java&#xff0c;C语言&#xff0c;C&#xff0c;Python在创建一个变量的时候&#xff0c;不需要声明变量类型&#xff0c;由编译器自行识别Python语句在只有一个语句的时候语句末尾不需要分号&#xff0c;…

Adaptive AUTOSAR中的Firewall技术:智能汽车网络安全架构的核心

1 防火墙技术基础 1.1 定义与演进历程 防火墙(Firewall)作为一种位于内部网络与外部网络之间的网络安全系统,本质上是依照特定规则允许或限制数据传输的信息安全防护机制。在汽车电子电气架构从分布式向集中式转变的背景下,防火墙技术已从传统的IT领域深度融入Adaptive A…

android闪光灯源码分析

目录 一、APP层源码分析 二&#xff0c;framework层代码分析 ​​​​​​​2.1 binder溯源 这几天撸了android11 aosp闪光灯源码&#xff0c;本着前人栽树后人乘凉的原则&#xff0c;有志于android系统开发的新同学们提供一盏明灯&#xff0c;照亮你们前行。 本人撸代码风格&…

文心一言4.5开源部署指南及文学领域测评

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、引言 二、文心一言开源模型 2.1 MoE架构 2.2 文心一言MoE架构 三、文心一言稠密模型部署 3.1 产品选择 3.2 环境选择 3.3 Python3.12安装 3.3 PaddlePaddle-GPU安装 3.4 FastDeploy-GPU安装 ​编辑3.…

深入探讨 C++ 中的浮点数数据类型

核心概念&#xff1a;IEEE 754 标准 C 中的浮点数&#xff08;float, double, long double&#xff09;在绝大多数现代系统上遵循 IEEE 754 标准。这个标准定义了浮点数在内存中的二进制表示方式、运算规则、特殊值&#xff08;如无穷大、NaN&#xff09;等。数据类型与精度 fl…

相机:以鼠标点为中心缩放(使用OpenGL+QT开发三维CAD)

很多软件中&#xff08;Auto CAD、ODA等&#xff09;支持以鼠标点为中心进行放缩操作&#xff0c;有什么黑科技吗&#xff1f; 本章节为相机原理和实现的补充内容&#xff0c;支持鼠标放缩时以鼠标点为中心进行放缩。 对应视频课程已上线&#xff0c;欢迎观看和支持~ https:…

​​XAMPP安全升级指南:修复CVE-2024-4577漏洞,从PHP 8.2.12升级至PHP 8.4.10​​

​​1. 背景与漏洞概述​​ 近期,PHP官方披露了一个高危漏洞 ​​CVE-2024-4577​​,该漏洞影响PHP 8.2.x及更早版本,可能导致远程代码执行(RCE)或信息泄露。由于XAMPP默认捆绑的PHP版本(如8.2.12)可能受此漏洞影响,建议用户尽快升级至最新的​​PHP 8.4.10​​(或官…

ES 压缩包安装

以下是 Elasticsearch (ES) 通过 .tar.gz 压缩包安装的详细步骤&#xff08;适用于 Linux/macOS 系统&#xff09;&#xff1a; 1. 准备工作 1.1 检查系统依赖 Java 环境&#xff1a;ES 需要 JDK&#xff0c;推荐 OpenJDK 11/17&#xff08;ES 7.x/8.x 兼容版本&#xff09;。…

RoboRefer:面向机器人视觉-语言模型推理的空间参考

25年6月来自北航、北大和北京智源的论文“RoboRefer: Towards Spatial Referring with Reasoning in Vision-Language Models for Robotics”。 空间参考是实体机器人与三维物理世界交互的基本能力。然而&#xff0c;即使有了强大的预训练视觉-语言模型 (VLM)&#xff0c;近期方…

【Unity】MiniGame编辑器小游戏(十)连连看【Link】

更新日期:2025年7月9日。 项目源码:获取项目源码 索引 连连看【Link】一、游戏最终效果二、玩法简介三、正式开始1.定义游戏窗口类2.规划游戏窗口、视口区域3.方块 Block①.定义方块类②.生成方块所有类型③.生成连连看棋盘④.绘制方块阵列4.连线 Line①.点击方块连线②.尝试…

Enable ADB Debugging Before Connect

If you don’t enable Developer Options and turn on USB Debugging before plugging in the cable, adb devices won’t detect the phone because the Android system doesn’t trust the connection yet. Here’s what you need to do step-by-step to fix this:✅ 1. Enab…

从互联网电脑迁移Dify到内网部署Dify方法记录

一、在互联网电脑上准备迁移文件1. 保存 Docker 镜像# 获取所有 Dify 相关镜像&#xff08;根据实际容器名调整&#xff09; docker ps --filter "namedify" --format "{{.Image}}" | sort -u > dify-images.list# 保存镜像为 .tar 文件 docker save $(…

【EGSR2025】材质+扩散模型+神经网络相关论文整理随笔(一)

MatSwap: Light-aware material transfers in images介绍任务&#xff1a;输入一张拍摄图像、示例材质纹理图像&#xff08;这里跟BRDF无关&#xff0c;通常我们讲到材质一般指的是SVBRDF&#xff0c;但是这里的材质指的只是纹理&#xff09;、用户为拍摄图像指定的遮罩区域&am…

饿了么el-upload上传组件报错:TypeError: ***.upload.addEventListener is not a function

在本地上传没有报这个错误&#xff0c;部署到服务器后会报这个错误&#xff0c;一开始以为是服务器配置等什么原因&#xff0c;但是一想这个报错应该还是在前端&#xff0c;接口都还没请求&#xff0c;不可能到后台去&#xff0c;后面搜了好几个AI也没有找到想要的答案或解决方…