cesium自定义状态栏组件

2,332 阅读3分钟

我们在cesium开发的过程中经常需要实时显示当前经纬度、高度、角度和刷新率等实时数据,本篇文章将介绍如何在基于Cesium的WebGL应用程序中创建一个自定义的状态栏组件。这个状态栏组件能够显示用户的当前位置(经度、纬度和海拔)、摄像机的方向(方向角和俯仰角)、摄像机的高度以及渲染性能指标(帧率和延迟)。此组件使用Vue.js框架编写,并且采用了TypeScript进行类型安全的开发。

image.png

组件结构

状态栏组件被设计为一个可复用的Vue组件,它接收一个viewer作为属性,并通过监听Cesium Viewer的各种事件来更新状态栏中的信息。

组件功能

  • 位置信息:显示用户当前的经度、纬度和海拔高度。
  • 摄像机方向:显示摄像机的方向角和俯仰角。
  • 摄像机高度:显示摄像机相对于地球表面的高度。
  • 性能指标:显示每秒帧数(FPS)和平均帧延迟(MS)。

技术栈

  • Vue.js:构建用户界面。
  • TypeScript:类型安全的JavaScript。
  • Cesium:3D地理空间数据可视化库。

组件实现

  1. 模板结构:状态栏分为三部分,分别显示位置信息、摄像机方向和性能指标。

  2. 状态管理:使用Vue的ref来存储状态栏中的各个数据项。

  3. 事件监听

    • camera.changed:监听摄像机的变化,更新摄像机方向和高度。
    • scene.postRender:监听场景渲染完成事件,更新帧率和延迟。
    • screenSpaceEventHandler:监听鼠标移动事件,更新位置信息。

代码概览

  1. 模板:定义了状态栏的布局和样式。

  2. 脚本

    • 使用setup函数初始化状态变量。
    • 通过onMountedonUnmounted生命周期钩子来添加和移除事件监听器。
    • 使用throttle函数来限制更新频率,提高性能。
  3. 样式:定义了状态栏的基本样式。

关键步骤

这个组件是个很基础的功能,唯一的关键难点在于获取 fps ,在这里我是参考了 从Cesium的源码,自定义帧率显示 这篇文章,并对其进行了小小的改动,获取到实时的帧率和延迟数据.

在这个项目中,经纬度的数据我获取的是鼠标的经纬度,你也可以用相机当前的经纬度。


import GetFPSInfo from '@/modules/status-bar/custom-fps';
let FPSInfo = new GetFPSInfo();

...

const updateFPS = throttle(() => {
  if (viewer) {
    let info = FPSInfo.update();
    frameRateFPS.value = info.fps
    frameRateMS.value = info.ms
  }
}, 200)

示例代码

<template>
  <div class="status-bar flex">
    <div class="flex">
      <div>经度: {{ longitude.toFixed(6) }}</div>
      <div>纬度: {{ latitude.toFixed(6) }}</div>
      <div>海拔:{{ altitude.toFixed(2) }} 米</div>
    </div>
    <div class="flex">
      <div>方向:{{ heading.toFixed(2) }}°</div>
      <div>俯仰角:{{ pitch.toFixed(2) }}°</div>
      <div>视高:{{ eyeHeight.toFixed(2) }} 米</div>
    </div>
    <div class="flex">
      <div>帧率:{{ frameRateFPS }} FPS</div>
      <div>延迟:{{ frameRateMS }} MS</div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, onUnmounted } from 'vue';
import * as Cesium from 'cesium';
import GetFPSInfo from '@/modules/status-bar/custom-fps';

const props = defineProps({
  viewer: {
    type: Object as () => Cesium.Viewer,
    required: true
  }
});

// 定义状态栏数据
const longitude = ref(0);
const latitude = ref(0);
const altitude = ref(0);
const heading = ref(0);
const pitch = ref(0);
const eyeHeight = ref(0);
const frameRateFPS = ref<number | string>(0);
const frameRateMS = ref<number | string>(0);

// 获取 Cesium 视图对象
let viewer: Cesium.Viewer | undefined;
let FPSInfo = new GetFPSInfo();

// 初始化状态栏
onMounted(() => {
  viewer = props.viewer;

  // 更新状态栏信息
  if (viewer) {
    // 监听相机变化
    viewer.scene.camera.changed.addEventListener(updateStatus);
    // 监听渲染
    viewer.scene.postRender.addEventListener(updateFPS);
    // 监听鼠标移动
    viewer.screenSpaceEventHandler.setInputAction(updatePosition, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }
});

// 更新状态栏信息
const updateStatus = throttle(() => {
  if (viewer) {
    eyeHeight.value = viewer.camera.positionCartographic.height;
    heading.value = Cesium.Math.toDegrees(viewer.camera.heading);
    pitch.value = Cesium.Math.toDegrees(viewer.camera.pitch);
  }
}, 200);

const updateFPS = throttle(() => {
  if (viewer) {
    let info = FPSInfo.update();
    frameRateFPS.value = info.fps;
    frameRateMS.value = info.ms;
  }
}, 200);

const updatePosition = throttle((movement: { endPosition: Cesium.Cartesian2 }) => {
  // 获取鼠标在屏幕上的位置
  const screenPosition = movement.endPosition;
  // 将屏幕位置转换为经纬度
  const cartesian = viewer?.scene.globe.pick(viewer.camera.getPickRay(screenPosition), viewer.scene);
  if (cartesian) {
    const cartographic = viewer?.scene.globe.ellipsoid.cartesianToCartographic(cartesian);
    longitude.value = Cesium.Math.toDegrees(cartographic.longitude);
    latitude.value = Cesium.Math.toDegrees(cartographic.latitude);
    altitude.value = cartographic.height;
  }
}, 200);

// 节流函数
function throttle<T extends (...args: any[]) => any>(
  func: T,
  wait: number,
): T & {
  cancel?: () => void;
  flush?: () => void;
} {
  ...
}

// 清理工作
onUnmounted(() => {
  if (viewer) {
    FPSInfo = new GetFPSInfo();
    viewer.scene.camera.changed.removeEventListener(updateStatus);
    viewer.scene.postRender.removeEventListener(updateFPS);
    viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  }
});
</script>

<style scoped lang="scss">
...
</style>

总结

本组件通过监听Cesium Viewer的各种事件并更新状态栏中的信息,为用户提供了一个直观的界面来了解当前的地理位置、摄像机方向和系统性能。通过这种方式,用户可以在探索虚拟地球的同时,更加清晰地了解自己的位置和系统的运行状况。

源码

完整源码和使用案例 请看这里,里面还有其他一些我写案例东西,可以的话请给个star