Skip to content

动目标

CZML

CZML是一种用来描述动态场景的JSON架构的语言(简单的可以理解为数组形式),它可以用来描述点、线、布告板、模型以及其他的图元,同时定义他们是怎样随时间变化的,CZML可以通到很多其他的程序自动生成,或者手写进行配置也可以

CZML可以准确的描述值随时间变化的属性,比如:一条线在某一时间内是红色的而在另一时间内是蓝色的;如有一辆车,分别定义了两个不同时间的位置,通过CZML定义的差值算法,客户端可以准确的显示在这两个时间点之间车的位置。CZML中所有的属性都可以是随时间的变化而变化

可以通过czml-writer来生成CZML

CZML的基本形式

js
let czml=[
    // packet1,id一定为document,否则会报错,这里定义的是整个显示场景的信息
    {
        id: "document",
        name: "SpaceX",
        version: "1.0",
        clock: {
          interval: "2019-08-28T04:00:00Z/2019-08-28T04:17:33Z",
          currentTime: "2019-08-28T04:00:00.00Z",
          multiplier: 1,
          range: "LOOP_STOP",
          step: "SYSTEM_CLOCK_MULTIPLIER"
         }
    },
    //packet two
    {
		...
	},
]
  • packet1代表了cesium场景(cesium时间轴的范围,当前时刻,倍速等信息),是必须要有的,其他的packet可以理解为描述某一时间范围内的entity的行为

  • 每个packet都有一个id属性用来标示我们当前描述的对象

    • id是必须要有的,假如没有指定id,那么客户端将自动生成一个唯一的id,但是这样的话在随后的包中我们就没有办法引用它了,例如我们不能再往它里面添加数据

    • id在同一个CZML以及与它载入同一个范围(scope)内的其他CZML文件中必须是唯一的,否则后面id的场景会覆盖前面相同id的场景

  • 除了id以外,一个包通常还包含1到多个定义对象图形特征的属性

  • 完整的CZML至少包合两个packet,第一个用于标识CZML,第二个packet对应一个场景中的对象,例如一架飞机,每个packet都有一个id属性,标示当前描述的对象

CZML常用属性

属性名称重要子属性
point(点)show,color,pixSize,outlineColor,outlineWidth,scaleByDistance,translucencyByDistance
polyline(折线)show,position,material,width,granularity,followSurface
rectangle(长方形)show,position,height,extrudedHeight,granularity,rotation,stRotation,fill,outline,outlineColor,outlineColor,outlineWidth,closeBottom,closeTop
polygon(多边形)show,postion,height,extrudedHeight,material,outline,outlineColor,outlineWidth,granularity,fill,perPositionHeight,stRotation
ellipse(椭圆)show,semiMajorAxis,semiMoinorAxis,rotation,material,height,extrudedHeight,granuarity,stRotation,fill,numberOfVerticalLines,outline,outlineColor,outlineWidth
box(盒子)show,dimensions,fill, material, outline,outlineColor,outlineWidth
corridor(走廊)show,positions,width,height,cornerType,extruded- Height,fill,material,outline,granularity,outlineColor,outlineWidth
cylinder(圆筒)show,length,topRadius,bottomRadius,fill,material,numberOfVerticalLines,slices,outlines,outlineColor,outlineWidth
polylineVolume(折线体积)show,position,shape,cornerType fill material,outline,granularity,outlineColor,outlineWidth
ellipsoid(椭球体)show,radii,fill,material, outline,outlineColor,outlineWidth,stackPartitions slicePartitions subdivisions
wall(墙)show,positions,material,minimumHeights,maximumHeights,granularity,fill, outline,outlineColor outlineWidth
model(模型)show,gltf,scale,runAnimations,incrementallyLoadTextures,minimumPixelSize,maximumScale,nodeTransformations
path(路径)show,material,width,resolution,leadTime trailTime
label(标签)show,textm style,font,scale,fillColor,outlineColor,outlineWidth,eyeOffset,translucencyByDistance,pixeloffset,horizontalOrigin,verticalOrigion,pixelOffsetScaleByDistance
billboard(广告牌)show,image,color,eyeOffset,horizontalOrigion,verticalOrigin,pixelOffset,scale,rotation,width,height,scaleByDistance,imageSubRegion,sizeInMeters,alignedAxis
id

描述对象的id,唯一标识CZML源中的单个对象以及加载到同一作用域的任何其他 CZML 源,我们可以通过getById来获取这个实体,如果未指定此属性,则客户端将自动生成一个唯一的属性,但是这样我们就无法获取这个实体了

name

对象的名称,不用唯一,用于提供给用户使用

parent

父对象或父文件的id

clock

整个数据集的时钟设置,仅对文档对象有效

名字类型描述
currentTime字符串当前时间,CZML文件模型的当前时间(一开始地球的时间),其中 Time ISO 8601 日期和时间字符串
multiplier动画播放倍数
range字符串时钟的当前时间到达其起点或终点时的行为,有效值为 'UNBOUNDED'(当时间到达时钟的起点或终点时,时钟会继续运行,不会停止或循环,当前时间可以超过起点或终点,继续向前或向后移动)、'CLAMPED'(当时间到达时钟的起点或终点时,时钟会停止在该时间点,不会继续向前或向后移动) 和 'LOOP_STOP'(当当前时间到达时钟的终点时,时钟会自动回滚到起点,并重新开始运行)
step字符串定义时钟在时间上的步进方式,有效值为 'SYSTEM_CLOCK'(时钟的当前时间与系统时钟同步。时钟的当前时间会根据系统时钟的实际时间进行更新,不受multiplier属性的影响)、'SYSTEM_CLOCK_MULTIPLIER' (时钟的当前时间与系统时钟同步,但可以通过multiplier属性调整时间步进的速度。multiplier大于1时,时钟运行速度加快;multiplier小于1时,时钟运行速度减慢)和 'TICK_DEPENDENT'(时钟的当前时间根据每次tick调用的时间间隔进行更新。时钟的步进速度由multiplier属性和每次tick的时间间隔决定)
version

