Cesium案例-三维模型的轨迹运动(动画控制)

前言

  本章讲述的是在Cesium中实现飞机模型动画的案例,主要实现实体路线绘制、场景视图切换、路径运动过渡效果等功能。也是作为官网实例的练习。如果还不太了解什么是Cesium,可以参见Cesium入门教程的博客(如下)。后续也会更新Cesium的相关实例,帮助自己以及小伙伴们进一步了解Cesium。
实例:https://sandcastle.cesium.com/?src=Interpolation.html&label=All
Cesium入门教程:https://blog.csdn.net/qq_58978006/article/details/141418040

效果:
在这里插入图片描述

代码实现

<template>
  <div id="customContainer">
    <div id="buttonContainer">
      <button id="downViewButton">俯视视图</button>
      <button id="trackViewButton">实体视图</button>
      <button id="sideViewButton">侧面视图</button>
    </div>
    <div id="selectContainer">
      <select id="select">
        <option value="LinearApproximation">线性近似插值路径</option>
        <option value="HermitePolynomialApproximation">Hermite 多项式近似插值</option>
        <option value="LagrangePolynomialApproximation">拉格朗日多项式近似插值路径</option>
      </select>
    </div>
  </div>
  <div id="cesiumContainer" style="width: 100%"></div>
</template>

一、初始化地图与viewer.clock时间配置

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

const init = async () => {
  //token权限需要在官网获取,详情可看Cesium入门教程指导
  Cesium.Ion.defaultAccessToken =  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiMmU4NTA0Ni1hM2VjLTQwYzItOTBhZi00NmNkM2I2MjFhMmMiLCJpZCI6MjM2MDEzLCJpYXQiOjE3MjQyMDg0ODR9.CB3bMqYbHybdGa3cHoTHdQgTh3DchtTOJ_Grosr3YfU";
  const viewer = new Cesium.Viewer("cesiumContainer", {
    //需要的配置项否则会出现报错,详见问题注意点
    infoBox: false,
    //如果不设置添加的模型实体动画不显示
    shouldAnimate: true,//控制视图是否在动画中,用于启用或禁用视图动画
    //三维展示地形地势、水形数据
    terrainProvider: await Cesium.createWorldTerrainAsync({
      requestWaterMask: true,
      requestVertexNormals: true,
    }),
  });
  //new Date(2024, 5, 20, 17, 30, 21),表示 2024 年 6 月 20 日 17:30:21
  //可以使用 Cesium.JulianDate.now() 来表示当前的事件时间。这个方法会获取当前的 JulianDate
  const startTime = Cesium.JulianDate.fromDate(new Date(2024, 5, 20, 17));
  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, 2000),
    orientation: {
      heading: Cesium.Math.toRadians(90),
      pitch: Cesium.Math.toRadians(-20),
      roll: 0,
    },
  });

在这里插入图片描述

参数补充说明:

Cesium.JulianDate.addSeconds(date, seconds, result): 一个静态方法,用于在给定的 JulianDate 对象上添加指定的秒数,从而生成一个新的 JulianDate 对象。
date: 原始的 Cesium.JulianDate 对象。seconds: 要添加的秒数,可以是正数或负数。result: 可选的 Cesium.JulianDate 对象,用于存放结果。如果未提供,将返回一个新的 JulianDate 对象。

二、Entity实体加载与路线绘制

  //点位数组
  const dataPoint = [
    { 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 },
  ];
  let positionProperty = new Cesium.SampledPositionProperty();
  for (let i = 0; i < dataPoint.length; i++) {
    const time = Cesium.JulianDate.addSeconds(startTime, i * 72, new Cesium.JulianDate());
    const position = Cesium.Cartesian3.fromDegrees(dataPoint[i].longitude, dataPoint[i].latitude, dataPoint[i].height);
    positionProperty.addSample(time, position);
    viewer.entities.add({
      position: position,
      point: {
        color: Cesium.Color.TRANSPARENT,
        pixelSize: 8,
        outlineColor: Cesium.Color.ORANGE,
        outlineWidth: 3,
      },
    });
  }
  let entity = viewer.entities.add({
    id: "408",
    name: "王牌战斗机",
    //availability 属性用于控制实体在时间上的可见性或有效性。它允许你指定实体在特定时间段内才可见或活动
    //如果不设置 availability 属性,实体将默认在整个时间范围内可见
    availability: new Cesium.TimeIntervalCollection([
      new Cesium.TimeInterval({
        start: startTime,
        stop: stopTime,
      }),
    ]),
    //Cesium.VelocityOrientationProperty 是 Cesium 中的一个类,用于根据实体的速度自动计算和更新其朝向。它基于实体的运动方向来确定朝向,使得实体能够在移动时保持正确的朝向
    orientation: new Cesium.VelocityOrientationProperty(positionProperty),
    position: positionProperty,
    model: {
      scale: 10,
      uri: "/models/Cesium_Air.glb",
      minimumPixelSize: 64,
    },
    path: {
      resolution: 1,
      width: 15,
      material: new Cesium.PolylineGlowMaterialProperty({
        //一个数字属性,指定光晕的强度,以总线宽度的百分比表示
        glowPower: 0.1,
        color: Cesium.Color.ORANGE,
        //一个数字属性,指定渐缩效果的强度,以总线长的百分比表示。如果为 1.0 或更高,则不使用渐缩效果
        //taperPower:0.5
      }),
    },
  });

