CZML是一种JSON格式,用于描述时间动态图形场景,主要用于在运行Cesium的Web浏览器中显示。它描述了线条、点、广告牌、模型和其他图形基元,并指定它们如何随时间变化。
虽然Cesium具有丰富的客户端API,但CZML允许它是数据驱动的,以便通用的Cesium查看器可以显示丰富的场景,而无需任何自定义代码。
在许多方面,Cesium和CZML之间的关系类似于谷歌地球和KML之间的关系。
CZML 和 KML 都是用于在各自的客户端中描述场景的数据格式,旨在由各种应用程序生成,甚至可能是手动编写的。两者都意味着与客户端完全无关,以便其他兼容的客户端可以渲染其中描述的场景。
CZML具有许多重要特征,其中一些特征将其与KML区分开来:
这里我们以JavaScript中的数据类型来理解CZML,我们可以把它当成就是一个对象数组,即格式如下:
const czml = [{...},{...}];
数组中的每一个对象都是一个packet(CZML数据包),每个数据包都是一个对象,数据包则用于描述在场景中的单个对象(比如一个点、线、广告牌、模型等)的属性信息,每个数据包都需要一个唯一标识,即id,如果不手动设置id的话,客户端也会自动赋予id一个唯一的值,但为了方便后续我们基于id来对相应的数据包进行操作,因此建议在构建每一个数据包时都赋予它们唯一标识。
数组的第一个对象,即czml[0],为声明对象,用于声明该数据格式为CZML,声明对象包含了id(唯一标识)、name(名称)、version(CZML的版本,暂时只有1.0)、clock(用于设置整个数据集的时钟)等属性。
clock数据类型为ClockStep对象,用于定义时钟在每个嘀嗒时刻是如何前进的,ClockStep对象包含了interval、currentTime、multiplier、range、step等属性,具体解析如下:
interval - 设置开始时间和结束时间
currentTime - 设置当前时间
multiplier - 乘数/倍率,当step被设置为TICK_DEPENDENT时,这是每个嘀嗒时刻前进的秒数;当step设置为SYSTEM_CLOCK_DEPENDENT时,它乘以每个嘀嗒时刻之间的系统时间;该值在SYSTEM_CLOCK模式下被忽略;默认值为1.0
range - 当前时间达到开始时间或结束时间的行为,包含了"UNBOUNDED"、“CLAMPED”、“LOOP_STOP"等选项值,默认值为"LOOP_STOP”,具体解析如下:
○ UNBOUNDED - 时钟将继续朝当前的方向前进
○ CLAMPED - 时钟会停止
○ LOOP_STOP - 当前进过程中到达结束时间时,时钟将跳转到开始时间;当前进过程中到达开始时间时,时钟将停止
step - 设置当前时间是如何前进的,包含了"TICK_DEPENDENT"、“SYSTEM_CLOCK_MULTIPLIER”、“SYSTEM_CLOCK"等选项值;默认值为"SYSTEM_CLOCK_MULTIPLIER”,具体解析如下:
○ TICK_DEPENDENT - 当前时间每个嘀嗒时刻就前进multiplier 秒
○ SYSTEM_CLOCK_MULTIPLIER - 当前时间按自最后一个嘀嗒时刻以来的系统时间前进,乘以multiplier
○ SYSTEM_CLOCK - 时钟总是设置为当前系统时间
const exampleObj = {
id: "document",
name: "My Document",
version: "1.0",
clock: {
interval: "2012-03-15T10:00:00Z/2012-03-16T10:00:00Z",
currentTime: "2012-03-15T10:00:00Z",
multiplier: 60,
range: "LOOP_STOP",
step: "SYSTEM_CLOCK_MULTIPLIER"
}
}
数组的第二个对象开始,都是实体对象,实体对象用于描述实体在三维场景中的信息和状态。
const exampleObj = {
...,
position: {
cartesian: [
0, 4650397.56551457, -3390535.52275848, -4087729.48877329,
300, 3169722.12564676, -2787480.80604407, -5661647.74541255,
600, 1369743.14695017, -1903662.23809705, -6663952.07552171
]
}
}
○ cartographicRadians - 基于WGS84坐标系的坐标,格式为 [ Longitude, Latitude, Height ] 或 [ Time, Longitude, Latitude, Height,Time, Longitude, Latitude, Height, … ]
const exampleObj = {
position: {
cartographicRadians: [ -1.3439035240356338, 0.6457718232379019, 100000 ]
}
}
○ cartographicDegrees - 基于WGS84坐标系的坐标,格式为 [ Longitude, Latitude, Height ] 或 [ Time, Longitude, Latitude, Height,Time, Longitude, Latitude, Height, … ]
const exampleObj = {
position: {
cartographicDegrees: [ -77, 37, 100000 ]
}
}
除了基本属性,还有一类属性是用于描述实体类型的,比如billboard(广告牌,即图标)、box(盒子)、corridor(走廊)、cylinder(圆柱体)、ellipse(椭圆)、ellipsoid(椭圆体)、label(文字)、model(模型)、path(路径)、point(点)、polygon(多边形)、polyline(折线)、polylineVolume(具有体积的折线)、rectangle(矩形)、tileset(3Dtileset)、wall(墙)等
由于不同实体类型的配置项不同,且配置项较多,这里就不做详细介绍了,在后续的应用示例中会涉及到一些相关应用。
const exampleObj = {
id: "uav_001",
name: "001号无人机",
availability: "2012-08-04T10:00:00Z/2012-08-04T15:00:00Z",
position: {
epoch: "2012-08-04T10:00:00Z",
cartographicDegrees: [
0, 104.374422, 28.117186, 150,
100, 104.375259, 28.116129, 180,
200, 104.375164, 28.115771, 220,
300, 104.376489, 28.114326, 250
]
},
model: {
gltf: "/public/model/uav.glb",
scale: 3.0,
},
}
间隔是通过interval属性来设置的,其值为两个时间间隔。
应用场景:比如添加一架飞机模型,但是要求它并不是一直显示的,它在某个时间段是隐藏的,而在某个时间段则是显示状态,那这时候就可以通过interval来对show属性(该属性为布尔值,用于控制显示和隐藏)进行配置,从而实现所要的效果。
const exampleObj = {
id: "air_001",
name: "001号飞机",
availability: "2012-08-04T10:00:00Z/2012-08-04T15:00:00Z",
position: {
cartographicDegrees: [ 104.374422, 28.117186, 180 ]
},
model: {
show: [
{
interval: "2012-08-04T10:00:00Z/2012-08-04T10:02:00Z",
boolean: false
},
{
interval: "2012-08-04T10:02:00Z/2012-08-04T11:00:00Z",
boolean: true
},
{
interval: "2012-08-04T11:00:00Z/2012-08-04T15:00:00Z",
boolean: false
}
],
gltf: "/public/model/air.glb",
scale: 1.0
}
}
代码解析:在上述代码中,飞机模型在早上10点到10点02分这段时间内是隐藏状态,十点02分到11点这段时间为显示状态,而11点到下午3点这段时间又回到隐藏状态。
表达不同时间,位置信息的不同可以用以下写法:
const exampleObj = {
...,
position: {
cartesian: [
"2012-04-30T12:00Z", 4650397.56551457, -3390535.52275848, -4087729.48877329,
"2012-04-30T12:01Z", 3169722.12564676, -2787480.80604407, -5661647.74541255,
"2012-04-30T12:02Z", 1369743.14695017, -1903662.23809705, -6663952.07552171
]
}
}
但是,上述写法需要给每个位置信息指定时间,写起来比较麻烦,因此可以采用以下写法:
const exampleObj = {
...,
position: {
epoch: "2012-04-30T12:00Z",
cartesian: [
0, 4650397.56551457, -3390535.52275848, -4087729.48877329,
60, 3169722.12564676, -2787480.80604407, -5661647.74541255,
120, 1369743.14695017, -1903662.23809705, -6663952.07552171
]
}
}
epoch表示起始时间,其使用ISO8601规范来表示日期和时间,即使用"2012-04-30T12:00Z"这种写法来表示时间,采用距离起始时间的秒数来改变数据。
代码解析:比如上述代码中,0相当于距离epoch的秒数为0秒,即0 == "2012-04-30T12:00Z"
;60相当于距离epoch的秒数为60秒,即60 == "2012-04-30T12:01Z"
;120相当于距离epoch的秒数为120秒,即120 == "2012-04-30T12:02Z"
。
应用场景:我们有一个需要插值的属性,时间为0到10秒,间隔为1.0秒,第一个数据包为0到3秒,第二个数据包为8到10秒,在客户端还没有接收到包含4到7秒的数据包时,如何渲染时间为5的场景。
const exampleObj = {
...,
position: {
epoch: "2012-04-30T12:00Z",
cartesian: [
0, 4650397.56551457, -3390535.52275848, -4087729.48877329,
1.0, 3169722.12564676, -2787480.80604407, -5661647.74541255,
2.0, 1369743.14695017, -1903662.23809705, -6663952.07552171,
3.0, 1369743.14695017, -1903662.23809705, -6663952.07552171
],
previousTime: -1.0,
nextTime: 4.0
}
}
代码解析:上述代码中,nextTime的作用是告诉客户端在3.0后下一个时间是4.0;3的后面是8,根据nextTime我们就知道3和8之间肯定还有一段数据没有接收到,所以在开始插值之前我们就需要先等待数据读取完成。
没有必要同时设置previousTime和nextTime,在不同的情况下选择使用其中最方便的一个就可以了,只要定义其中的一个,在进行插值前就会首先对数据进行完整性检查。
每个数据包将作为单独的事件流式传输到客户端,从而解决了大json文件读取慢的问题。
我们通常会使用一个数据包来描述一个对象,其包含了该对象所有的图形属性;不过还可以使用其他的方式来表示,比如使用多个数据包来描述同一个对象,只需要让这些数据包拥有相同的id即可,一般在当对象的属性跨越多个时间间隔或者一个时间间隔又多个时间戳采样时使用这种方式。
当客户端接受到一个数据包时,会遍历数据包的每一个属性;针对每个属性,会遍历属性定义的每个时间间隔;针对每个时间间隔,会判断该时间间隔是否已定义,如已定义,则更新已存在的间隔,如没定义,则根据这个时间间隔来创建一个新的。
当更新一个已存在的时间间隔时,如果有子属性,则会覆盖掉原有的值;而当已有的属性和新接收到的属性都包含时间戳采样时,新接收的采样不会覆盖已有的,而是加到已有的采样列表之中。
当新的时间间隔与已有的发生重叠时,新的时间间隔拥有较高的优先级,原有的时间间隔将被截断或者整个移除。
在同一个数据包中的时间间隔的时间必须以增序排列,不同的数据包之间则没有要求,但是对于不连续的采样还是应考虑合理的插值顺序。
使用availability属性来判断对象的数据在什么时间段内是可用的。
const exampleObj = {
id: "uav",
// 实体在什么时间范围可用
availability: "2012-08-04T10:00:00Z/2012-08-04T15:00:00Z",
position: {
epoch: "2012-08-04T10:00:00Z",
cartographicDegrees: [
0, 104.374422, 28.117186, 150,
100, 104.375259, 28.116129, 180,
200, 104.375164, 28.115771, 220,
300, 104.376489, 28.114326, 250
],
},
model: {
gltf: "/public/model/uav.glb",
scale: 3.0,
}
}
可以给CZML添加自定义属性,最好采用特定的前缀进行区分。
错误的加载方式
以import…from…的方式导入,项目会报错。(禁用)
import test from '/public/test.czml';
cosnt promiseData = Cesium.CzmlDataSource.load(test);
正确的加载方式
以.czml文件格式进行加载。(不太推荐×)
cosnt promiseData = Cesium.CzmlDataSource.load('public/test.czml');
编写一个对象数组来代替.czml文件进行加载。(推荐√)
// 创建czml
const czml = [
{
id: "document",
name: "MYCZML",
version: "1.0",
},
{
id:"id1",
...
}
];
// 加载czml数据
const promiseData = Cesium.CzmlDataSource.load(czml);
以.json文件格式取代.czml格式进行加载。(推荐√)
const promiseData = Cesium.CzmlDataSource.load('public/test.json');
// 加载运动的装甲车
const czml = [
{
"id": "document",
"clock": {
"interval": "2022-10-14T10:08:00+08:00/2022-10-15T16:08:00+08:00",
"currentTime": "2022-10-14T10:08:00+08:00",
"step": "SYSTEM_CLOCK_MULTIPLIER",
"range": "CLAMPED",
"multiplier": 60,
},
"version": "1.0"
},
{
"id": "Air",
"name": "Cesium Air",
"description": "Cesium Air",
"position": {
"epoch": "2022-10-14T10:08:00+08:00",
"cartographicDegrees": [
0, -94.030766, 41.827829, 0,
1000, -94.035766, 41.827829, 0,
2000, -94.035766, 41.829013, 0
]
},
"model": {
"gltf": new URL('./assets/GroundVehicle.glb', import.meta.url).href,
"scale": 1,
"minimumPixelSize": 200,
},
"path" : {
material: {
polylineOutline: {
color: {
rgba: [255, 0, 255, 255],
},
},
},
width: 8,
leadTime: 10,
trailTime: 1000,
resolution: 5,
},
label: {
fillColor: {
rgba: [255, 255, 255, 255],
},
horizontalOrigin: "LFET",
pixelOffset: {
cartesian2: [0, 60],
},
// style: "FILL",
text: "汽车",
showBackground: true,
backgroundColor: {
rgba: [112, 89, 57, 0],
},
},
},
];
const dataSourcePromise = viewer.dataSources.add(
Cesium.CzmlDataSource.load(czml)
);
dataSourcePromise
.then(function (dataSource) {
viewer.trackedEntity = dataSource.entities.getById(
"Air"
);// 对应czml实体id
viewer.dataSources.add(dataSource).then(function(ds){
var s = ds.entities.getById("Air"); // 对应czml实体id
s.orientation =new Cesium.VelocityOrientationProperty(s.position);
});
// viewer.flyTo(dataSource)
})
.catch(function (error) {
window.alert(error);
});
// 加载透明红色球体
const czml = [
{
"id": "document",
"name": "CZML typhoon",
"version": "1.0",
"clock": {
"interval": "2022-10-14T10:08:00+08:00/2022-10-15T16:08:00+08:00",
"currentTime": "2022-10-14T10:08:00+08:00",
"step": "SYSTEM_CLOCK_MULTIPLIER",
"range": "CLAMPED",
"multiplier": 60,
},
},
{
"id": "typhoon",
"name": "Red sphere with black outline",
"position": {
"epoch": "2022-10-14T10:08:00+08:00", // 开始时间
"cartographicDegrees": [
0, 100, 20, 0,
500, 102, 20, 0,
100, 102, 22, 0
],
},
"ellipsoid": {
"radii": {
"cartesian": [30000, 30000, 30000],
},
"fill": true,
"material": {
"solidColor": {
"color": {
"rgba": [255, 0, 0, 100],
},
},
},
"outline": true,
"outlineColor": {
"rgbaf": [0, 0, 0, 1],
},
},
}
]
viewer.dataSources.add(Cesium.CzmlDataSource.load(czml))