Cesium--3D地球展示

152 阅读5分钟

摘要

近期一个项目主要是用于无人机巡检(某一特点行业巡检),在开源平台看到的大多都是一些需要付费才可商业的系统,所以就打算自己开发一个,看到别人系统中的3D地球展示,轨迹追踪等炫酷效果,难免会引起一些好奇心,于是乎就自己开始研究这项技术

Cesium简介

Cesium 是一个开源的 JavaScript 库,基于WebGL渲染,用于在全球尺度下创建世界级的三维地理空间可视化应用。它最著名的功能是创建基于虚拟地球地图的应用程序,允许开发者将各种数据(如 3D 模型、点、线、面、影像、地形、矢量数据等)整合到一个交互式的 3D 场景中。

简单来说,你可以把它理解为  “用于浏览器的数字地球引擎”

最为常见的使用场景:

  • 航空航天与国防: 任务规划、卫星轨道可视化、无人机航迹模拟。
  • 测绘与地理信息系统: 创建三维 GIS 应用,集成和展示复杂的测绘数据。
  • 智慧城市与建筑: 城市级的数字孪生,BIM 模型与地理环境的结合。
  • 气象与海洋学: 可视化全球天气模式、飓风路径、海洋洋流等动态数据。
  • 教育与科研: 用于天文、地理、环境科学等领域的教学和科研演示。
  • 应急响应: 灾害模拟、救援路线规划和态势感知。

引入

一些基础的引入项目工程等就不说明了可以直接看官网~ cesium.com/

项目使用

image.png 在项目中使用地球的信息搭配搞得地图用来模拟获取无人机飞行的一个实时航拍的画面,画质清晰度以及细节模拟已经是目前能做到的极限了,终究无法做到真正代替无人机航拍的画面,具体的使用流程如下:

新建一个这个画面的组件后期直接引入对应主组件

  <div ref="cesiumContainer" class="cesium-container">
    <!-- 无人机信息面板 -->
    <div v-if="isDroneVisible" class="drone-info-panel">
      <h3>无人机状态</h3>
      <p>高度: {{ droneAltitude.toFixed(2) }}米</p>
      <p>速度: {{ droneSpeed.toFixed(2) }}m/s</p>
      <p>坐标: {{ droneLongitude.toFixed(6) }}°, {{ droneLatitude.toFixed(6) }}°</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import * as Cesium from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";

// 声明 CESIUM_BASE_URL 类型
declare global {
  interface Window {
    CESIUM_BASE_URL: string;
  }
}

// 配置 Cesium 资源路径
window.CESIUM_BASE_URL = '/cesium/';

// 状态管理
const cesiumContainer = ref<HTMLDivElement | null>(null);
let viewer: Cesium.Viewer | null = null;
let droneEntity: Cesium.Entity | null = null;
let pathEntity: Cesium.Entity | null = null;
let lastPosition: Cesium.Cartesian3 | null = null;

// 无人机状态数据
const isDroneVisible = ref(false);
const droneAltitude = ref(0);
const droneSpeed = ref(0);
const droneLongitude = ref(116.4074);
const droneLatitude = ref(39.9042);

// 轨迹点数组
const positions = ref<Cesium.Cartesian3[]>([]);
let previousSceneMode: Cesium.SceneMode | null = null;

  // 监听场景模式变化
