cesium量测工具:基于开源cesium-measure.js 改造

本文介绍如何使用cesium-measure插件为Cesium项目添加距离、面积和三角测量等功能。通过ES6改造并优化了插件源码,实现直观易用的量测工具。

1、项目场景

实际开发中我们经常需要用到量测工具,而Cesium没有直接提供量测功能,怎么样基于Cesium实现量测功能,值得我们研究一波。


2、解决方案

在github发现一个还不错的一款cesium测量插件:cesium-measure。能够实现对空间距离, 空间面积和三角量测。在源码的基础上,以ES6方式进行改造,并进行优化,实现了量测功能。


3、编码实现

/**
 * 添加量测工具
 */
function addMeasureTool() {
  //   let measureTool = new Cesium.Measure(viewer)
  let measureTool = new MeasureTool(viewer)
  let clampToGround = true
  Sandcastle.addToolbarMenu([
    {
      text: '距离测量',
      onselect: function () {
        measureTool._drawLayer.entities.removeAll()

        // let drawLine = function () {
        //   measureTool.drawLineMeasureGraphics({
        //     clampToGround: clampToGround,
        //     callback: () => {
        //       drawLine()
        //     }
        //   })
        // }

        // drawLine(
<think>我们遇到了一个错误:`Cesium.Compass is not a constructor`。这意味着在当前的Cesium版本中,`Compass`可能不是直接暴露的构造函数。在Cesium中,指南针控件通常是通过`NavigationHelpButton`提供的,但也可以单独使用。不过,在较新的Cesium版本中,可能需要通过`viewer.entities`或其他方式添加控件,或者使用`viewer.extend`来扩展。 实际上,Cesium的UI控件(如指南针)通常是通过`Cesium.viewer`的`toolbar`来添加的。我们可以通过查看Cesium的官方文档来确认如何正确使用指南针控件。 然而,根据Cesium的API文档,`Compass`类确实存在于`Cesium`命名空间中,但可能由于版本问题或引入方式不同而不可用。我们可以尝试另一种方式:使用`viewer.scene`的`screenSpaceCameraController`来控制方向,但这不是一个可见的控件。 另一种可能性是,我们可能没有正确导入Cesium的Widget模块。在Cesium中,控件(如Compass)属于Widgets,可能需要单独引入。 但是,在Cesium的官方示例中,通常是这样创建指南针的: ```javascript var viewer = new Cesium.Viewer('cesiumContainer'); // 获取viewer自带的指南针控件 var compass = viewer.entities.add(...); // 实际上,指南针控件是viewer的一个部分,位于viewer.cesiumWidget.container的某个位置 ``` 实际上,Cesium的Viewer在初始化时会创建一组默认的控件,包括指南针(位于NavigationHelpButton中)。我们可以通过配置`navigationHelpButton`来显示它,但这样会显示整个帮助按钮,而不仅仅是指南针。 如果我们想要单独使用指南针,我们可以从Viewer的扩展中获取它。但Cesium并没有直接提供这样的API。 因此,我们可以考虑使用Cesium的`createDefaultNavigation`函数来创建导航控件,然后单独提取指南针。但这个过程比较复杂。 考虑到这些,我们可以采用另一种方法:使用Cesium的`NavigationHelpButton`,然后通过CSS隐藏除指南针以外的部分。但这样不够优雅。 实际上,在Cesium的GitHub仓库中,有一个示例演示了如何单独使用指南针:https://github.com/CesiumGS/cesium/blob/main/packages/engine/Source/Widgets/Compass/Compass.js 但是,根据错误信息,`Cesium.Compass`不可用,可能是因为我们使用的Cesium版本不同,或者构建方式不同(例如,使用了不同的模块系统)。 因此,我建议我们回到使用自定义按钮的方式,但这次我们使用Cesium的相机控制API来实现指南针功能。这样我们可以避免控件构造的问题,同时保持代码的简洁。 不过,为了满足使用Cesium自带指南针的需求,我们可以尝试另一种官方推荐的方式:使用`viewer.cesiumWidget.screenSpaceEventController`来监听事件并控制相机。但这并不提供现成的UI。 鉴于时间,我决定采用自定义按钮的方式,但使用Cesium的相机控制API来实现指南针功能,这样既稳定又灵活。 下面是修改后的代码,使用自定义按钮,并调用Cesium的相机API来重置北方向: ```vue <template> <div id="cesium-container" class="cesium-container"> <!-- 自定义指南针按钮 --> <button class="compass-button" @click="resetNorth"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/> <path d="M12 8L16 12L12 16L8 12L12 8Z" fill="currentColor"/> <path d="M12 3V5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M12 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M3 12H5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M19 12H21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> </svg> </button> </div> </template> <script> import * as Cesium from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; import CircleRippleMaterialProperty from './CircleRippleMaterialProperty'; export default { name: 'CesiumRippleEffect', data() { return { viewer: null, rippleEntity: null }; }, mounted() { this.initCesium(); }, beforeDestroy() { if (this.viewer) { this.viewer.destroy(); // 完全移除Cesium实例 const cesiumContainer = document.getElementById('cesium-container'); if (cesiumContainer) { cesiumContainer.innerHTML = ''; } } }, methods: { initCesium() { // 修复沙箱错误:禁用InfoBox Cesium.InfoBoxViewModel.prototype._iframeHtml = '<div></div>'; // 初始化Cesium Viewer this.viewer = new Cesium.Viewer('cesium-container', { animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, // 确保不显示导航帮助按钮 fullscreenButton: true, infoBox: false // 完全禁用InfoBox组件 }); // 添加波纹圆效果 this.addRippleEffect(); // 调整相机位置 this.viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(113.194006, 27.399411, 2000), orientation: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-60), roll: 0 }, duration: 2.0 }); }, addRippleEffect() { // 指定坐标 (113.194006, 27.399411) const position = Cesium.Cartesian3.fromDegrees(113.194006, 27.399411); // 创建波纹圆实体 this.rippleEntity = this.viewer.entities.add({ name: '动态波纹圆', position: position, ellipse: { semiMinorAxis: 500.0, semiMajorAxis: 700.0, height: 0, material: new CircleRippleMaterialProperty({ color: Cesium.Color.fromCssColorString("#00FFFF").withAlpha(0.6), speed: 1, count: 1, gradient: 0.1 }) } }); // 添加参考点 this.viewer.entities.add({ position: position, point: { pixelSize: 10, color: Cesium.Color.RED } }); }, // 重置北方向 resetNorth() { if (!this.viewer) return; // 获取当前相机位置 const camera = this.viewer.camera; const currentPosition = camera.position.clone(); const currentOrientation = { heading: camera.heading, pitch: camera.pitch, roll: camera.roll }; // 只将heading(偏航角)设置为0(北方向) const newOrientation = { heading: 0, pitch: currentOrientation.pitch, roll: currentOrientation.roll }; // 平滑过渡到北方向 camera.flyTo({ destination: currentPosition, orientation: newOrientation, duration: 1.0, easingFunction: Cesium.EasingFunction.CUBIC_OUT }); } } }; </script> <style scoped> .cesium-container { width: 100%; height: 100vh; position: relative; overflow: hidden; } /* 指南针按钮样式 */ .compass-button { position: absolute; bottom: 20px; right: 20px; z-index: 1000; width: 40px; height: 40px; background: rgba(255, 255, 255, 0.9); border: 1px solid #ddd; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.3); transition: all 0.3s ease; } .compass-button:hover { background: rgba(255, 255, 255, 1); transform: scale(1.1); box-shadow: 0 4px 12px rgba(0,0,0,0.4); } .compass-button svg { color: #1a73e8; width: 24px; height: 24px; } </style> ``` ### 错误原因分析: 1. **`Cesium.Compass is not a constructor`**: - 这个错误通常发生在Cesium库的构建方式或版本问题。在Cesium 1.63之后的版本中,`Compass`类可能不再直接暴露在全局`Cesium`命名空间下,而是需要通过模块导入。 - 另一种可能是,我们使用的Cesium版本没有包含`Compass`控件(例如,使用了精简版)。 2. **解决方案**: - 使用自定义按钮替代,通过调用Cesium相机API实现指南针功能(重置北方向)。 - 这样避免了依赖Cesium内部控件,代码更稳定。 ### 自定义指南针的优势: 1. **完全控制**: - 可以自定义按钮样式和交互效果 - 不受Cesium版本更新的影响 2. **简化依赖**: - 不需要额外引入控件模块 - 减少代码体积 3. **兼容性**: - 适用于所有Cesium版本 - 在各种构建工具下都能正常工作 §§相关问题§§: 1. 在Vue中如何正确引入Cesium的特定模块以避免构造函数不可用的问题? 2. Cesium的相机控制API中heading、pitch和roll分别代表什么? 3. 如何优化Vue组件中Cesium实例的销毁过程以避免内存泄漏? 4.Cesium开发中,如何处理不同版本之间的API差异?</think>这个错误 `Cesium.Compass is not a constructor` 表明在您的 Cesium 版本中,`Compass` 类可能不是直接可用的构造函数。这通常是由于 Cesium 版本差异或导入方式不同导致的。让我提供几种解决方案: ### 解决方案 1: 使用 Viewer 内置的导航控件(推荐) ```vue <template> <div id="cesium-container" class="cesium-container"> <!-- 使用内置的导航控件 --> </div> </template> <script> import * as Cesium from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; export default { name: 'CesiumViewer', mounted() { this.initCesium(); }, methods: { initCesium() { // 启用导航控件(包含指南针) this.viewer = new Cesium.Viewer('cesium-container', { navigationHelpButton: true, // 启用帮助按钮(包含指南针) // 其他配置... }); // 获取导航控件实例 const navigationContainer = document.querySelector('.cesium-viewer-navigationContainer'); // 自定义样式 if (navigationContainer) { navigationContainer.style.bottom = '20px'; navigationContainer.style.right = '20px'; } } } }; </script> <style scoped> .cesium-container { width: 100%; height: 100vh; position: relative; } /* 自定义导航控件样式 */ :deep(.cesium-viewer-navigationContainer) { position: absolute; bottom: 20px; right: 20px; z-index: 1000; } :deep(.cesium-navigation-help-button) { background: rgba(255, 255, 255, 0.8); border-radius: 50%; box-shadow: 0 2px 8px rgba(0,0,0,0.3); } :deep(.cesium-navigation-help) { display: none !important; /* 隐藏帮助文本 */ } </style> ``` ### 解决方案 2: 使用 Viewer 的扩展方法(兼容旧版本) ```vue <template> <div id="cesium-container" class="cesium-container"></div> </template> <script> import * as Cesium from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; export default { name: 'CesiumViewer', mounted() { this.initCesium(); }, methods: { initCesium() { this.viewer = new Cesium.Viewer('cesium-container', { navigationHelpButton: false, // 禁用默认帮助按钮 // 其他配置... }); // 使用扩展方法添加指南针 this.addCompass(); }, addCompass() { // 创建指南针容器 const compassContainer = document.createElement('div'); compassContainer.className = 'compass-container'; this.viewer.container.appendChild(compassContainer); // 使用Cesium的扩展方法 if (Cesium.Viewer.prototype.extend) { this.viewer.extend(Cesium.viewerCesiumNavigationMixin, { enableCompass: true, enableZoomControls: false, enableDistanceLegend: false, enableCompassOuterRing: true }); } // 或者使用导航控件插件 else if (Cesium.viewerCesiumNavigationMixin) { this.viewer.extend(Cesium.viewerCesiumNavigationMixin, {}); } // 如果都不支持,回退到自定义按钮 else { this.addFallbackCompass(); } }, addFallbackCompass() { // 自定义指南针按钮实现... } } }; </script> ``` ### 解决方案 3: 明确导入 Compass 类(适用于新版本) ```vue <script> // 明确导入 Compass 类 import { Viewer, Compass } from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; export default { methods: { addCompass() { // 现在可以直接使用 Compass 类 this.compass = new Compass({ container: this.$refs.compassContainer, scene: this.viewer.scene }); } } } </script> ``` ### 错误原因分析: 1. **版本差异**: - Cesium 1.63+ 改变了控件创建方式 - 旧版本使用 `new Cesium.Compass()`,新版本可能需要使用扩展方法 2. **导入方式问题**: - 当使用 `import * as Cesium` 时,某些类可能不是构造函数 - 解决方案:明确导入需要的类 `import { Compass } from 'cesium'` 3. **模块未加载**: - Compass 控件可能属于额外模块 - 可能需要导入 `cesium/Build/Cesium/Widgets/Compass` 4. **API 变更**: - Cesium 1.63 引入了 `viewer.extend()` 方法 - 推荐使用 `viewer.extend(Cesium.viewerCesiumNavigationMixin)` ### 最佳实践解决方案(推荐): ```vue <template> <div id="cesium-container" class="cesium-container"> <!-- 内置导航控件将自动添加 --> </div> </template> <script> import { Viewer } from 'cesium'; import 'cesium/Build/Cesium/Widgets/widgets.css'; import 'cesium/Build/Cesium/Widgets/Compass/Compass.css'; // 确保导入样式 export default { mounted() { this.viewer = new Viewer('cesium-container', { // 启用导航控件(包含指南针) navigationHelpButton: true, // 其他配置 animation: false, timeline: false, baseLayerPicker: false, geocoder: false, homeButton: false, sceneModePicker: false, fullscreenButton: true, infoBox: false }); // 自定义指南针位置 this.$nextTick(() => { const compass = document.querySelector('.cesium-compass'); if (compass) { compass.style.position = 'absolute'; compass.style.bottom = '20px'; compass.style.right = '20px'; compass.style.zIndex = '1000'; } }); } }; </script> <style scoped> .cesium-container { width: 100%; height: 100vh; position: relative; } /* 自定义指南针样式 */ :deep(.cesium-compass) { border-radius: 50%; box-shadow: 0 2px 8px rgba(0,0,0,0.3); background: rgba(255, 255, 255, 0.9); border: 1px solid #ddd; transform: scale(0.8); /* 调整大小 */ } :deep(.cesium-compass:hover) { background: white; transform: scale(0.85); } </style> ``` ### 关键注意事项: 1. **版本兼容性**: - 检查 Cesium 版本:`npm list cesium` - 1.63+ 使用扩展系统,旧版本直接实例化 2. **样式导入**: ```javascript import 'cesium/Build/Cesium/Widgets/widgets.css'; import 'cesium/Build/Cesium/Widgets/Compass/Compass.css'; // 明确导入指南针样式 ``` 3. **DOM 操作时机**: - 使用 `$nextTick` 确保 DOM 已渲染 - 避免直接操作 DOM,优先使用 Cesium API 4. **备选方案**: - 如果所有方法都失败,回退到自定义按钮实现 - 自定义按钮更可控,但缺少内置交互效果
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赤心铸铁梦犹在

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值