Skip to content

模型动画效果

参考博客:Cesium案例-三维模型的轨迹运动(动画控制)

时钟设置

vue
<template>
	<div id="cesiumContainer"></div>
</template>

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

  Cesium.Ion.defaultAccessToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkMGM1ODNmMi01NjgxLTRiNjYtYjEzYS0xMWZhODZlNDIyOWIiLCJpZCI6MTE2MzEzLCJpYXQiOjE3MzY2NjMwNjN9.tTeB32oDNJyNSn7iecCvMb2O5ETRw56CmX_OCSsMu34"

  let viewer: Cesium.Viewer

  onMounted(() => {
    initMap()
  })

  async function initMap() {
    viewer = new Cesium.Viewer("cesiumContainer", {
      infoBox: false,
      // 不设置该属性,模型的动画不会显示
      shouldAnimate: true,
      terrainProvider: await Cesium.createWorldTerrainAsync({
        requestWaterMask: true,
        requestVertexNormals: true
      })
    })

    /** 时钟设置 */
    const startTime = Cesium.JulianDate.fromDate(new Date(2025, 2, 16, 17, 0, 0))
    const stopTime = Cesium.JulianDate.addSeconds(
      startTime,
      360,
      new Cesium.JulianDate()
    )

    viewer.clock.startTime = startTime.clone()
    viewer.clock.stopTime = stopTime.clone()
    viewer.clock.multiplier = 20 // 时钟速度调为正常的 20 倍
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 开启时钟动画,循环播放
    viewer.timeline.zoomTo(startTime, stopTime) // 时间轴固定到指定范围

    viewer.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(-122.541, 37.815, 20000),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-90),
        roll: 0
      }
    })
  }
</script>

时钟设置

Entity实体加载与路线绘制

vue
<template>
	<div id="cesiumContainer"></div>
</template>

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

  Cesium.Ion.defaultAccessToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkMGM1ODNmMi01NjgxLTRiNjYtYjEzYS0xMWZhODZlNDIyOWIiLCJpZCI6MTE2MzEzLCJpYXQiOjE3MzY2NjMwNjN9.tTeB32oDNJyNSn7iecCvMb2O5ETRw56CmX_OCSsMu34"

  let viewer: Cesium.Viewer
  let planeModel: Cesium.Entity
  let positionProperty: Cesium.SampledPositionProperty

  onMounted(() => {
    initMap()
  })

  async function initMap() {
    viewer = new Cesium.Viewer("cesiumContainer", {
      infoBox: false,
      // 不设置该属性,模型的动画不会显示
      shouldAnimate: true,
      terrainProvider: await Cesium.createWorldTerrainAsync({
        requestWaterMask: true,
        requestVertexNormals: true
      })
    })

    /** 时钟设置 */
    // Cesium.JulianDate.now()可以获取当前时间
    const startTime = Cesium.JulianDate.fromDate(new Date(2025, 2, 16, 17, 0, 0))
    const stopTime = Cesium.JulianDate.addSeconds(
      startTime,
      360,
      new Cesium.JulianDate()
    )

    viewer.clock.startTime = startTime.clone()
    viewer.clock.stopTime = stopTime.clone()
    viewer.clock.multiplier = 20 // 时钟速度调为正常的 20 倍
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 开启时钟动画,循环播放
    viewer.timeline.zoomTo(startTime, stopTime) // 时间轴固定到指定范围

    viewer.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(-122.541, 37.815, 20000),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-90),
        roll: 0
      }
    })

    /** Entity实体加载与线路绘制 */
    const dataPoints = [
      { longitude: -122.567, latitude: 37.843, height: 200 },
      { longitude: -122.537, latitude: 37.817, height: 500 },
      { longitude: -122.519, latitude: 37.811, height: 800 },
      { longitude: -122.475, latitude: 37.822, height: 500 },
      { longitude: -122.464, latitude: 37.854, height: 300 },
      { longitude: -122.438, latitude: 37.874, height: 500 }
    ]
    positionProperty = new Cesium.SampledPositionProperty()
    for (let i = 0; i < dataPoints.length; i++) {
      const time = Cesium.JulianDate.addSeconds(startTime, i * 72, new Cesium.JulianDate())
      const position = Cesium.Cartesian3.fromDegrees(
        dataPoints[i].longitude,
        dataPoints[i].latitude
      )
      // 用于创建具有时间依赖性的位置点动画
      positionProperty.addSample(time, position)

      // 将每一个点添加到地图中
      viewer.entities.add({
        position: position,
        point: {
          show: true,
          pixelSize: 8,
          color: Cesium.Color.TRANSPARENT,
          outlineColor: Cesium.Color.ORANGE,
          outlineWidth: 3
        }
      })
    }