在这里插入图片描述

参数补充说明:

Cesium.SampledPositionProperty: Cesium 中用于管理和插值实体位置的类。它允许你在一系列时间点上指定位置,并根据这些位置在其他时间点上进行插值。适用于需要动态调整位置的实体,如动画、轨迹等。
addSample(time, position): 通常用于将时间点和对应位置添加到 SampledPositionProperty 对象中。这可以用于创建具有时间依赖性的对象位置动画,例如动画轨迹或动态物体的位置更新。
time:Cesium.JulianDate 对象,表示样本的时间点。position:Cesium.Cartesian3 对象,表示在给定时间点的位置。

三、场景视图切换

  //添加自定义按钮(相机视角切换)
  let downViewButton = document.getElementById("downViewButton");
  let trackViewButton = document.getElementById("trackViewButton");
  let sideViewButton = document.getElementById("sideViewButton");

  downViewButton?.addEventListener("click", function () {
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90)));
  });
  trackViewButton?.addEventListener("click", function () {
    viewer.trackedEntity = entity;
  });
  sideViewButton?.addEventListener("click", function () {
    viewer.zoomTo(entity, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(0), Cesium.Math.toRadians(-30), 4000));
  });

在这里插入图片描述

参数补充说明:

Cesium.HeadingPitchRange(heading, pitch, range): Cesium 中用于定义相机视角的一个构造函数,它主要用来描述相机的位置和朝向。
heading: 相机的航向角,表示相机的朝向方向。航向角通常是绕着垂直于地面的轴旋转的角度,以弧度为单位。0 弧度表示相机面向东,90 弧度表示相机面向北。
pitch: 相机的俯仰角,表示相机的俯仰程度。俯仰角是相机相对于水平面的倾斜角度,以弧度为单位。0 弧度表示相机与地面平行,90 弧度表示相机朝向天空。
range: 相机到目标点的距离,以米为单位。这个值决定了相机与目标点之间的空间距离,从而影响视角的缩放程度。

四、路径过渡效果插值变化

  //添加自定义选项菜单(路径效果处理)
  //document.getElementById('select') 获取到的是一个 HTMLElement 类型
  //因为 HTMLElement 类型没有 value 属性,所以需要将其类型断言为 HTMLSelectElement 类型
  let selectItem = document.getElementById("select") as HTMLSelectElement;
  selectItem?.addEventListener("change", function () {
    switch (this.value) {
      case "LinearApproximation":
        //设置插值选项
        //线性插值进行平滑过渡
        //positionProperty是前面创建的一个 SampledPositionProperty 对象
        positionProperty.setInterpolationOptions({
          interpolationDegree: 1, //指定了插值的多项式度数,这里使用的是线性插值(度数为 1)
          interpolationAlgorithm: Cesium.LinearApproximation, //选择了线性插值算法,这是一种简单的插值方法,适用于在位置数据之间进行线性过渡
        });
        //将 sampledPositionProperty 赋值给 entity 的 position 属性
        entity.position = positionProperty;
        break;
      case "HermitePolynomialApproximation":
        //二次赫尔米特多项式进行插值实现相对平滑且连续的过度效果
        positionProperty.setInterpolationOptions({
          //二次多项式进行插值。二次多项式插值相较于线性插值会更光滑,但不如高阶多项式平滑
          interpolationDegree: 2, 
          //赫尔米特多项式插值算法。使得插值曲线能够平滑过渡,并且可以更好地控制运动的速度和方向变化
          interpolationAlgorithm: Cesium.HermitePolynomialApproximation, 
        });
        entity.position = positionProperty;
        break;
      case "LagrangePolynomialApproximation":
        //五次拉格朗日多项式进行插值实现更加平滑的过渡效果
        positionProperty.setInterpolationOptions({
          //插值的多项式度数为 5。多项式度数决定了插值的复杂程度和光滑程度。度数为 5 表示使用五次多项式进行插值,这通常会使插值结果更加平滑
          interpolationDegree: 5, 
          //拉格朗日多项式插值算法。拉格朗日多项式插值算法适用于需要高精度的光滑插值场景
          interpolationAlgorithm: Cesium.LagrangePolynomialApproximation,
        });
        entity.position = positionProperty;
        break;
      default:
        break;
    }
  });
};