正在编写的CZML 版本,仅对文档对象有效,CZML目前只有1.0版本,固定值

description

对象的HTML描述

可用性availability

availability属性用来表示一个对象的数据在什么时候是可用的,如果availability属性没有定义,那么默认是全部时间内都可用的,在一个流中,只有定义在最后的那个availability起作用,其他的都会被忽略,在某一时刻,如果一个对象是可用的,那么这个对象至少要有一个可用的属性并且在此时间段内需要的属性都要有定义(也就是获取到了数据),不然Cesium就会等待数据直到接收到数据为止

位置position

对象在世界中的位置属性,该属性的子属性有:

名字范围类型描述
referenceFrameInterval字符串指定笛卡尔位置的参考系,可能的值为“FIXED”“INERTIAL”,默认参考系为“FIXED”。此外,此属性的值可以是哈希 (#) 符号,后跟同一范围内另一个对象的 ID,该对象的“position”“orientation”属性定义定义此位置的参考帧。当使用笛卡尔以外的任何类型指定位置时,将忽略此属性
cartesianInterval数组以笛卡尔坐标[X,Y,Z]表示的相对于参考坐标系的位置,单位为米。如果数组有三个元素,则位置是恒定的。如果它有四个或更多元素,则它们是时间标记的样本,排列为 :[Time, X, Y, Z],其中 Time ISO 8601 日期和时间字符串或者来自epoch的间隔秒数
cartographicRadiansInterval数组WGS84 表示的位置[Longitude, Latitude, Height],其中经度和纬度以弧度为单位,高度以米为单位。如果数组有三个元素,则位置是恒定的。如果它有四个或更多元素,则它们是时间标记的样本,排列为 :[Time, Longitude, Latitude, Height],其中 Time ISO 8601 日期和时间字符串或者来自epoch的间隔秒数
cartographicDegreesInterval数组 WGS 84 表示位置[Longitude, Latitude, Height],其中经度和纬度以度为单位,高度以米为单位。如果数组有三个元素,则位置是恒定的。如果它有四个或更多元素,则它们是时间标记的样本,排列为 :[Time, Longitude, Latitude, Height],其中 Time ISO 8601 日期和时间字符串或者来自epoch的间隔秒数
cartesianVelocityInterval数组位置和速度以米为单位表示为相对于参照系的两个笛卡尔坐标[X,Y,Z,vX,vY,vZ]。如果数组有六个元素,则位置是恒定的。如果它有七个或更多元素,则它们是时间标记的样本,排列为[Time,X,Y,Z,vX,vY,vZ],其中 Time ISO 8601 日期和时间字符串或者来自epoch的间隔秒数
referenceInterval字符串参考属性
epochPacket字符串指定开始的时间
nextTimePacket字符串或数字此间隔内下一个样本的时间,指定为 ISO 8601 日期和时间字符串或来自epoch的间隔秒数。此属性用于确定不同数据包中指定的样本之间是否存在间隙
previousTimePacket字符串或数字此间隔内上一个样本的时间,指定为 ISO 8601 日期和时间字符串或来自epoch的间隔秒数。此属性用于确定不同数据包中指定的样本之间是否存在间隙
orientation

对象在世界中的方向(模型在世界中的方向)

子属性:referenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

名字类型描述
axes字符串使用三个正交轴(x轴、y轴和z轴)来定义实体的方向。每个轴都是一个Cartesian3对象,表示该轴在世界坐标系中的方向,axes适用于简单的方向定义,特别是当你只需要定义一个轴的方向时
unitQuaternion数组使用单位四元数(unit quaternion)来表示实体的方向,四元数是根据位置信息postion和航向(heading)、俯仰(pitch)和翻滚(roll)生成的,unitQuaternion适用于需要精确控制旋转的场景,四元数在处理旋转动画和复杂方向变化时更加稳定和高效
velocityReference字符串指定实体的方向与其设置矢量之间的关系,如方向的参考属性,指向实体的位置属性:"velocityReference": "#position"
时间间隔intervals

CZML数组中的每个元素对应每一个不同的时间,属性的值。时间间隔的定义使用interval 属性,通过ISO8601 interval格式的字面值表示

js
"material": {
  "solidColor": {
    "color": [
      {
        "interval": "2024-07-28T04:00:00Z/2024-07-28T04:10:00Z",
        "rgba": [ 0, 255, 0, 255 ]
      },
      {
        "interval": "2024-07-28T04:10:00Z/2024-07-28T04:13:00Z",
        "rgba": [ 255, 255, 0, 255 ]
      }
    ]
  }
}

我们定义了轨道线的颜色属性,它有两个时间间隔,第一个是从四点到四点十分,属性值为一种颜色,第二个是从四点十分到四点十三分,属性值为另一种颜色,在时间由第一个间隔变化到第二个间隔的时候,属性值会瞬间发生变化

注意,这里的时间为UTC(协调世界时),而我们国家通常使用的是UTC+8也就是北京时间

对与北京时间可以表示为:2024-07-28T04:00:00+08:00

interval属性是可选的,如果没有定义,默认为整个时间,表明该属性贯穿整个时间段

时间戳epoch

时间戳采样来控制位置信息

js
"epoch": "2019-08-28T04:00:00Z",  // 指定初始时间时刻
"cartesian":[
    0.0, 1.0, 2.0, 3.0,
    60.0, 4.0, 5.0, 6.0,
    120.0, 7.0, 8.0, 9.0
]

第一个参数表示时间使用距离起始时间(epoch)的秒数来表示

后面三个参数表示笛卡尔坐标系中的XYZ三个坐标参数

其他属性:

  • interpolationAlgorithm:定义了采样用于插值的算法,其值有LAGTANGEHERMITEGEODESIC,默认是LAGRANGE:如果位置不在该采样区间,那么这个属性值会被忽略

  • interpolationDegree:定义了用来插值所使用的多项式的次数,1表示线性差值,2表示二次插值法,默认为1,如果使用GEODESIC插值算法,那么这个属性将被忽略

  • nextTime:在时间间隔内下一个采样的时间,可以通过ISO8061方式,也可以通过与epoch秒数来定义。它决定了不同packet之间的采样是否有停顿

  • previousTime:在时间间隔内前一个采样的时间,可以通过ISO8061方式,也可以通过与epoch秒数来定义,它决定了不同packet之间的采样是否有停顿

billboard

billboard属性表示标记广告牌属性,与视图对其的广告牌或图像,广告牌通过 position 属性在场景中定位,其子属性有:

子属性描述
color广告牌的颜色属性,该颜色值与广告牌的“图像”的值相乘,以产生最终颜色,使用子属性对象进行描述
eyeOffset放置广告牌相对于属性的眼睛坐标偏移量,眼睛坐标是一种左手坐标系,其中 X 轴指向观看者的右侧,Y 轴指向上方,Z 轴指向屏幕,使用子属性对象进行描述
horizontalOrigin广告牌的水平原点,控制广告牌图像是左对齐、居中对齐还是右对齐,使用字符串进行描述,有效值为“LEFT”“CENTER”“RIGHT”
verticalOrigin广告牌的垂直原点,控制广告牌图像是底部对齐、居中对齐还是顶部对齐,使用字符串进行描述,有效值为“BOTTOM”“CENTER”“TOP”
image广告牌显示的图像,以URL路径 表示
pixelOffset广告牌原点的偏移量(以视口像素为单位),使用子属性对象进行描述
scale广告牌的尺寸规模,乘以广告牌的像素大小就是最终的大小,使用子属性对象进行描述
rotation广告牌相对于对齐轴的旋转,使用子属性对象进行描述
show是否显示广告牌,使用布尔值进行描述
  • color属性的子属性:referenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

    名字类型描述
    rgba数组指定为颜色分量数组 [Red、Green、Blue、Alpha] 的颜色,其中每个分量都在 0-255 的范围内。如果数组有四个元素,则颜色是恒定的。如果它有五个或更多元素,则它们是时间标记的样本,排列为 [Time、Red、Green、Blue、Alpha]
    rgbaf数组指定为颜色分量数组 [Red、Green、Blue、Alpha] 的颜色,其中每个分量都在 0.0-1.0 范围内。如果数组有四个元素,则颜色是恒定的。如果它有五个或更多元素,则它们是时间标记的样本,排列为 [Time、Red、Green、Blue、Alpha]
  • eyeOffset属性的子属性:referenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

    名字类型描述
    cartesian数组指定为笛卡尔位置在眼坐标(以米为单位)中的眼睛偏移。如果数组有三个元素[X, Y, Z],则眼偏移是恒定的。如果它有四个或更多元素[Time, X, Y, Z],则它们是时间标记的样本
  • pixelOffset属性的子属性:referenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

    名字类型描述
    cartesian2数组在视口中指定为笛卡尔的像素偏移以像素为单位坐标[X, Y],其中 X 是右侧的像素,Y 是向上的像素。如果数组有两个元素,则像素偏移量是恒定的。如果它有三个或更多元素[Time, X, Y],则它们是时间标记的样本
  • scale属性的子属性:referenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

    名字类型描述
    number数字或者数组该值可以是单个数字,在这种情况下,该值在间隔内是恒定的,也可以是一个数组。如果它是一个数组,并且数组有一个元素,则该值在间隔内是恒定的。如果它有两个或多个元素 [Time, Value],则它们是时间标记的样本
  • rotation属性的子属性:referenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

    名字类型描述
    number数字或者数组该值可以是单个数字,在这种情况下,该值在间隔内是恒定的,也可以是一个数组。如果它是一个数组,并且数组有一个元素,则该值在间隔内是恒定的。如果它有两个或多个元素 [Time, Value],则它们是时间标记的样本
label

label标签是放置在场景中的一串文本,其子属性和billboard类似,可以参考billboard的子属性,label的相似子属性有eyeOffsethorizontalOriginverticalOriginpixelOffsetscaleshow

其他子属性:

子属性描述
fillColor标签的填充颜色,其颜色子属性参数为rgbargbaf
font用于标签字体的属性,其值为字体类型的字符串,如:21pt Lucida Console
outlineColor标签的轮廓线颜色,其颜色子属性参数为rgbargbaf
outlineWidth标签的轮廓宽度,其子属性为number参数
style标签的样式,其子属性为labelStyle,有效值为“FILL”“OUTLINE”“FILL_AND_OUTLINE”
text标签显示的文本,其值为文本字符串
js
// 相关例子
"label":{
  "fillColor":[
    {
      "interval":"2019-08-28T04:00:00Z/2019-08-28T04:17:33Z",
      "rgba":[ 255,255,0,255 ]
    }
  ],
  "font":"bold 10pt Segoe UI Semibold",
  "horizontalOrigin":"CENTER",
  "outlineColor":{
    "rgba":[ 0,0,0,255 ]
  },
  "pixelOffset":{
    "cartesian2":[
      0.0,20.0
    ]
  },
  "scale":1.0,
  "show":[
    {
      "interval":"2019-08-28T04:00:00Z/2019-08-28T04:17:33Z",
      "boolean":true
    }
  ],
  "style":"FILL",
  "text":"火箭发射",
  "verticalOrigin":"CENTER"
},
path

path属性即路径属性,是由对象随时间推移运动定义的折线,path的子属性和billboard类似的属性这里不做介绍,可以参考billboard的子属性,类似的子属性有:show

其他子属性:

子属性描述
material用于绘制路径的材料,其子属性有:solidColorpolylineOutline
width路径线的宽度,该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
resolution用于对路径进行采样的最大步长(以秒为单位),如果属性的数据点相距比分辨率指定的数据点更远,则将采取其他步骤,从而创建更平滑的路径,该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
leadTime显示路径的动画时间(以秒为单位)之前的时间(实体还没有达到时,出现的路径轨迹),该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
trailTime显示路径的动画时间(以秒为单位)之后的时间(实体经过后还残留的路径轨迹),该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
  • solidColor:用纯色为线条着色,纯色可能是半透明的,该属性的子属性color有以下的属性值:rgbargbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明

  • polylineOutline:使用颜色和轮廓为线条着色

    子属性描述
    color表面的颜色,该子属性有以下的属性值:rgbargbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
    outlineColor曲面轮廓的颜色,该子属性有以下的属性值:rgbargbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
    outlineWidth轮廓的宽度,该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
  • polylineGlow:用发光的颜色为路径线条着色

    子属性描述
    color表面的颜色,该子属性有以下的属性值:rgbargbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
    glowPower光芒的强度,该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
model

model为3维模型属性,model的子属性和billboard类似的属性这里不做介绍,可以参考billboard的子属性,类似的子属性有:showscale

其他子属性:

子属性描述
minimumPixelSize与缩放无关的模型的最小像素大小(在怎么缩放地球,模型也不会小于这个像素尺寸),该子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
maximumScale定义模型的最大缩放比例,防止模型在近处变得过大
gltf其子属性url引入gltf3维模型的url路径
runAnimations一个布尔属性,指定是否应启动模型中指定的gltf动画
articulations定义模型的关节动画,用于控制模型的可动部分,其子属性为模型绘制时定义的各个部位的属性名,这些子属性有以下的属性值:numberrgbafreferenceepochnextTimepreviousTime 子属性查看之前的介绍,这里不做详细的说明
nodeTransformations定义模型中特定节点的变换(如平移、旋转、缩放),对于特定关键属性有如下的子属性:translation(通过cartesian控制)、rotation(通过quaternion控制)

CZML动态路径

主要涉及的Api和方法:

  • CzmlDataSource:加载CZML数据源的接口
  • trackedEntity:涉及到实体跟踪的方法
  • Quaternion:构造旋转矩阵的接口
  • DebugModelMatrixPrimitive:辅助调试接口,将模型的偏移矩阵/旋转矩阵通过Primitive的方式加载到地球上,进行可视化的展示,辅助我们进行判断

构造CZML时必须要有的节点document信息,该节点对象一般放在最上方:

js
{
    id: "document",
    name: "SpaceX",
    version: "1.0",
    clock: {
      interval: "2019-08-28T04:00:00Z/2019-08-28T04:17:33Z",
      currentTime: "2019-08-28T04:00:00.00Z",
      multiplier: 1,
      range: "LOOP_STOP",
      step: "SYSTEM_CLOCK_MULTIPLIER"
     }
},

参数解释:

  • clock:在节点中定义了一个时钟,时钟相当于一个全局的控制器
    • interval:经历的一个总的时间段
    • currentTime:设置当前的时间
    • multiplier:每个时刻的倍数

节点实体动态路径的关键信息path,动态路径通过其内部的path来实现的:

js
{
    id: "path",
    name: "path for Spacex",
    description: "<p>this is a description</p>",
    availability: "2019-08-28T04:00:00Z/2019-08-28T04:17:33Z",
    path: {
        material: {
            polylineOutline: {
                color: {
                    rgba: [255, 0, 255, 255],
                },
                outlineColor: {
                    rgba: [0, 255, 255, 255],
                },
                outlineWidth: 5    
            }, 
        },
        width: 8,   // 折线宽度
        leadTime: 5,  
        trailTime: 50000,
        resolution: 5,
     },
     orientation: {
         "unitQuaternion": [
             0.191, -0.331, 0.462, 0.800
         ]
     },
     model: {
         gltf: './model/Cesium_Air.glb'
     },
     position: {
         epoch: "2019-08-28T04:00:00.00Z",
         cartographicDegrees: [
             0, 120, 30, 6000,
             3000, 120, 40, 6500
         ]
     }
},

参数解释:

  • path:描述动态路径的参数
    • material:路径的材质参数
      • polylineOutline:折线轮廓样式
    • leadTime:提前出现的路径,提前5秒出现路径(比当前时间块5秒的路径)
    • trailTime:尾部残留路径的残留时间
    • resolution:路径线的分辨率
  • orientation:设置模型的方向,使模型的朝向符合航线的方向
    • 通过构造unitQuaternion数组去实现模型方向
  • model:引入动态模型的残参数
  • position:位置信息,为动态路径提供点位进行插值计算
    • cartographicDegrees:存放点位信息,每一行有一个点位信息,点位信息有四个参数:
      • 第一个参数0:表示时间刻度(时间点)
      • 第二个参数120:表示精度
      • 第三个参数30:表示纬度
      • 第四个参数6000:表示高度

设置完CZML参数后,我们需要将其加载到模型中:

js
var dataSourcePromise = viewer.dataSources.add(Cesium.CzmlDataSource.load(czml));

同时通过trackedEntity方法设置视角的跟踪:追踪模型的视角

js
dataSourcePromise.then(function (dataSource) {
    // 根据模型所在的实体去进行加载
    viewer.trackedEntity = dataSource.entities.getById('path');
}).catch(function (error) {
  console.error(error);
});

但是通过构造unitQuaternion数组去实现模型方向中数组往往是不好构造的,我们可以通过代码将模型调整到一个正确的位置,在调整位置时,我们需要先知道模型的坐标轴,以下方法可以做出模型局部坐标系的坐标轴:

js
dataSourcePromise.then(function (dataSource) {
    // 根据模型所在的实体去进行加载
    viewer.trackedEntity = dataSource.entities.getById('path');
    // 获取到模型的旋转矩阵
    let matrix = viewer.trackedEntity.computeModelMatrix(Cesium.JulianDate.fromIso8601('2019-08-28T04:00:00.00Z'))
    // 通过调试的实体来绘制模型坐标轴
    viewer.scene.primitives.add(
        new Cesium.DebugModelMatrixPrimitive({  // 加载模型偏移矩阵的图元
            modelMatrix: matrix,  // 设置偏移矩阵
            length: 100000,
            width: 10
        })
    );
}).catch(function (error) {
  console.error(error);
});

参考坐标轴,我们可以正确的给我们的模型的方向进行调整

旋转模型:orientation调整偏移矩阵

js
viewer.trackedEntity.orientation = Cesium.Quaternion.fromHeadingPitchRoll(new Cesium.HeadingPitchRoll(Cesium.math.toRadians(-60), Cesium.Math.toRadians(45), Cesium.Math.toRadians(0)));

设置heading:偏航角,pitch:俯仰角,roll:翻滚角三个的旋转角度,来使我们的模型朝向正确的一个角度

我们可以打印viewer.trackedEntity.orientation的四元数,将四元数填入模型中的unitQuaternion数组中,直接使用数组即可调整方向

CZML文件加载

CZML文件最终成为了CzmlDataSource对象,被加载到viewerdatasources

js
var dataSourcePromise = viewer.dataSources.add(Cesium.CzmlDataSource.load(czml));

我们可以通过getById来获取CZML文件中定义的entity实体,从而对实体进行操作

js
// dataSource是一个CzmlDataSource对象
dataSourcePromise.then(function (dataSource) {
    // 视角跟随实体
	viewer.trackedEntity = dataSource.entities.getById('Vulcan');
}).catch(function (error) {
	console.error(error);
});

注意:当加载多个czml文件时,场景信息会以最后一个czml文件定义的为准

CZML文件移除

CZML文件的移除,即CzmlDataSource对象的移除

js
let entity = temp.entities.getById("GroundControlStation");
if(entity){
	temp.entities.remove(entity);
}

temp为保存的CzmlDataSource对象

删除viewer中所有的dataSources对象:

js
cesium.viewer.dataSources.removeAll(true)

加载3维模型

主要涉及的Api和方法:

  • Cartesian3:笛卡尔世界坐标系的基类
  • Math:数学计算的基类
  • HeadingPitchRoll:模型方位的基类
  • Transforms:转换相关的基类
  • Entity:实体相关的基类
  • ModelGraphicsEntity的子类,模型相关的
  • ClippingPlane:用于裁点的平面

添加3D模型的相关函数:

js
addModel(url, height) {  // 调用方法传递的参数为模型的url和模型处于地球的高度
    window.viewer.entities.removeAll();  // 清除地球上的所有实体
    const position = window.Cesium.Cartesian3.fromDegrees(
        120.059,
        36.328,
        height
    );
    // 创建调整方向的三个参数heading,pitch,roll
    // Math.toRadians(135)表示将135度转化为弧度
    const heading = window.Cesium.Math.toRadians(135);
    const pitch = 0;
    const roll = 0;
    const hpr = new window.Cesium.HeadingPitchRoll(heading, pitch, roll); 
    // 根据参考系计算四元数(表示对应的旋转变换),方便在3D场景中进行旋转操作,可以控制模型具体的方位
    const orientation = window.Cesium.Transforms.headingPitchRollQuaternion(
        position,  // 作为旋转时的中心点
        hpr  // 传入heading,pitch,roll
    );
    
    // 在实体中添加模型
    const entity = window.viewer.entities.add({
        name: "",  // 模型名称
        position: position,  // 模型位置
        orientation: orientation,  // 模型方向,根据四元数来确定的
        model: {  // 构建模型
            uri: url,  // 传入url
            minimumPixelSize: 128,  // 模型在地球3维场景中缩放显示的最小的像素
            maximumScale: 20000,  // 设置最大的缩放比例
            runAnimations: false,  // 不启用模型中的动画
            
            // 定义模型节点动画参数,用于控制模型的不同部分在时间轴上的变化
            articulations: {
                "BoosterFlames Size": {
                  epoch: "2019-08-28T04:00:00Z",  // 指定时间点
                  number: [  // 一个数组,包含成对的时间点和数值
                    0, 1, // 从epoch 开始(0秒),BoosterFlames Size的值为1
                    180, 1,
                    182, 0,
                    1000,0                  
                  ]
                }
            }
        },
    });
    // 跟踪实体,初始化的时候定位到这个实体,跟踪实体后,对地球是无法进行操作的,只能围绕着模型进行视图上的操作
    window.viewer.trackedEntity = entity;  
}

gltf模型节点动画

主要涉及的Api和方法:

  • Model:通过这个类来加载gltf模型
  • Matrix4:四维矩阵
  • Primitive:图元方式管理模型
  • ModelNode:模型节点的类

gltf模型的构成要素

在建模的时候,模型的骨架由关节和节点构成,我们需要对节点进行控制来实现动画的效果

js
// 通过图元的方式进行加载模型
let primitive = window.viewer.scene.primitives.add(Cesium.Model.fromGltf({
    url: "/model/glb/Cesium_Man.glb",
    // 通过偏移矩阵modelMatrix来调整模型的初始位置
    modelMatrix: Cesium.Matrix4.fromTranslationQuaternionRotationScale(Cesium.Cartesian3.fromDegrees(117.66, 36.1, 330), Cesium.Quaternion.fromHeadingPitchRoll(Cesium.HeadingPitchRoll.fromDegrees(0, 20, -45)), new Cesium.Cartesian3(1, 1, 1)),
    minimumPixelSize: 200
}));

articulations自定义动画

CZML数组中的model属性中加入articulations子属性来自定义3维模型的动画,火箭发射的自定义动画如下所示:

js
articulations: {   // 定义动画参数
    "BoosterFlames Size": {   // 二级发动机底部火焰样式的显示情况
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 1,
        180, 1,
        182, 0,
        1000,0                  
      ]
    },
    "Booster MoveZ": {  // 二级发动机随着z轴向下掉落的情况
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 0,
        180, 0,
        200, -300,
        1000,-300
      ]
    },
    "Booster Yaw": {
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 0,
        190, 0,
        200, 90,
        1000, 90
      ]
    },
    "Booster Size": {  // 二级发动机随时间变化的显示情况
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 1,
        200, 1,
        201, 0,
        1000,0
      ]
    },
    "InterstageAdapter MoveZ": {
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0,0,
        180, 0,
        200, -300,
        1000,-300
      ]
    },
    "InterstageAdapter Size": {
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 1,
        190, 1,
        191, 0, 
        1000,0
      ]
    },
    "SRBFlames Size": {   // 一级助推器底部火焰随时间变化的显示情况
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 1,
        60, 1,
        61, 0, 
        1000,0
      ]
    },
    "SRBs Separate": {  // 一级助推器分开的距离
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0,0,
        61, 0,
        71, 40,
        1000,40
      ]
    },
    "SRBs Drop": {  // 一级助推器掉落:随时间的变化掉落的距离,负号表示向下掉落
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 0,
        61, 0,
        71, -150,
        1000,-150
      ]
    },
    "SRBs Rotate": {  // 一级助推器掉落的张开角度随时间的变化,张开的角度单位为度
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0,0,
        61, 0,
        71, 120,
        1000,120
      ]
    },
    "SRBs Size": {  // 一级助推器的显示和消失情况
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0,1,
        70, 1,
        71, 0, 
        1000,0
      ]
    },
    "UpperStageFlames Size": {   // 顶部卫星仓助推器底部火焰随时间变化的显示情况
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0,0,
        181, 0,
        182, 1,
        1000,1,
        1001,0,
        1050,0
      ]
    },
    "Fairing Open": {  // 整流罩随时间的变化而打开的角度
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 0,
        200, 0,
        210, 120,  // 第210秒时,打开的角度为120度
        1000,120
      ]
    },
    "Fairing Separate": {  // 整流罩分离:随时间的变化分离出去的距离,负号表示往外部分离
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 0,
        200, 0,
        210, -50,
        1000,-50
      ]
    },
    "Fairing Drop": {  // 整流罩掉落:随时间的变化掉落的距离,负号表示向下掉落
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 0,
        202, 0,
        210, -150,
        1000,-150
      ]
    },
    "Fairing Size": {  // 整流罩的显示和消失:1表示显示,0表示消失
      epoch: "2019-08-28T04:00:00Z",
      number: [
        0, 1,
        215, 1,
        216, 0, 
        1000,0
      ]
    }
  }
  • 上述代码中的BoosterFlames这些属性都是在3维建模中对应的相关模块信息(就是gltf里面的一个部件名称(name)),我们需要调用这些模块来进行对该模块的自定义动画
  • 对于Size这些状态(关节动作),用于控制对应部件的状态,常用的状态有:
    • Size:定义物体或其特定部分的大小/显示情况的变化,阈值为 0 至 1,0 表示模型会消失
      • Rotate:定义物体或其特定部分的旋转变化,也可以使用RotateX为绕 X 轴旋转,阈值为 -360 至 360(同理还有RotateYRotateZ),单位为度
    • MoveZ:定义物体或其特定部分在Z轴上的移动变化,阈值为 -1000 至 1000,单位为米(同理还有MoveXMoveY
    • Pitch:定义物体或其特定部分的俯仰角变化
    • Drop:定义物体或其特定部分的下降变化
    • Separate:定义物体或其特定部分的分离变化
    • Open:定义物体或其特定部分的打开变化
    • Yaw:定义物体或其特定部分的偏航角变化
    • Roll:定义物体或其特定部分的滚转角变化
    • Brightness:定义物体或其特定部分的亮度变化
    • Height:定义物体或其特定部分的高度变化
    • Width:定义物体或其特定部分的宽度变化

制作模型旋转动画

  1. 整体的模型是需要分部件的,如果没有分离,需要将模型拆开进行布局
  2. 选择一个部件制作旋转动画,右键该部件->设置原点->(原点->质心(体积)),如果想要自定义旋转中心,可以先通过游标进行确定位置,在将旋转中心放到游标的位置
  3. 将部件动画的当前帧拖动到第一帧,按下i键插入动画
  4. 拖动到其他的位置帧,分别插入旋转动画即可
  5. 最后设置结束的时间即可
  6. 我们可以右键所有关键帧,设置关键帧为线性插值,来保证运动的平滑
  7. 进入动画页面,外部插值设置为连续,保证首尾平滑效果(依次点击通道->插值模式->线性插值)

生成路径数组

编写生成路径数组的函数,传入起点和终点的经纬度和高度信息,生成路径数组

js
Wfunction generateArcPath(startLon, startLat, startHeight, endLon, endLat, endHeight, numPoints) {
    const startCartographic = new Cesium.Cartographic(Cesium.Math.toRadians(startLon), Cesium.Math.toRadians(startLat), startHeight);
    const endCartographic = new Cesium.Cartographic(Cesium.Math.toRadians(endLon), Cesium.Math.toRadians(endLat), endHeight);

    const geodesic = new Cesium.EllipsoidGeodesic(startCartographic, endCartographic);  // EllipsoidGeodesic 对象用于计算地球表面上两点之间的最短路径(测地线)

    const cartesianArray = [];
    for (let i = 0; i <= numPoints; i++) {  // 使用循环生成路径上的点,点的数量由 numPoints 决定
        const fraction = i / numPoints;  // fraction 表示当前点在起点和终点之间的比例
        const intermediateCartographic = geodesic.interpolateUsingFraction(fraction);
        const intermediateHeight = Cesium.Math.lerp(startHeight, endHeight, fraction);  // 计算当前点的地理坐标(弧度)
        const intermediatePosition = Cesium.Cartographic.toCartesian(intermediateCartographic);  // 将地理坐标转换为笛卡尔坐标
        const cartographic = Cesium.Cartographic.fromCartesian(intermediatePosition);  // 将笛卡尔坐标转换回地理坐标
        const lon = Cesium.Math.toDegrees(cartographic.longitude);
        const lat = Cesium.Math.toDegrees(cartographic.latitude);
        cartesianArray.push(i * (240 / numPoints), lon, lat, intermediateHeight);
    }
    return cartesianArray;
}

numPoints表示生成该路径数组中有几个生成点,点越多路径的弧度更平滑


导弹机动发射

导弹机动发射的函数方法,外界输入起始点的经纬度和高度后,点击发送按钮,就可以发射导弹

js
let viewer = null;
let cesiumInitialized = false;  // 判断是初始化还是触发导弹发射函数

export function addMissileMobileLaunch(startLon, startLat, startHeight, endLon, endLat, endHeight, missileId) {
    if (!cesiumInitialized) {
        const script = document.createElement('script');
        script.src = 'https://cesium.com/downloads/cesiumjs/releases/1.99/Build/Cesium/Cesium.js';
        script.onload = () => {
            window.Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3Njg4ZWU5Yi1iZDhiLTRhYmUtOTRiYS04YjM5NmUwNjVmMDMiLCJpZCI6MjI3MzQ3LCJpYXQiOjE3MjA1MjA4Mjh9.E5XW4LnwgfVAaBC-znaYr61m4yK0-j2qEQhi9qwFFPE'
            viewer = new Cesium.Viewer('cesiumContainer', {
                shouldAnimate: true  // 一开始就播放动画
            });
            cesiumInitialized = true;
            // addMissile(startLon, startLat, startHeight, endLon, endLat, endHeight, missileId);  // 一开始是否自动发射一次导弹
        };
        document.head.appendChild(script);

        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = 'https://cesium.com/downloads/cesiumjs/releases/1.119/Build/Cesium/Widgets/widgets.css';
        document.head.appendChild(link);
    } else {
        addMissile(startLon, startLat, startHeight, endLon, endLat, endHeight, missileId);  // 点击导弹发射按钮时触发
    }
}

/**
 * startLon:导弹发射起点的的经度
 * startLat:导弹发射起点的的纬度
 * startHeight::导弹发射起点的的高度
 * endLon:导弹发射终点的的经度
 * endLat:导弹发射终点的的纬度
 * endHeight:导弹发射终点的高度
 * missileId:导弹的id
 */
function addMissile(startLon, startLat, startHeight, endLon, endLat, endHeight, missileId) {
    const numPoints = 600; // 生成路径的点数
    const cartesianData = generateArcPath(startLon, startLat, startHeight, endLon, endLat, endHeight, numPoints);  // 通过generateArcPath函数生成路径

    const now = Cesium.JulianDate.toIso8601(viewer.clock.currentTime);  // 获取当前的时刻
    
    // 构造导弹的cmzl数组
    var czml =
        [
            {
                id: "document",
                name: "Missile",
                version: "1.0",
                clock: {
                    currentTime: `${now}`,
                    multiplier: 1,
                    range: "LOOP_STOP",
                    step: "SYSTEM_CLOCK_MULTIPLIER"
                }
            },
            {
                id: `MissileLaunch${missileId}`,
                name: `MissileLaunch${missileId}`,
                path: {
                    show: true,
                    width: 1,
                    resolution: 86400,
                    leadTime: 500,
                    trailTime: 500,
                    material: {
                        solidColor: {
                            color: {
                                rgba: [0, 255, 0, 255] // 设置轨迹颜色为绿色
                            }
                        }
                    }
                },
                model: {
                    show: true,
                    gltf: [
                        {
                            interval: `${now}/9999-12-31T23:59:59.9999999Z`,
                            uri: "https://raw.githubusercontent.com/jinlinchao123/Cesium-assets/main/3Dmodel/MissileTwo.glb"
                        }
                    ],
                    minimumPixelSize: 128,
                    scale: 1,
                    runAnimations: false,
                },
                position: {
                    interpolationAlgorithm: "LAGRANGE",
                    interpolationDegree: 5,
                    referenceFrame: "FIXED",
                    epoch: `${now}`,
                    cartographicDegrees: cartesianData
                },
                orientation: {
                    "interpolationAlgorithm": "LINEAR",
                    "interpolationDegree": 1,
                    "epoch": `${now}`,
                    "velocityReference": "#position"  // 方向的参考属性,指向实体的位置属性
                }
            }
        ]

    var dataSourcePromise = viewer.dataSources.add(Cesium.CzmlDataSource.load(czml));

    dataSourcePromise.then(function (dataSource) {
        viewer.trackedEntity = dataSource.entities.getById('MissileLaunch0');
    }).catch(function (error) {
        console.error(error);
    });
}

missileMobileLaunch.vue的文件形式如下:

vue
<template>
    <div id="cesiumContainer" class="cesium-container"></div>
    <div class="itemClass">
        <div>
            <input type="string" class="inputClass" v-model="startLon" placeholder="起点的经度:" />
            <input type="string" class="inputClass" v-model="startLat" placeholder="起点的纬度:" />
            <input type="string" class="inputClass" v-model="startHeight" placeholder="起点的高度:" />
        </div>
        <div>
            <input type="string" class="inputClass" v-model="endLon" placeholder="终点的经度:" />
            <input type="string" class="inputClass" v-model="endLat" placeholder="终点的纬度:" />
            <input type="string" class="inputClass" v-model="endHeight" placeholder="终点的高度:" />
        </div>
        <div>
            <button @click="launchMissile" style="margin: 1px;">发射导弹</button>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { addMissileMobileLaunch } from './function';

const startLon = ref(120);
const startLat = ref(30);
const startHeight = ref(0);
const endLon = ref(180);
const endLat = ref(60);
const endHeight = ref(1000000);

let missileId = 0;

onMounted(() => {
    addMissileMobileLaunch(startLon.value, startLat.value, startHeight.value, endLon.value, endLat.value, endHeight.value, missileId++);
});

const launchMissile = () => {
    addMissileMobileLaunch(startLon.value, startLat.value, startHeight.value, endLon.value, endLat.value, endHeight.value, missileId++);
};
</script>

<style>
.cesium-container {
    width: 100%;
    height: 100vh;
}
.itemClass {
    position: absolute;
    top: 10px;
    left: 10px;
}
.inputClass {
    width: 80px;
    height: 15px;
    margin: 1px;
}
</style>

WebSocket

WebSocket是一种用于在Web应用程序和服务器之间建立实时、双向的通信连接,属于应用层协议,它基于TCP传输协议,并复用HTTP的握手通道,WebSocket可以在浏览器中进行使用,支持双向通信,使用比较简单,当数据需要在一段时间内需要时刻变化时,我们可以使用WebSocket来完成页面的实时响应(服务端将生成的数据发送客户端,客户端页面马上就监听到,之后再进行响应),而不是通过前后端的方式,通过定时器不断的请求接口得到数据

WebSocket的优势包括:

  • 实时性: 由于WebSocket的持久化连接,它可以实现实时的数据传输,避免了Web应用程序需要不断地发送请求以获取最新数据的情况
  • 双向通信: WebSocket协议支持双向通信,这意味着服务器可以主动向客户端发送数据,而不需要客户端发送请求
  • 减少网络负载: 由于WebSocket的持久化连接,它可以减少HTTP请求的数量,从而减少了网络负载

WebSocket API 是用于在 Web 应用程序中创建和管理WebSocket 连接的接口集合。WebSocket API 由浏览器原生支持,无需使用额外的 JavaScript 库或框架,可以直接在 JavaScript 中使用

WebSocket在前端的接入和使用:在vue下进行使用

vue
<script>
const uid = "jlc1234";
const ws = new WebSocket(`ws://localhost:8081/ws/${uid}`); // 服务的的地址</script>

如果服务端的地址错误,在前端中会显示WebSocket连接失败的提示报错,没有抛出错误表示连接成功

WebSocket的四个函数

open

WebSocket通信当服务端开启时会触发open函数

js
const openHandle = () => {
    console.log("WebSocket成功连接")
}
ws.addEventListener("open", openHandle)
close

WebSocket通信当服务端关闭时会触发close函数,我们将服务端停掉后,客户端可以立刻触发钩子,来触发对应的函数

js
const closeHandle = () => {
    console.log("WebSocket断开连接")
}
ws.addEventListener("close", closeHandle)
message

message函数可以获取后端生成并传递的数据消息

js
const messageHandle = (data) => {
    console.log(data)
    console.log("前端接收到消息了")
}
ws.addEventListener("message", messageHandle)
error

WebSocket通信当客户端没有正确使用服务端的地址时会触发error函数

js
const errorHandle = () => {
    console.log("WebSocket连接出错")
}
ws.addEventListener("error", errorHandle)

上述方法都在满足一定的条件下自动执行,我们可以简写为:

js
const ws = new WebSocket(`ws://localhost:8081/ws/${uid}`);
ws.onopen = function() {
    // 与服务器端连接成功后自动执行
}
ws.onmessage = function(event) {
    // 服务器端向客户端发送数据时自动执行
    var response = event.data;  // 获取服务器端传递的数据
}
ws.onclose = function(event) {
    // 服务器端主动断开连接时自动执行
}

操作WebSocket的方法

对于生成的const ws = new WebSocket();对象,我们可以用一些方法来对通信进行控制

  • close:关闭WebSocket连接:ws.close()
  • send:客户端向服务的发送消息:ws.send("我来自客户端"),发送的数据可以是字符串、Blob 对象或ArrayBuffer对象

重连

当我们关闭服务端的时候,客户端可以监听到服务端被关闭了,但是我们后面重新开启服务的的时候,客户端是不能去重新自动连接的,因此我们需要写一个重连的方法,我们可以通过定时器进行实现:当我们WebSocket关闭的钩子执行后,我们需要有一个restart的方法,该方法中有一个定时器,当执行restart方法后,定时器就会执行,我们可以设置定时器每隔5秒去重新请求连接服务端,只要连接到了服务端,我们就关闭这个定时器:

js
const uid = "jlc1234";
let ws = new WebSocket(`ws://localhost:8081/ws/${uid}`); // 服务的的地址
let timer = null;
let isHandle = false;  // 表示是否是手动关闭的,如果是手动关闭,则不重连

const openHandle = () => {
    console.log("WebSocket成功连接")
}
const closeHandle = () => {
    console.log("WebSocket断开连接")
    if(!isHandle){
        restart()
    }
    isHandle = false
}
const messageHandle = (data) => {
    console.log(data)
    console.log("前端接收到消息了")
}
const errorHandle = () => {
    console.log("WebSocket连接出错")
}
const restart = () => {
    console.log("客户端和服务端连接失败,准备开始重连")
    timer = setInterval(() => {
        console.log("重连中...")
        ws = new WebSocket(`ws://localhost:8081/ws/${uid}`); // 重连
        // 判断是否重连成功,重连成功后需要将定时器清除
        if(ws.readyState === 0){
            clearInterval(timer)
            timer = null
            // 将事件重新进行绑定
            ws.addEventListener("open", openHandle)
            ws.addEventListener("close", closeHandle)
            ws.addEventListener("message", messageHandle)
            ws.addEventListener("error", errorHandle)
        }
    }, 5000)
}
const closews = () => {
    isHandle = true
    ws.close()
}

ws.addEventListener("close", closeHandle)
ws.addEventListener("close", closeHandle)
ws.addEventListener("message", messageHandle)
ws.addEventListener("error", errorHandle)

WebSocket的卸载

Vue中,当我们的页面onUnmounted的时候,我们需要对WebSocket进行卸载

Released under the MIT License.