动目标
CZML
CZML
是一种用来描述动态场景的JSON
架构的语言(简单的可以理解为数组形式),它可以用来描述点、线、布告板、模型以及其他的图元,同时定义他们是怎样随时间变化的,CZML
可以通到很多其他的程序自动生成,或者手写进行配置也可以
CZML
可以准确的描述值随时间变化的属性,比如:一条线在某一时间内是红色的而在另一时间内是蓝色的;如有一辆车,分别定义了两个不同时间的位置,通过CZML
定义的差值算法,客户端可以准确的显示在这两个时间点之间车的位置。CZML
中所有的属性都可以是随时间的变化而变化
可以通过czml-writer
来生成CZML
CZML
的基本形式
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
对象在世界中的位置属性,该属性的子属性有:
名字 | 范围 | 类型 | 描述 |
---|---|---|---|
referenceFrame | Interval | 字符串 | 指定笛卡尔位置的参考系,可能的值为“FIXED” 和“INERTIAL” ,默认参考系为“FIXED” 。此外,此属性的值可以是哈希 (#) 符号,后跟同一范围内另一个对象的 ID ,该对象的“position” 和“orientation” 属性定义定义此位置的参考帧。当使用笛卡尔以外的任何类型指定位置时,将忽略此属性 |
cartesian | Interval | 数组 | 以笛卡尔坐标[X,Y,Z] 表示的相对于参考坐标系的位置,单位为米。如果数组有三个元素,则位置是恒定的。如果它有四个或更多元素,则它们是时间标记的样本,排列为 :[Time, X, Y, Z] ,其中 Time 是 ISO 8601 日期和时间字符串或者来自epoch 的间隔秒数 |
cartographicRadians | Interval | 数组 | 以 WGS84 表示的位置[Longitude, Latitude, Height] ,其中经度和纬度以弧度为单位,高度以米为单位。如果数组有三个元素,则位置是恒定的。如果它有四个或更多元素,则它们是时间标记的样本,排列为 :[Time, Longitude, Latitude, Height] ,其中 Time 是 ISO 8601 日期和时间字符串或者来自epoch 的间隔秒数 |
cartographicDegrees | Interval | 数组 | 以 WGS 84 表示位置[Longitude, Latitude, Height] ,其中经度和纬度以度为单位,高度以米为单位。如果数组有三个元素,则位置是恒定的。如果它有四个或更多元素,则它们是时间标记的样本,排列为 :[Time, Longitude, Latitude, Height] ,其中 Time 是 ISO 8601 日期和时间字符串或者来自epoch 的间隔秒数 |
cartesianVelocity | Interval | 数组 | 位置和速度以米为单位表示为相对于参照系的两个笛卡尔坐标[X,Y,Z,vX,vY,vZ] 。如果数组有六个元素,则位置是恒定的。如果它有七个或更多元素,则它们是时间标记的样本,排列为[Time,X,Y,Z,vX,vY,vZ] ,其中 Time 是 ISO 8601 日期和时间字符串或者来自epoch 的间隔秒数 |
reference | Interval | 字符串 | 参考属性 |
epoch | Packet | 字符串 | 指定开始的时间 |
nextTime | Packet | 字符串或数字 | 此间隔内下一个样本的时间,指定为 ISO 8601 日期和时间字符串或来自epoch 的间隔秒数。此属性用于确定不同数据包中指定的样本之间是否存在间隙 |
previousTime | Packet | 字符串或数字 | 此间隔内上一个样本的时间,指定为 ISO 8601 日期和时间字符串或来自epoch 的间隔秒数。此属性用于确定不同数据包中指定的样本之间是否存在间隙 |
orientation
对象在世界中的方向(模型在世界中的方向)
子属性:reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
名字 | 类型 | 描述 |
---|---|---|
axes | 字符串 | 使用三个正交轴(x轴、y轴和z轴)来定义实体的方向。每个轴都是一个Cartesian3 对象,表示该轴在世界坐标系中的方向,axes 适用于简单的方向定义,特别是当你只需要定义一个轴的方向时 |
unitQuaternion | 数组 | 使用单位四元数(unit quaternion )来表示实体的方向,四元数是根据位置信息postion 和航向(heading )、俯仰(pitch )和翻滚(roll )生成的,unitQuaternion 适用于需要精确控制旋转的场景,四元数在处理旋转动画和复杂方向变化时更加稳定和高效 |
velocityReference | 字符串 | 指定实体的方向与其设置矢量之间的关系,如方向的参考属性,指向实体的位置属性:"velocityReference": "#position" |
时间间隔intervals
CZML
数组中的每个元素对应每一个不同的时间,属性的值。时间间隔的定义使用interval
属性,通过ISO8601 interval
格式的字面值表示
"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
时间戳采样来控制位置信息
"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
:定义了采样用于插值的算法,其值有LAGTANGE
,HERMITE
和GEODESIC
,默认是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
属性的子属性:reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
名字 类型 描述 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
属性的子属性:reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
名字 类型 描述 cartesian
数组 指定为笛卡尔位置在眼坐标(以米为单位)中的眼睛偏移。如果数组有三个元素 [X, Y, Z]
,则眼偏移是恒定的。如果它有四个或更多元素[Time, X, Y, Z]
,则它们是时间标记的样本
pixelOffset
属性的子属性:reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
名字 类型 描述 cartesian2
数组 在视口中指定为笛卡尔的像素偏移以像素为单位坐标 [X, Y]
,其中X
是右侧的像素,Y
是向上的像素。如果数组有两个元素,则像素偏移量是恒定的。如果它有三个或更多元素[Time, X, Y]
,则它们是时间标记的样本
scale
属性的子属性:reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
名字 类型 描述 number
数字或者数组 该值可以是单个数字,在这种情况下,该值在间隔内是恒定的,也可以是一个数组。如果它是一个数组,并且数组有一个元素,则该值在间隔内是恒定的。如果它有两个或多个元素 [Time, Value]
,则它们是时间标记的样本
rotation
属性的子属性:reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
名字 类型 描述 number
数字或者数组 该值可以是单个数字,在这种情况下,该值在间隔内是恒定的,也可以是一个数组。如果它是一个数组,并且数组有一个元素,则该值在间隔内是恒定的。如果它有两个或多个元素 [Time, Value]
,则它们是时间标记的样本
label
label
标签是放置在场景中的一串文本,其子属性和billboard
类似,可以参考billboard
的子属性,label
的相似子属性有eyeOffset
、horizontalOrigin
、verticalOrigin
、pixelOffset
、scale
、show
其他子属性:
子属性 | 描述 |
---|---|
fillColor | 标签的填充颜色,其颜色子属性参数为rgba 和rgbaf |
font | 用于标签字体的属性,其值为字体类型的字符串,如:21pt Lucida Console |
outlineColor | 标签的轮廓线颜色,其颜色子属性参数为rgba 和rgbaf |
outlineWidth | 标签的轮廓宽度,其子属性为number 参数 |
style | 标签的样式,其子属性为labelStyle ,有效值为“FILL” 、“OUTLINE” 和“FILL_AND_OUTLINE” |
text | 标签显示的文本,其值为文本字符串 |
// 相关例子
"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 | 用于绘制路径的材料,其子属性有:solidColor 、polylineOutline |
width | 路径线的宽度,该子属性有以下的属性值:number 、rgbaf 、reference 、epoch 、nextTime 和previousTime 子属性查看之前的介绍,这里不做详细的说明 |
resolution | 用于对路径进行采样的最大步长(以秒为单位),如果属性的数据点相距比分辨率指定的数据点更远,则将采取其他步骤,从而创建更平滑的路径,该子属性有以下的属性值:number 、rgbaf 、reference 、epoch 、nextTime 和previousTime 子属性查看之前的介绍,这里不做详细的说明 |
leadTime | 显示路径的动画时间(以秒为单位)之前的时间(实体还没有达到时,出现的路径轨迹),该子属性有以下的属性值:number 、rgbaf 、reference 、epoch 、nextTime 和previousTime 子属性查看之前的介绍,这里不做详细的说明 |
trailTime | 显示路径的动画时间(以秒为单位)之后的时间(实体经过后还残留的路径轨迹),该子属性有以下的属性值:number 、rgbaf 、reference 、epoch 、nextTime 和previousTime 子属性查看之前的介绍,这里不做详细的说明 |
solidColor
:用纯色为线条着色,纯色可能是半透明的,该属性的子属性color
有以下的属性值:rgba
、rgbaf
、reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
polylineOutline
:使用颜色和轮廓为线条着色
子属性 描述 color
表面的颜色,该子属性有以下的属性值: rgba
、rgbaf
、reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明outlineColor
曲面轮廓的颜色,该子属性有以下的属性值: rgba
、rgbaf
、reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明outlineWidth
轮廓的宽度,该子属性有以下的属性值: number
、rgbaf
、reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
polylineGlow
:用发光的颜色为路径线条着色
子属性 描述 color
表面的颜色,该子属性有以下的属性值: rgba
、rgbaf
、reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明glowPower
光芒的强度,该子属性有以下的属性值: number
、rgbaf
、reference
、epoch
、nextTime
和previousTime
子属性查看之前的介绍,这里不做详细的说明
model
model
为3维模型属性,model
的子属性和billboard
类似的属性这里不做介绍,可以参考billboard
的子属性,类似的子属性有:show
、scale
其他子属性:
子属性 | 描述 |
---|---|
minimumPixelSize | 与缩放无关的模型的最小像素大小(在怎么缩放地球,模型也不会小于这个像素尺寸),该子属性有以下的属性值:number 、rgbaf 、reference 、epoch 、nextTime 和previousTime 子属性查看之前的介绍,这里不做详细的说明 |
maximumScale | 定义模型的最大缩放比例,防止模型在近处变得过大 |
gltf | 其子属性url 引入gltf 3维模型的url 路径 |
runAnimations | 一个布尔属性,指定是否应启动模型中指定的gltf 动画 |
articulations | 定义模型的关节动画,用于控制模型的可动部分,其子属性为模型绘制时定义的各个部位的属性名,这些子属性有以下的属性值:number 、rgbaf 、reference 、epoch 、nextTime 和previousTime 子属性查看之前的介绍,这里不做详细的说明 |
nodeTransformations | 定义模型中特定节点的变换(如平移、旋转、缩放),对于特定关键属性有如下的子属性:translation (通过cartesian 控制)、rotation (通过quaternion 控制) |
CZML
动态路径
主要涉及的Api
和方法:
CzmlDataSource
:加载CZML
数据源的接口trackedEntity
:涉及到实体跟踪的方法Quaternion
:构造旋转矩阵的接口DebugModelMatrixPrimitive
:辅助调试接口,将模型的偏移矩阵/旋转矩阵通过Primitive
的方式加载到地球上,进行可视化的展示,辅助我们进行判断
构造CZML
时必须要有的节点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"
}
},
参数解释:
clock
:在节点中定义了一个时钟,时钟相当于一个全局的控制器
interval
:经历的一个总的时间段currentTime
:设置当前的时间multiplier
:每个时刻的倍数
节点实体动态路径的关键信息path
,动态路径通过其内部的path
来实现的:
{
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
参数后,我们需要将其加载到模型中:
var dataSourcePromise = viewer.dataSources.add(Cesium.CzmlDataSource.load(czml));
同时通过trackedEntity
方法设置视角的跟踪:追踪模型的视角
dataSourcePromise.then(function (dataSource) {
// 根据模型所在的实体去进行加载
viewer.trackedEntity = dataSource.entities.getById('path');
}).catch(function (error) {
console.error(error);
});
但是通过构造unitQuaternion
数组去实现模型方向中数组往往是不好构造的,我们可以通过代码将模型调整到一个正确的位置,在调整位置时,我们需要先知道模型的坐标轴,以下方法可以做出模型局部坐标系的坐标轴:
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
调整偏移矩阵
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
对象,被加载到viewer
的datasources
中
var dataSourcePromise = viewer.dataSources.add(Cesium.CzmlDataSource.load(czml));
我们可以通过getById
来获取CZML
文件中定义的entity
实体,从而对实体进行操作
// dataSource是一个CzmlDataSource对象
dataSourcePromise.then(function (dataSource) {
// 视角跟随实体
viewer.trackedEntity = dataSource.entities.getById('Vulcan');
}).catch(function (error) {
console.error(error);
});
注意:当加载多个czml
文件时,场景信息会以最后一个czml
文件定义的为准
CZML
文件移除
CZML
文件的移除,即CzmlDataSource
对象的移除
let entity = temp.entities.getById("GroundControlStation");
if(entity){
temp.entities.remove(entity);
}
temp
为保存的CzmlDataSource对象
删除viewer
中所有的dataSources
对象:
cesium.viewer.dataSources.removeAll(true)
加载3维模型
主要涉及的Api
和方法:
Cartesian3
:笛卡尔世界坐标系的基类Math
:数学计算的基类HeadingPitchRoll
:模型方位的基类Transforms
:转换相关的基类Entity
:实体相关的基类ModelGraphics
:Entity
的子类,模型相关的ClippingPlane
:用于裁点的平面
添加3D
模型的相关函数:
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
模型的构成要素
在建模的时候,模型的骨架由关节和节点构成,我们需要对节点进行控制来实现动画的效果
// 通过图元的方式进行加载模型
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维模型的动画,火箭发射的自定义动画如下所示:
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(同理还有RotateY
和RotateZ
),单位为度MoveZ
:定义物体或其特定部分在Z轴上的移动变化,阈值为 -1000 至 1000,单位为米(同理还有MoveX
和MoveY
)Pitch
:定义物体或其特定部分的俯仰角变化Drop
:定义物体或其特定部分的下降变化Separate
:定义物体或其特定部分的分离变化Open
:定义物体或其特定部分的打开变化Yaw
:定义物体或其特定部分的偏航角变化Roll
:定义物体或其特定部分的滚转角变化Brightness
:定义物体或其特定部分的亮度变化Height
:定义物体或其特定部分的高度变化Width
:定义物体或其特定部分的宽度变化
制作模型旋转动画
- 整体的模型是需要分部件的,如果没有分离,需要将模型拆开进行布局
- 选择一个部件制作旋转动画,右键该部件->设置原点->(原点->质心(体积)),如果想要自定义旋转中心,可以先通过游标进行确定位置,在将旋转中心放到游标的位置
- 将部件动画的当前帧拖动到第一帧,按下
i
键插入动画 - 拖动到其他的位置帧,分别插入旋转动画即可
- 最后设置结束的时间即可
- 我们可以右键所有关键帧,设置关键帧为线性插值,来保证运动的平滑
- 进入动画页面,外部插值设置为连续,保证首尾平滑效果(依次点击通道->插值模式->线性插值)
生成路径数组
编写生成路径数组的函数,传入起点和终点的经纬度和高度信息,生成路径数组
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
表示生成该路径数组中有几个生成点,点越多路径的弧度更平滑
导弹机动发射
导弹机动发射的函数方法,外界输入起始点的经纬度和高度后,点击发送按钮,就可以发射导弹
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
的文件形式如下:
<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
下进行使用
<script>
const uid = "jlc1234";
const ws = new WebSocket(`ws://localhost:8081/ws/${uid}`); // 服务的的地址</script>
如果服务端的地址错误,在前端中会显示WebSocket
连接失败的提示报错,没有抛出错误表示连接成功
WebSocket
的四个函数
open
WebSocket
通信当服务端开启时会触发open
函数
const openHandle = () => {
console.log("WebSocket成功连接")
}
ws.addEventListener("open", openHandle)
close
WebSocket
通信当服务端关闭时会触发close
函数,我们将服务端停掉后,客户端可以立刻触发钩子,来触发对应的函数
const closeHandle = () => {
console.log("WebSocket断开连接")
}
ws.addEventListener("close", closeHandle)
message
message
函数可以获取后端生成并传递的数据消息
const messageHandle = (data) => {
console.log(data)
console.log("前端接收到消息了")
}
ws.addEventListener("message", messageHandle)
error
WebSocket
通信当客户端没有正确使用服务端的地址时会触发error
函数
const errorHandle = () => {
console.log("WebSocket连接出错")
}
ws.addEventListener("error", errorHandle)
上述方法都在满足一定的条件下自动执行,我们可以简写为:
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秒去重新请求连接服务端,只要连接到了服务端,我们就关闭这个定时器:
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
进行卸载