const handleSceneModeChange = () => {
  if (!viewer) return;
  const currentMode = viewer.scene.mode;
  
  // 只在模式发生变化时处理
  if (previousSceneMode === currentMode) return;
  previousSceneMode = currentMode;
  
  if (droneEntity && viewer) {
    if (currentMode !== Cesium.SceneMode.SCENE3D) {
      // 在2D和哥伦布视图模式下,移除所有orientation相关的属性
      droneEntity.orientation = undefined;
      
      // 确保position是一个简单的CartesianPositionProperty
      const currentPosition = droneEntity.position?.getValue(Cesium.JulianDate.now());
      if (currentPosition) {
        droneEntity.position = new Cesium.ConstantPositionProperty(currentPosition);
      }
    }
  }
};// 创建无人机实体
const createDroneEntity = (position: Cesium.Cartesian3) => {
  if (!viewer) return null;

  return viewer.entities.add({
    position: position,
    point: {
      pixelSize: 12,
      color: Cesium.Color.CYAN,
      outlineColor: Cesium.Color.WHITE,
      outlineWidth: 2,
      show: new Cesium.CallbackProperty(() => {
        return viewer?.scene.mode !== Cesium.SceneMode.SCENE3D;
      }, false),
      disableDepthTestDistance: Number.POSITIVE_INFINITY
    },
    model: {
      uri: '/models/drone.glb',
      minimumPixelSize: 48,
      maximumScale: 20000,
      color: Cesium.Color.WHITE,
      colorBlendMode: Cesium.ColorBlendMode.HIGHLIGHT,
      silhouetteColor: Cesium.Color.CYAN,
      silhouetteSize: 1.0,
      show: new Cesium.CallbackProperty(() => {
        return viewer?.scene.mode === Cesium.SceneMode.SCENE3D;
      }, false),
    },
    orientation: new Cesium.VelocityOrientationProperty(new Cesium.ConstantPositionProperty(position)),
    path: {
      resolution: 1,
      material: new Cesium.PolylineGlowMaterialProperty({
        glowPower: 0.2,
        color: Cesium.Color.CYAN,
      }),
      width: 10,
    },
  });
};

// 更新无人机位置
const updateDronePosition = (newLongitude: number, newLatitude: number, newAltitude: number) => {
  if (!viewer || !droneEntity) return;

  const position = Cesium.Cartesian3.fromDegrees(
    newLongitude,
    newLatitude,
    newAltitude
  );

  // 更新无人机位置
  const currentMode = viewer.scene.mode;
  if (currentMode === Cesium.SceneMode.SCENE3D) {
    droneEntity.position = new Cesium.SampledPositionProperty();
    (droneEntity.position as Cesium.SampledPositionProperty).addSample(
      Cesium.JulianDate.now(),
      position
    );
  } else {
    droneEntity.position = new Cesium.ConstantPositionProperty(position);
  }

  // 如果有上一个位置,计算速度
  if (lastPosition) {
    const distance = Cesium.Cartesian3.distance(lastPosition, position);
    droneSpeed.value = distance * 0.1; // 假设更新间隔为100ms,转换为m/s
  }

  // 更新轨迹
  positions.value.push(position);
  if (positions.value.length > 1000) {
    positions.value.shift(); // 保持轨迹点数量在合理范围内
  }

  // 更新路径实体
  if (pathEntity) {
    pathEntity.position = new Cesium.SampledPositionProperty();
    positions.value.forEach((pos, index) => {
      (pathEntity?.position as Cesium.SampledPositionProperty)?.addSample(
        Cesium.JulianDate.fromDate(new Date(Date.now() + index * 100)),
        pos
      );
    });
  }

  // 更新状态数据
  droneAltitude.value = newAltitude;
  droneLongitude.value = newLongitude;
  droneLatitude.value = newLatitude;
  lastPosition = position;

  // 相机跟随
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(
      newLongitude,
      newLatitude,
      newAltitude + 100
    ),
    orientation: {
      heading: Cesium.Math.toRadians(0),
      pitch: Cesium.Math.toRadians(-45),
      roll: 0
    },
    duration: 0.5
  });
};