onMounted(() => {
  init();
});
</script>

在这里插入图片描述

参数补充说明:

SampledPositionProperty: 是支持插值选项的类。setInterpolationOptions 方法是 SampledPositionProperty 类中的方法,用来设置在对位置进行插值时要使用的算法和次数。

<style lang="scss">
#customContainer {
  position: absolute;
  z-index: 999;
  padding: 20px;
  #buttonContainer {
    margin-bottom: 5px;
    #trackViewButton {
      margin: 0 10px;
    }
  }
}
</style>

内容补充

如何获取Cesium库的三维模型

获取当前项目的 Cesium_Air 模型,直接点击下方链接下载即可

glb模型下载:https://sandcastle.cesium.com/SampleData/models/CesiumAir/Cesium_Air.glb

获取其他Cesium的glb三维模型

  • 由于Cesium官方并没有提供可直接下载的链接,所以我将其中的一部分glb文件下载存放到了仓库,可以访问我的Gitee仓库然后根据需要下载对应的模型即可。
  • 如果想要获取更全的模型,可以参考下方直接从GitHub上下载Cesium项目,然后获取。

Gitee仓库地址:https://gitee.com/zhang-zhen-yang/cesium_model_upload.git

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当然你也可以直接从GitHub上把Cesium项目下载下来就能直接复制上面的glb文件了

Cesium下载参考:https://blog.csdn.net/qq_58978006/article/details/142065403

下载完成后从根目录 Apps > SampleData> models 下的文件夹中获取glb文件复制到项目中即可

在这里插入图片描述

viewer.zoomTo和viewer.camera.setView对比

viewer.zoomTo 和 viewer.camera.setView 都用于调整视图,但它们的使用场景和功能有所不同。

viewer.zoomTo(target, offset)

// 聚焦单个实体
viewer.zoomTo(entity);
// 聚焦实体集合
viewer.zoomTo(viewer.entities);

// 定义偏移量
var offset = new Cesium.HeadingPitchRange(
    Cesium.Math.toRadians(45.0), // heading:相机将偏转 45 度。0 弧度表示正北方向,90 弧度表示正东方向
    Cesium.Math.toRadians(-30.0), // pitch:相机将向下倾斜 30 度。负值表示向下看,正值表示向上看
    5000 // range (距离),设置为 5000,表示相机与目标之间的距离为 5000 米
);

// 聚焦单个实体并应用偏移量
viewer.zoomTo(entity, offset);
  • viewer.zoomTo 方法用于将视图调整到指定的实体或实体集合上。当你需要让视图自动聚焦到一个或多个实体上时,使用 viewer.zoomTo 是合适的。它会自动计算所需的视角和缩放级别,以确保所有指定的实体都在视图中。

viewer.camera.setView

viewer.camera.setView({
  destination : Cesium.Cartesian3.fromDegrees(-75.10, 40.71, 100000.0),
  orientation : {
    heading : Cesium.Math.toRadians(90.0), // heading:相机将偏转 90 度。0 弧度表示正北方向,90 弧度表示正东方向
    pitch : Cesium.Math.toRadians(-30.0), // pitch:相机将向下倾斜 30 度。负值表示向下看,正值表示向上看
    roll : 0.0,// roll:摄像机的滚转角,以弧度为单位。表示摄像机绕其自身轴线的旋转
  }
});
  • viewer.camera.setView 方法用于直接设置相机的视角和位置。 当你需要精确地控制相机的视角和位置时,viewer.camera.setView 是合适的。这通常用于需要自定义视角或动画效果的场景。
  • viewer.camera.setView 接受一个对象,这个对象可以包含以下属性:
    • destination:相机的位置(通常是 Cartesian3 坐标)。
    • orientation:相机的方向和视角,包括heading(方位角)、pitch(俯仰角)和 roll(滚动角)。

问题注意点

cesium报错
在这里插入图片描述
解决方法
将infoBox设为false即可

const viewer = new Cesium.Viewer("cesiumContainer", {
    infoBox: false,
});

完整代码参考

<template>
  <div id="customContainer">
    <div id="buttonContainer">
      <button id="downViewButton">俯视视图</button>
      <button id="trackViewButton">实体视图</button>
      <button id="sideViewButton">侧面视图</button>
    </div>
    <div id="selectContainer">
      <select id="select">
        <option value="LinearApproximation">线性近似插值路径</option>
        <option value="HermitePolynomialApproximation">Hermite 多项式近似插值</option>
        <option value="LagrangePolynomialApproximation">拉格朗日多项式近似插值路径</option>
      </select>
    </div>
  </div>
  <div id="cesiumContainer" style="width: 100%"></div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import * as Cesium from "cesium";