</script>

Entity点绘制

飞机模型加载

vue
<template>
	<div id="cesiumContainer"></div>
</template>

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

  Cesium.Ion.defaultAccessToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkMGM1ODNmMi01NjgxLTRiNjYtYjEzYS0xMWZhODZlNDIyOWIiLCJpZCI6MTE2MzEzLCJpYXQiOjE3MzY2NjMwNjN9.tTeB32oDNJyNSn7iecCvMb2O5ETRw56CmX_OCSsMu34"

  let viewer: Cesium.Viewer
  let planeModel: Cesium.Entity
  let positionProperty: Cesium.SampledPositionProperty

  onMounted(() => {
    initMap()
  })

  async function initMap() {
    viewer = new Cesium.Viewer("cesiumContainer", {
      infoBox: false,
      // 不设置该属性,模型的动画不会显示
      shouldAnimate: true,
      terrainProvider: await Cesium.createWorldTerrainAsync({
        requestWaterMask: true,
        requestVertexNormals: true
      })
    })

    /** 时钟设置 */
    // Cesium.JulianDate.now()可以获取当前时间
    const startTime = Cesium.JulianDate.fromDate(new Date(2025, 2, 16, 17, 0, 0))
    const stopTime = Cesium.JulianDate.addSeconds(
      startTime,
      360,
      new Cesium.JulianDate()
    )

    viewer.clock.startTime = startTime.clone()
    viewer.clock.stopTime = stopTime.clone()
    viewer.clock.multiplier = 20 // 时钟速度调为正常的 20 倍
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 开启时钟动画,循环播放
    viewer.timeline.zoomTo(startTime, stopTime) // 时间轴固定到指定范围

    viewer.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(-122.541, 37.815, 20000),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-90),
        roll: 0
      }
    })

    /** Entity实体加载与线路绘制 */
    const dataPoints = [
      { longitude: -122.567, latitude: 37.843, height: 200 },
      { longitude: -122.537, latitude: 37.817, height: 500 },
      { longitude: -122.519, latitude: 37.811, height: 800 },
      { longitude: -122.475, latitude: 37.822, height: 500 },
      { longitude: -122.464, latitude: 37.854, height: 300 },
      { longitude: -122.438, latitude: 37.874, height: 500 }
    ]
    positionProperty = new Cesium.SampledPositionProperty()
    for (let i = 0; i < dataPoints.length; i++) {
      const time = Cesium.JulianDate.addSeconds(
        startTime,
        i * 72,
        new Cesium.JulianDate()
      )
      const position = Cesium.Cartesian3.fromDegrees(
        dataPoints[i].longitude,
        dataPoints[i].latitude
      )
      // 用于创建具有时间依赖性的位置点动画
      positionProperty.addSample(time, position)

      // 将每一个点添加到地图中
      viewer.entities.add({
        position: position,
        point: {
          show: true,
          pixelSize: 8,
          color: Cesium.Color.TRANSPARENT,
          outlineColor: Cesium.Color.ORANGE,
          outlineWidth: 3
        }
      })
    }

    /** 飞机模型加载 */
    planeModel = viewer.entities.add({
      id: "plane-408",
      name: "plane-408-无人机",
      // availability属性用于控制实体在时间轴上的有效性,它允许你指定实体在特定的时间段内才可见。
      // 如果不指定 availability 属性,则实体会在整个事件范围内可见。
      availability: new Cesium.TimeIntervalCollection([
        new Cesium.TimeInterval({
          start: startTime,
          stop: stopTime
        })
      ]),
      // Cesium.VelocityOrientationProperty 用于根据实体的速度自动计算和更新其朝向。
      // 它基于实体的运动方向来确定朝向,使得实体能够在移动时保持正确的朝向。
      orientation: new Cesium.VelocityOrientationProperty(positionProperty),
      position: positionProperty,
      model: {
        scale: 10,
        uri: "/model/Cesium/Cesium_Air.glb",
        minimumPixelSize: 64
      },
      path: {
        resolution: 1,
        width: 15,
        material: new Cesium.PolylineGlowMaterialProperty({
          // 发光的强度
          glowPower: 0.1,
          // path路径的渐缩效果,1.0或不设置代表不会设置渐缩效果
          taperPower: 1.0,
          color: Cesium.Color.ORANGE
        })
      }
    })
  }