onMounted(async () => {
  if (!cesiumContainer.value) return;

  try {
    // 设置 Cesium Ion 访问令牌
    Cesium.Ion.defaultAccessToken ='需要你自己申请token';

    // 创建地形提供者
    const terrainProvider = await Cesium.createWorldTerrainAsync({
      requestWaterMask: true,
      requestVertexNormals: true
    });

    // 创建影像提供者(使用高德地图)
    const imageryProvider = new Cesium.UrlTemplateImageryProvider({
      url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
      maximumLevel: 19,
      credit: '© 高德地图'
    });

    // 初始化 Viewer
    viewer = new Cesium.Viewer(cesiumContainer.value, {
      terrainProvider,
      baseLayer: new Cesium.ImageryLayer(imageryProvider),
      animation: false,
      baseLayerPicker: false,
      fullscreenButton: false,
      vrButton: false,
      geocoder: true, // 启用地理编码器
      homeButton: true,
      infoBox: false,
      sceneModePicker: true, // 允许切换2D/3D模式
      selectionIndicator: false,
      timeline: false,
      navigationHelpButton: true,
      contextOptions: {
        webgl: {
          alpha: true,
          depth: true,
          stencil: true,
          antialias: true,
          powerPreference: 'high-performance'
        }
      }
    });

    // 隐藏 Cesium ion logo
    const creditContainer = viewer.cesiumWidget.creditContainer as HTMLElement;
    creditContainer.style.display = "none";

    // 监听场景模式变化
    viewer.scene.morphComplete.addEventListener(handleSceneModeChange);
    viewer.scene.morphStart.addEventListener(handleSceneModeChange);

    // 添加城市建筑数据
    try {
      const osmBuildings = await Cesium.createOsmBuildingsAsync();
      viewer.scene.primitives.add(osmBuildings);
    } catch (error) {
      console.error("加载建筑数据失败:", error);
    }

    // 初始化无人机位置
    const initialPosition = Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 100);
    droneEntity = createDroneEntity(initialPosition);
    lastPosition = initialPosition;

    // 创建路径实体
    pathEntity = viewer.entities.add({
      position: new Cesium.SampledPositionProperty(),
      path: {
        resolution: 1,
        material: new Cesium.PolylineGlowMaterialProperty({
          glowPower: 0.2,
          color: Cesium.Color.CYAN
        }),
        width: 10
      }
    });

    // 设置相机初始视角
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 1500),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-45),
        roll: 0
      }
    });

    // 启用高级渲染效果
    viewer.scene.globe.enableLighting = true;
    viewer.scene.globe.depthTestAgainstTerrain = true;
    viewer.scene.fog.enabled = true;
    viewer.scene.fog.density = 0.0001;
    viewer.scene.fog.minimumBrightness = 0.8;

    // 显示无人机信息面板
    isDroneVisible.value = true;

    // 模拟无人机运动(示例)
    setInterval(() => {
      const time = Date.now() * 0.001;
      const newLongitude = 116.4074 + Math.sin(time) * 0.01;
      const newLatitude = 39.9042 + Math.cos(time) * 0.01;
      const newAltitude = 100 + Math.sin(time * 0.5) * 50;

      updateDronePosition(newLongitude, newLatitude, newAltitude);
    }, 100);

  } catch (error) {
    console.error("初始化 Cesium 失败:", error);
  }
});

onUnmounted(() => {
  if (viewer) {
    // 移除事件监听器
    viewer.scene.morphComplete.removeEventListener(handleSceneModeChange);
    viewer.scene.morphStart.removeEventListener(handleSceneModeChange);
    
    viewer.destroy();
    viewer = null;
  }
});
</script>



<style scoped>
.cesium-container {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  position: relative;
}

.drone-info-panel {
  position: absolute;
  top: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  padding: 15px;
  border-radius: 8px;
  z-index: 1000;
  min-width: 200px;
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.drone-info-panel h3 {
  margin: 0 0 10px 0;
  font-size: 16px;
  color: #00d4ff;
}

.drone-info-panel p {
  margin: 5px 0;
  font-size: 14px;
  color: #ffffff;
}

:deep(.cesium-viewer-bottom) {
  display: none;
}

:deep(.cesium-viewer) {
  --cesium-view-background: transparent;
}

:deep(.cesium-viewer-toolbar) {
  background: rgba(0, 0, 0, 0.7);
  border-radius: 8px;
  padding: 5px;
  backdrop-filter: blur(10px);
}

:deep(.cesium-button) {
  background-color: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: #fff;
  border-radius: 4px;
  padding: 4px 8px;
  margin: 2px;
}

:deep(.cesium-button:hover) {
  background-color: rgba(255, 255, 255, 0.2);
  border-color: rgba(255, 255, 255, 0.3);
}

/* 添加动画效果 */
@keyframes glow {
  0% { box-shadow: 0 0 5px rgba(0, 212, 255, 0.5); }
  50% { box-shadow: 0 0 20px rgba(0, 212, 255, 0.8); }
  100% { box-shadow: 0 0 5px rgba(0, 212, 255, 0.5); }
}

.drone-info-panel {
  animation: glow 2s infinite;
}

:deep(.cesium-viewer-bottom) {
  display: none;
}
</style>