import "cesium/Build/Cesium/Cesium";

//一、初始化地图与viewer.clock时间配置
const init = async () => {
  Cesium.Ion.defaultAccessToken =
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiMmU4NTA0Ni1hM2VjLTQwYzItOTBhZi00NmNkM2I2MjFhMmMiLCJpZCI6MjM2MDEzLCJpYXQiOjE3MjQyMDg0ODR9.CB3bMqYbHybdGa3cHoTHdQgTh3DchtTOJ_Grosr3YfU";
  const viewer = new Cesium.Viewer("cesiumContainer", {
    infoBox: false,
    shouldAnimate: true,
    terrainProvider: await Cesium.createWorldTerrainAsync({
      requestWaterMask: true,
      requestVertexNormals: true,
    }),
  });
  const startTime = Cesium.JulianDate.fromDate(new Date(2024, 5, 20, 17));
  const stopTime = Cesium.JulianDate.addSeconds(startTime, 360, new Cesium.JulianDate());
  viewer.clock.startTime = startTime.clone();
  viewer.clock.stopTime = stopTime.clone();
  viewer.clock.multiplier = 20;
  viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
  viewer.timeline.zoomTo(startTime, stopTime);
  viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(-122.541, 37.815, 2000),
    orientation: {
      heading: Cesium.Math.toRadians(90),
      pitch: Cesium.Math.toRadians(-20),
      roll: 0,
    },
  });

  //二、Entity实体加载与路线绘制
  const dataPoint = [
    { 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 },
  ];
  let positionProperty = new Cesium.SampledPositionProperty();
  for (let i = 0; i < dataPoint.length; i++) {
    const time = Cesium.JulianDate.addSeconds(startTime, i * 72, new Cesium.JulianDate());
    const position = Cesium.Cartesian3.fromDegrees(dataPoint[i].longitude, dataPoint[i].latitude, dataPoint[i].height);
    positionProperty.addSample(time, position);
    viewer.entities.add({
      position: position,
      point: {
        color: Cesium.Color.TRANSPARENT,
        pixelSize: 8,
        outlineColor: Cesium.Color.ORANGE,
        outlineWidth: 3,
      },
    });
  }
  let entity = viewer.entities.add({
    id: "408",
    name: "王牌战斗机",
    availability: new Cesium.TimeIntervalCollection([
      new Cesium.TimeInterval({
        start: startTime,
        stop: stopTime,
      }),
    ]),
    orientation: new Cesium.VelocityOrientationProperty(positionProperty),
    position: positionProperty,
    model: {
      scale: 10,
      uri: "/models/Cesium_Air.glb",
      minimumPixelSize: 64,
    },
    path: {
      resolution: 1,
      width: 15,
      material: new Cesium.PolylineGlowMaterialProperty({
        glowPower: 0.1,
        color: Cesium.Color.ORANGE,
      }),
    },
  });

  //三、场景视图切换
  let downViewButton = document.getElementById("downViewButton");
  let trackViewButton = document.getElementById("trackViewButton");
  let sideViewButton = document.getElementById("sideViewButton");

  downViewButton?.addEventListener("click", function () {
    viewer.zoomTo(viewer.entities, new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90)));
  });
  trackViewButton?.addEventListener("click", function () {
    viewer.trackedEntity = entity;
  });
  sideViewButton?.addEventListener("click", function () {
    viewer.zoomTo(entity, new Cesium.HeadingPitchRange(Cesium.Math.toRadians(0), Cesium.Math.toRadians(-30), 4000));
  });

  //四、路径过渡效果插值变化
  let selectItem = document.getElementById("select") as HTMLSelectElement;
  selectItem?.addEventListener("change", function () {
    switch (this.value) {
      case "LinearApproximation":
        positionProperty.setInterpolationOptions({
          interpolationDegree: 1,
          interpolationAlgorithm: Cesium.LinearApproximation,
        });
        entity.position = positionProperty;
        break;
      case "HermitePolynomialApproximation":
        positionProperty.setInterpolationOptions({
          interpolationDegree: 2,
          interpolationAlgorithm: Cesium.HermitePolynomialApproximation,
        });
        entity.position = positionProperty;
        break;
      case "LagrangePolynomialApproximation":
        positionProperty.setInterpolationOptions({
          interpolationDegree: 5,
          interpolationAlgorithm: Cesium.LagrangePolynomialApproximation,
        });
        entity.position = positionProperty;
        break;
      default:
        break;
    }
  });
};

onMounted(() => {
  init();
});
</script>
<style lang="scss">
#customContainer {
  position: absolute;
  z-index: 999;
  padding: 20px;
  #buttonContainer {
    margin-bottom: 5px;
    #trackViewButton {
      margin: 0 10px;
    }
  }
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值