</script>

飞机模型加载

视角切换

vue
<template>
  <div id="cesiumContainer">
    <div id="buttonGroup">
      <button @click="changeView('down')">俯视视图</button>
      <button @click="changeView('entity')">实体视图</button>
      <button @click="changeView('side')">侧面视图</button>
    </div>
  </div>
</template>

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

  Cesium.Ion.defaultAccessToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkMGM1ODNmMi01NjgxLTRiNjYtYjEzYS0xMWZhODZlNDIyOWIiLCJpZCI6MTE2MzEzLCJpYXQiOjE3MzY2NjMwNjN9.tTeB32oDNJyNSn7iecCvMb2O5ETRw56CmX_OCSsMu34"

  let viewer: Cesium.Viewer
  let planeModel: Cesium.Entity
  let positionProperty: Cesium.SampledPositionProperty

  onMounted(() => {
    initMap()
  })

  async function initMap() {
    viewer = new Cesium.Viewer("cesiumContainer", {
      infoBox: false,
      // 不设置该属性,模型的动画不会显示
      shouldAnimate: true,
      terrainProvider: await Cesium.createWorldTerrainAsync({
        requestWaterMask: true,
        requestVertexNormals: true
      })
    })

    /** 时钟设置 */
    // Cesium.JulianDate.now()可以获取当前时间
    const startTime = Cesium.JulianDate.fromDate(new Date(2025, 2, 16, 17, 0, 0))
    const stopTime = Cesium.JulianDate.addSeconds(
      startTime,
      360,
      new Cesium.JulianDate()
    )

    viewer.clock.startTime = startTime.clone()
    viewer.clock.stopTime = stopTime.clone()
    viewer.clock.multiplier = 20 // 时钟速度调为正常的 20 倍
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 开启时钟动画,循环播放
    viewer.timeline.zoomTo(startTime, stopTime) // 时间轴固定到指定范围

    viewer.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(-122.541, 37.815, 20000),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-90),
        roll: 0
      }
    })

    /** Entity实体加载与线路绘制 */
    const dataPoints = [
      { longitude: -122.567, latitude: 37.843, height: 200 },
      { longitude: -122.537, latitude: 37.817, height: 500 },
      { longitude: -122.519, latitude: 37.811, height: 800 },
      { longitude: -122.475, latitude: 37.822, height: 500 },
      { longitude: -122.464, latitude: 37.854, height: 300 },
      { longitude: -122.438, latitude: 37.874, height: 500 }
    ]
    positionProperty = new Cesium.SampledPositionProperty()
    for (let i = 0; i < dataPoints.length; i++) {
      const time = Cesium.JulianDate.addSeconds(
        startTime,
        i * 72,
        new Cesium.JulianDate()
      )
      const position = Cesium.Cartesian3.fromDegrees(
        dataPoints[i].longitude,
        dataPoints[i].latitude
      )
      // 用于创建具有时间依赖性的位置点动画
      positionProperty.addSample(time, position)

      // 将每一个点添加到地图中
      viewer.entities.add({
        position: position,
        point: {
          show: true,
          pixelSize: 8,
          color: Cesium.Color.TRANSPARENT,
          outlineColor: Cesium.Color.ORANGE,
          outlineWidth: 3
        }
      })
    }

    /** 飞机模型加载 */
    planeModel = viewer.entities.add({
      id: "plane-408",
      name: "plane-408-无人机",
      // availability属性用于控制实体在时间轴上的有效性,它允许你指定实体在特定的时间段内才可见。
      // 如果不指定 availability 属性,则实体会在整个事件范围内可见。
      availability: new Cesium.TimeIntervalCollection([
        new Cesium.TimeInterval({
          start: startTime,
          stop: stopTime
        })
      ]),
      // Cesium.VelocityOrientationProperty 用于根据实体的速度自动计算和更新其朝向。
      // 它基于实体的运动方向来确定朝向,使得实体能够在移动时保持正确的朝向。
      orientation: new Cesium.VelocityOrientationProperty(positionProperty),
      position: positionProperty,
      model: {
        scale: 10,
        uri: "/model/Cesium/Cesium_Air.glb",
        minimumPixelSize: 64
      },
      path: {
        resolution: 1,
        width: 15,
        material: new Cesium.PolylineGlowMaterialProperty({
          // 发光的强度
          glowPower: 0.1,
          // path路径的渐缩效果,1.0或不设置代表不会设置渐缩效果
          taperPower: 1.0,
          color: Cesium.Color.ORANGE
        })
      }
    })
  }

  function changeView(type: string) {
    switch (type) {
      case "down":
        viewer.zoomTo(
          viewer.entities,
          new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90))
        )
        break
      case "entity":
        viewer.trackedEntity = planeModel
        break
      case "side":
        viewer.zoomTo(
          planeModel,
          new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-30), 4000)
        )
        break
      default:
        break
    }
  }
