cesium模型轨迹动画

306 阅读5分钟

GIF 2025-02-11 14-23-45.gif

vue3+cesium.加载无人机模型,并播放轨迹飞行动画.

轨迹全部显示,也能改为经过显示.至于叠加显示的功能,暂时还没时间做,暂时就这么处理了.

时间轴和动画控件css隐藏了.因为后端没有返回轨迹点时间,有需要的可以打开

<template>
  <div
    v-show="modalVisible"
    class="dialog fixed w-1200px h-720px left-55% top-50% -translate-x-50% -translate-y-50% z-12"
  >
    <CustomModal
      v-model:visible="modalVisible"
      :show-close-btn="true"
      :overlay-opacity="0.92"
      blur-px="5px"
      @close="closeModal"
    >
      <div id="track-map-container" class="w-full h-full"></div>
    </CustomModal>
    <div id="credit"></div>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick } from 'vue';
import * as Cesium from 'cesium';
import ChongQingCity from '@/assets/data/重庆市.json';
import { addTMap } from '~/src/utils';
import { getPosition } from '~/src/service';
const modalVisible = ref(false);
let viewer: any = null;
const animateEntity: any = ref(null);
const clockTickListener: any = ref(null);
const polylineList = [
  {
    height: '2000',
    lng: '106.3874',
    lat: '29.491311',
    dynamicTime: '1708486991000'
  },
  {
    height: '2000',
    lng: '106.447766',
    lat: '29.506903',
    dynamicTime: '1708490591000'
  },
  {
    height: '2000',
    lng: '106.544352',
    lat: '29.576786',
    dynamicTime: '1708494191000'
  }
];

// cesium时钟时间格式化函数
function CesiumTimeFormatter(datetime: any, viewModel: any) {
  const julianDT = new Cesium.JulianDate();
  Cesium.JulianDate.addHours(datetime, 8, julianDT);
  const gregorianDT = Cesium.JulianDate.toGregorianDate(julianDT);

  const hour = `${gregorianDT.hour}`;
  const minute = `${gregorianDT.minute}`;
  const second = `${gregorianDT.second}`;
  return `${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
}
// cesium时钟日期格式化函数
function CesiumDateFormatter(datetime, viewModel, ignoredate) {
  const julianDT = new Cesium.JulianDate();
  Cesium.JulianDate.addHours(datetime, 8, julianDT);
  const gregorianDT = Cesium.JulianDate.toGregorianDate(julianDT);

  return `${gregorianDT.year}${gregorianDT.month}${gregorianDT.day}日`;
}
// cesium时间轴格式化函数
function CesiumDateTimeFormatter(datetime, viewModel, ignoredate) {
  const julianDT = new Cesium.JulianDate();
  Cesium.JulianDate.addHours(datetime, 8, julianDT);
  const gregorianDT = Cesium.JulianDate.toGregorianDate(julianDT);

  const hour = `${gregorianDT.hour}`;
  const minute = `${gregorianDT.minute}`;
  return `${gregorianDT.day}${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
}

const initMap = () => {
  return new Promise((resolve, _) => {
    if (!viewer) {
      Cesium.Ion.defaultAccessToken =
        '------------------申请的token-----------------';
      // viewer是所有Api的开始
      viewer = new Cesium.Viewer('track-map-container', {
        animation: true, // 是否显示动画控件
        shouldAnimate: true,
        // credit: false, //是否显示版权信息
        creditContainer: 'credit',
        baseLayerPicker: false, // 是否显示图层选择控件
        geocoder: false, // 是否显示地名查找控件
        timeline: true, // 是否显示时间线控件
        sceneModePicker: false, // 是否显示投影方式控件
        navigationHelpButton: false, // 是否显示帮助信息控件
        fullscreenButton: false, // 是否显示全屏按钮
        infoBox: false, // 是否显示点击要素之后显示的信息
        homeButton: false // 是否显示Home按钮
        // terrainProvider: Cesium.createWorldTerrain(), // 是否显示地形
        // imageryProvider: new Cesium.UrlTemplateImageryProvider({
        //   url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
        // })
      });
      addTMap(viewer, 'img');
      addTMap(viewer, 'cia');
      // addAMapImagery(viewer, 'img'); // 加载高德影像底图
      // addAMapImagery(viewer, 'marker'); // 加载高德标注图层
      // 添加错误处理和状态检查
      console.log('开始加载地形...');
      const loadTerrain = async () => {
        // 添加超时处理函数
        const timeoutPromise = (promise: Promise<any>, timeout: number) => {
          return Promise.race([
            promise,
            new Promise((_, reject) => {
              setTimeout(() => reject(new Error('加载超时')), timeout);
            })
          ]);
        };

        try {
          const terrainProviderLocal = new Cesium.CesiumTerrainProvider({
            // url: 'http://localhost:8090'
            // url: 'http://localhost:8091'
            url: '/terrain'
            // url: import.meta.env.VITE_TERRAIN_URL
          });
          // 先尝试加载本地地形
          await timeoutPromise(terrainProviderLocal.readyPromise, 3000);
          if (terrainProviderLocal.ready) {
            viewer.scene.terrainProvider = terrainProviderLocal;
            console.log('本地地形加载成功terrainProvider', terrainProviderLocal);
          } else {
            throw new Error('本地地形未就绪');
          }
        } catch (localError) {
          console.warn('本地地形加载失败:', localError);
        } finally {
          console.log('地形加载流程完成');
        }
      };

      viewer.scene.globe.depthTestAgainstTerrain = true;
      loadBoundary();
      loadTerrain();
      Cesium.Timeline.prototype.makeLabel = CesiumDateTimeFormatter;
      viewer.animation.viewModel.dateFormatter = CesiumDateFormatter;
      viewer.animation.viewModel.timeFormatter = CesiumTimeFormatter;

      resolve('初始化完成');
    } else {
      resolve('已经初始化');
    }
  });
};

// 绘制边界
function drawBoundary(data: any) {
  // 要绘制的区域经纬度数据
  const checkLatlng = data.features[0].geometry.coordinates[0][0];
  removeBoundary();
  // 中国边界
  const extent = { xmin: 73.0, xmax: 136.0, ymin: 3.0, ymax: 59.0 };
  const geojson = {
    type: 'Feature',
    geometry: {
      type: 'MultiPolygon',
      coordinates: [
        [
          [
            [extent.xmin, extent.ymin],
            [extent.xmax, extent.ymin],
            [extent.xmax, extent.ymax],
            [extent.xmin, extent.ymax],
            [extent.xmin, extent.ymin]
          ],
          checkLatlng
        ]
      ]
    }
  };
  const geoPromise = Cesium.GeoJsonDataSource.load(geojson, {
    stroke: Cesium.Color.fromCssColorString('#39E09B').withAlpha(0.8),
    fill: Cesium.Color.fromCssColorString('rgb(2,26,79)').withAlpha(0.6),
    strokeWidth: 10,
    clampToGround: true
  });
  geoPromise.then(dataSource => {
    // 添加geojson
    viewer.dataSources.add(dataSource);
    // 给geojson命名
    dataSource.name = 'geojsonBoundary';
    // 视角跳转至geojson
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(106.547514, 29.578797, 50000),
      duration: 0
    });
  });
}
// 清除绘制
function removeBoundary() {
  viewer.dataSources.remove(viewer.dataSources.getByName('geojsonBoundary')[0]);
}
// 获取geojson数据
function loadBoundary() {
  drawBoundary(ChongQingCity);
}
function loadTrackData(jobId: string) {
  console.log('加载轨迹数据');
  getPosition(jobId).then((res: any) => {
    console.log('%c[🚀🚀🚀 table-action-modal.vue - line 538]res', 'color: red;font-size:x-large', res);

    nextTick(() => {
      // 更改polylineList的dynamicTime,每个为当前时间多1000000*i
      polylineList.forEach((item: any, index: number) => {
        item.dynamicTime = new Date().getTime() + index * 1000000;
      });
      //   newAddPolyline(res);
      newAddPolyline(polylineList);
      addClockTickListener();
    });
  });
}

/**
 * 添加轨迹线
 * @param trackList 坐标数组
 */
function newAddPolyline(trackList: any) {
  const startTime = new Date(Number(trackList[0].dynamicTime));
  const endTime = new Date(Number(trackList[trackList.length - 1].dynamicTime));
  const start = Cesium.JulianDate.fromDate(startTime);
  const stop = Cesium.JulianDate.fromDate(endTime);
  //   console.log('开始时间1:', startTime);
  //   console.log('结束时间1:', endTime);
  //   console.log('开始时间2:', start);
  //   console.log('结束时间2:', stop);

  const positionProperty = new Cesium.SampledPositionProperty();
  positionProperty.setInterpolationOptions({
    interpolationDegree: 1,
    interpolationAlgorithm: Cesium.LagrangePolynomialApproximation
  });

  for (let i = 0; i < trackList.length; i++) {
    const { lng, lat, height, dynamicTime } = trackList[i];
    const position = Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height));
    const time = new Date(Number(dynamicTime));
    const csTime = Cesium.JulianDate.fromDate(time);
    positionProperty.addSample(csTime, position);
  }

  animateEntity.value = viewer.entities.add({
    name: 'DroneM30',
    availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({ start, stop })]),
    position: positionProperty,
    orientation: new Cesium.VelocityOrientationProperty(positionProperty),
    // 轨迹路径
    path: {
      resolution: 1,
      width: 2,
      material: Cesium.Color.RED
    },
    // path: {
    //   leadTime: 1,
    //   resolution: 1,
    //   material: new Cesium.PolylineGlowMaterialProperty({
    //     glowPower: 0.1,
    //     color: Cesium.Color.GREEN
    //   }),
    //   width: 10
    // },
    // path: {
    //   leadTime: 1,
    //   resolution: 1,
    //   material: new Cesium.PolylineGlowMaterialProperty({
    //     glowPower: 0.1,
    //     color: viewer.trackedEntity === animateEntity.value ? Cesium.Color.GREEN : Cesium.Color.RED // 根据条件设置颜色
    //   }),
    //   width: 10
    // },
    // 使用模型
    model: {
      show: true,
      uri: '/dji_m300_drone_animated.glb', // 使用 require 来加载模型文件
      minimumPixelSize: 164,
      maximumScale: 20000
    }
    // ,
    // label: {
    //   text: '无人机M30',
    //   font: '500 24px Helvetica',
    //   scale: 1,
    //   style: Cesium.LabelStyle.FILL,
    //   fillColor: Cesium.Color.RED,
    //   pixelOffset: new Cesium.Cartesian2(0, -40),
    //   showBackground: false
    // }
  });

  viewer.clock.startTime = start.clone();
  viewer.clock.stopTime = stop.clone();
  viewer.clock.currentTime = start.clone();
  viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // Loop at the end
  viewer.clock.multiplier = 100;
  viewer.timeline.zoomTo(start, stop);
  // 在动画开始前取消对实体的追踪
  viewer.trackedEntity = undefined;
  viewer.trackedEntity = animateEntity.value;
  viewer.timeline.shouldAnimate = true;
  viewer.clock.shouldAnimate = true;
  const { lng, lat, height } = trackList[0];
  viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height) + 10000),
    duration: 0
  });
}
function setEntityPosition() {
  // 获取当前时间
  const currentTime = viewer.clock.currentTime;
  // 获取实体的位置
  const position = animateEntity.value.position.getValue(currentTime);
  // position 是一个 Cartesian3 对象,你可以使用 Cesium.Cartographic.fromCartesian 来将其转换为经纬度
  const cartographic = Cesium.Cartographic.fromCartesian(position);
  const longitude = Cesium.Math.toDegrees(cartographic.longitude);
  const latitude = Cesium.Math.toDegrees(cartographic.latitude);
  const height = Cesium.Math.toDegrees(cartographic.height);

  // console.log(`实体的当前位置是:经度 ${longitude},纬度 ${latitude}`);
  // 创建一个HeadingPitchRange实例,设置视角为垂直向下
  const hpr = new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 1000);
  // 设置相机高度
  const cameraHeight = 200000; // 你可以修改这个值来设置相机高度
  // 获取trackEntity的position
  //   const trackEntityPosition = animateEntity.value.position.getValue(currentTime);
  // 更改trackEntityPosition的高度
  //   trackEntityPosition.z += 15000;
  // 创建一个新的Cartesian3实例,设置z坐标为相机高度
  // const newPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, cameraHeight);
  const newPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
  //   console.log('%c[🚀🚀🚀 trackMap.vue - line 278]height', 'color: red;font-size:x-large', height);
  // 调整相机视角,改为非锁定视角
  //   viewer.camera.lookAt(newPosition, hpr);
  //   viewer.camera.flyTo({
  //     destination: newPosition,
  //     orientation: {
  //       heading: 0,
  //       pitch: -90,
  //       roll: 0
  //     }
  //   });
}
function addClockTickListener() {
  clockTickListener.value = (clock: { shouldAnimate: any }) => {
    if (clock.shouldAnimate) {
      // 动画正在播放
      //   viewer.trackedEntity = animateEntity.value;
      //   console.log('动画正在播放');
      viewer.trackedEntity = undefined; // 确保不锁定相机
      //   setEntityPosition();
    } else {
      // 动画已暂停
      viewer.trackedEntity = undefined;
      //   console.log('动画已暂停');
    }
  };
  viewer.clock.onTick.addEventListener(clockTickListener.value);
}
function removeClockTickListener() {
  if (clockTickListener.value) {
    viewer.clock.onTick.removeEventListener(clockTickListener.value);
    clockTickListener.value = null;
  }
}
function closeModal() {
  removeClockTickListener();
  // 停止动画实体
  viewer.entities.remove(animateEntity.value);
  // 停止时间线
  viewer.timeline.stop();
  // 停止时间线动画
  viewer.timeline.shouldAnimate = false;
  // 停止动画实体
  viewer.trackedEntity = undefined;
  modalVisible.value = false;
}
function openModal(jobId: string) {
  modalVisible.value = true;
  nextTick(() => {
    initMap().then(res => {
      console.log(res);
      loadTrackData(jobId);
    });
  });
}
defineExpose({
  openModal
});
</script>

<style lang="scss">
.track-map-box {
  width: 100%;
  height: 100%;
}
#track-map-container {
  .cesium-viewer-animationContainer {
    display: none;
  }
  .cesium-viewer-timelineContainer {
    display: none;
  }
}
</style>