</script>

<style scoped>
  #buttonGroup {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 9999;
  }

  #buttonGroup button {
    margin-right: 8px;
  }
</style>

侧面视图

路径过渡效果

vue
<template>
  <div id="cesiumContainer">
    <div id="buttonGroup">
      <button @click="changeView('down')">俯视视图</button>
      <button @click="changeView('entity')">实体视图</button>
      <button @click="changeView('side')">侧面视图</button>
    </div>

    <div id="selectGroup">
      <select @change="handleSelect">
        <option value="line">线性近似插值路径</option>
        <option value="hermite">二次赫米尔多项式近似插值路径</option>
        <option value="lagrange">拉格朗日多项式近似插值路径</option>
    	</select>
    </div>
  </div>
</template>

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

  Cesium.Ion.defaultAccessToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkMGM1ODNmMi01NjgxLTRiNjYtYjEzYS0xMWZhODZlNDIyOWIiLCJpZCI6MTE2MzEzLCJpYXQiOjE3MzY2NjMwNjN9.tTeB32oDNJyNSn7iecCvMb2O5ETRw56CmX_OCSsMu34"

  let viewer: Cesium.Viewer
  let planeModel: Cesium.Entity
  let positionProperty: Cesium.SampledPositionProperty

  onMounted(() => {
    initMap()
  })

  async function initMap() {
    viewer = new Cesium.Viewer("cesiumContainer", {
      infoBox: false,
      // 不设置该属性,模型的动画不会显示
      shouldAnimate: true,
      terrainProvider: await Cesium.createWorldTerrainAsync({
        requestWaterMask: true,
        requestVertexNormals: true
      })
    })

    /** 时钟设置 */
    // Cesium.JulianDate.now()可以获取当前时间
    const startTime = Cesium.JulianDate.fromDate(new Date(2025, 2, 16, 17, 0, 0))
    const stopTime = Cesium.JulianDate.addSeconds(
      startTime,
      360,
      new Cesium.JulianDate()
    )

    viewer.clock.startTime = startTime.clone()
    viewer.clock.stopTime = stopTime.clone()
    viewer.clock.multiplier = 20 // 时钟速度调为正常的 20 倍
    viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP // 开启时钟动画,循环播放
    viewer.timeline.zoomTo(startTime, stopTime) // 时间轴固定到指定范围

    viewer.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(-122.541, 37.815, 20000),
      orientation: {
        heading: Cesium.Math.toRadians(0),
        pitch: Cesium.Math.toRadians(-90),
        roll: 0
      }
    })

    /** Entity实体加载与线路绘制 */
    const dataPoints = [
      { longitude: -122.567, latitude: 37.843, height: 200 },
      { longitude: -122.537, latitude: 37.817, height: 500 },
      { longitude: -122.519, latitude: 37.811, height: 800 },
      { longitude: -122.475, latitude: 37.822, height: 500 },
      { longitude: -122.464, latitude: 37.854, height: 300 },
      { longitude: -122.438, latitude: 37.874, height: 500 }
    ]
    positionProperty = new Cesium.SampledPositionProperty()
    for (let i = 0; i < dataPoints.length; i++) {
      const time = Cesium.JulianDate.addSeconds(
        startTime,
        i * 72,
        new Cesium.JulianDate()
      )
      const position = Cesium.Cartesian3.fromDegrees(
        dataPoints[i].longitude,
        dataPoints[i].latitude
      )
      // 用于创建具有时间依赖性的位置点动画
      positionProperty.addSample(time, position)

      // 将每一个点添加到地图中
      viewer.entities.add({
        position: position,
        point: {
          show: true,
          pixelSize: 8,
          color: Cesium.Color.TRANSPARENT,
          outlineColor: Cesium.Color.ORANGE,
          outlineWidth: 3
        }
      })
    }

    /** 飞机模型加载 */
    planeModel = viewer.entities.add({
      id: "plane-408",
      name: "plane-408-无人机",
      // availability属性用于控制实体在时间轴上的有效性,它允许你指定实体在特定的时间段内才可见。
      // 如果不指定 availability 属性,则实体会在整个事件范围内可见。
      availability: new Cesium.TimeIntervalCollection([
        new Cesium.TimeInterval({
          start: startTime,
          stop: stopTime
        })
      ]),
      // Cesium.VelocityOrientationProperty 用于根据实体的速度自动计算和更新其朝向。
      // 它基于实体的运动方向来确定朝向,使得实体能够在移动时保持正确的朝向。
      orientation: new Cesium.VelocityOrientationProperty(positionProperty),
      position: positionProperty,
      model: {
        scale: 10,
        uri: "/model/Cesium/Cesium_Air.glb",
        minimumPixelSize: 64
      },
      path: {
        resolution: 1,
        width: 15,
        material: new Cesium.PolylineGlowMaterialProperty({
          // 发光的强度
          glowPower: 0.1,
          // path路径的渐缩效果,1.0或不设置代表不会设置渐缩效果
          taperPower: 1.0,
          color: Cesium.Color.ORANGE
        })
      }
    })
  }

  function changeView(type: string) {
    switch (type) {
      case "down":
        viewer.zoomTo(
          viewer.entities,
          new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90))
        )
        break
      case "entity":
        viewer.trackedEntity = planeModel
        break
      case "side":
        viewer.zoomTo(
          planeModel,
          new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-30), 4000)
        )
        break
      default:
        break
    }
  }

  function handleSelect(e: any) {
    switch (e.target.value) {
      case "hermite":
        positionProperty.setInterpolationOptions({
          interpolationDegree: 2, // 二次赫米尔多项式插值
          interpolationAlgorithm: Cesium.HermitePolynomialApproximation
        })
        planeModel.position = positionProperty
        break
      case "lagrange":
        positionProperty.setInterpolationOptions({
          interpolationDegree: 5, // 五次拉格朗日多项式插值
          interpolationAlgorithm: Cesium.LagrangePolynomialApproximation
        })
        break
      case "line":
      default:
        positionProperty.setInterpolationOptions({
          interpolationDegree: 1,
          interpolationAlgorithm: Cesium.LinearApproximation
        })
        planeModel.position = positionProperty
        break
    }
  }
</script>

<style scoped>
  #buttonGroup {
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 9999;
  }

  #buttonGroup button {
    margin-right: 8px;
  }

  #selectGroup {
    position: absolute;
    top: 40px;
    left: 10px;
    z-index: 9999;
  }
</style>

拉格朗日多项式插值路径

Released under the MIT License.