转自文章 Cesium随笔(5)CZML介绍(介个文章是转的嘿嘿)
CZML的结构
CZML是一种用来描述动态场景的JSON架构的语言,主要用于Cesium在浏览器中的展示。它可以用来描述点、线、布告板、模型以及其他的图元,同时定义他们是怎样随时间变化的。Cesium拥有一套富客户端API,通过CZML采用数据驱动的方式,不用写代码我就可以使用通用的Cesium viewer构建出丰富的场景。Cesium与CZML的关系就如同Google Earth和KML的关系。CZML和KML都是用来描述场景的数据格式,可以通到很多其他的程序自动生成,或者手写也可以。CZML拥有很多的特性,其中有一些区别于KML的:
- CZML 是基于JSON的。
- CZML可以准确的描述值随时间变化的属性。理由,一条在某一时间内是红色的而在另一时间内是蓝色的。同时客户端可以根据时间戳进行差值。加入有一辆车,分别定义了两个不同时间的位置,通过CZML定义的差值算法,客户端可以准确的显示在这两个时间点之间车的位置。所有属性都可以是随时间变化的。
- CZML通过增量流的方式传送到客户端。在场景显示之前,整个CZML文档需要首先被下载到客户端。在某些情况下,个别客户端可能会加入或离开正在传输的流。(这段翻译的不好)
- CZML高度优化,旨在解析时更紧凑也更容易,让人工的读写更容易。
- CZML可扩展,尽管CZML的主要作用在与虚拟地球客户端程序与场景的交流,但它可以很容易的通过扩展来满足其他一些辅助的程序对静态或动态数据的需求。例如,随时间动态变化在数据就可以用在某些2D的图表程序中。
- CZML是一个开放的格式。我们希望有更多的程序能使用CZML,同时期待有一天它也能成为OGC一样的标准。
- 可以通过czml-writer来生成CZML,这个程序维护在Github上。
我们将CZML标准以及它的相应实现分为4个部分:
CZML Structure -- CZML文档的整体结构
CZML Content --内容
CZML in Cesium -- Cesium中解析和显示CZML的流程
czml-writer-Architecture –czml-writer的架构
数据结构
CZML是json的一个子集,也就是说一个有效的CZML文档同时也是一个有效的JSON文档。特别的,一个CZML文档包含一个JSON数组,数组中个每一个对象都是一个CZML数据包(packet),一个packet对应一个场景中的对象,例如一个飞机。
注:在下面的例子中我们使用javascript方式的注释来帮助理解CZML,但在真实场景中是不允许的。
[
// packet one
{
"id":"GroundControlStation"
"position":{"cartographicDegrees":[-75.5,40.0,0.0]},
"point":{
"color":{"rgba":[0,0,255,255]},
}
},
// packet two
{
"id":"PredatorUAV",
// ...
}
]
每个packet都有一个id属性用来标示我们当前描述的对象。id在同一个CZML以及与它载入同一个范围(scope)内的其他CZML文件中必须是唯一的。随后我们将讨论scope的含义。
假如没有指定id,那么客户端将自动生成一个唯一的id。但是这样的话在随后的包中我们就没有办法引用它了,例如我们不能再往它里面添加数据。
除了id以为,一个包通常还包含0到多个(正常情况下是1到多个)定义对象图形特征的属性。正如上面的例子,我们定义了一个“GroundControlStation”对象,它拥有一个固定的经度-75.0维度40.0的WGS84坐标,高度为0,随后一个蓝色的点将会绘制在他的坐标位置处。
CZML还有很多标准的属性,包括用来添加点、布告板、模型、线以及其他图形到场景的属性。所有这些属性将在CZML Content这节讨论。在这里我们主要讨论这些数据是怎样组织的。例如,我们怎样定义一个属性,使它在两个不同的时间拥有两个不同的值。
时间间隔Intervals
通常情况下CZML的属性值是一个数组,数组中的每个元素对应每一个不同的时间,属性的值。时间间隔的定义使用interval 属性,通过ISO8601 interval格式的字面值表示。
{
"id":"myObject",
"someProperty":[
{
"interval":"2012-04-30T12:00:00Z/13:00:00Z",
"number":5
},
{
"interval":"2012-04-30T13:00:00Z/14:00:00Z",
"number":6
}
]
}
这里我们定义了一个someProperty属性,它有两个时间间隔,第一个是从中午到下午一点,属性值为5,第二个是从下午一点到下午两点,属性值为6,在时间由第一个间隔变化到第二个间隔的时候,属性值会瞬间从5变到6。注意,这里的时间为UTC(协调世界时),而我们国家通常使用的是UTC+8也就是北京时间,所以要注意。
我们使用number来表示属性是一个数字类型的属性。值得注意的是有些属性允许通过不同的格式来定义,例如表示位置的属性可以通过笛卡儿坐标的X、Y、Z表示,也可以通过经纬度和高程来表示。CZML Content一节将会详细讨论。
Interval属性是可选的,你可以定义也可以不定义,如果没有定义,默认为整个时间。通常定义多个无限的时间间隔或者间隔之间有重叠是没什么意思的,如果你非要这么做,那么在CZML中最后定义的时间间隔将拥有较高的优先级。
通常情况下属性值只跨越一个时间间隔,这时候你间隔列表可以省略。
{
"id":"myObject",
"someProperty":{
"interval":"2012-04-30T12:00:00Z/14:00:00Z",
"number":5
}
}
和之前的一样,加入interval跨越整个时间,那么你可以省略掉它。对于一些简单的值,例如上面我们使用的数字类型,加入它对于整个时间来说都不变,那么我还可以定义的更简单一些。
{
"id":"myObject",
"someProperty":5
}
这种简略的表示法,适用于所有值是简单的json数据类型(string、number、boolean)的的属性。
Composite Values(复合值)
一些复杂的值复合值例如笛卡尔坐标位置或颜色,是通过json数组的形式来表示。例如坐标位置,所使用的数组有三个元素,分别对应于坐标的x、y和z。
{
"id":"myObject",
"someComplexProperty":{
"cartesian":[1.0,2.0,3.0]
}
}
合成值必须定义在间隔中,即使他的时间间隔是无限的也是一样,不能采用简略的写法。假如允许值[1.0,2.0,3.0]直接作为someComplexProperty的属性值,那么客户端代码就需要解析CZML并且判断数组里面的值到底是时间间隔列表(intervals list)还是说是简单的值。为了简单起见,我们不允许这样的写法。
Sampled Property Values(属性值采样)
到目前为止我们讨论的了怎样为那些横跨整个时间的的属性定义单一的值,以及针对离散的时间间隔定义不同的值。一些属性(CZML Content一节将告诉你是哪些)允许你定义时间戳采样,客户端通过给定的时间差值计算出属性的值。时间的定义是采用ISO8601标准字符串。
{
// ...
"someInterpolatableProperty":{
"cartesian":[
"2012-04-30T12:00Z",1.0,2.0,3.0,
"2012-04-30T12:01Z",4.0,5.0,6.0,
"2012-04-30T12:02Z",7.0,8.0,9.0
]
}
}
[1.0, 2.0, 3.0]
和[4.0, 5.0, 6.0]线性差值得出,为[2.5, 3.5, 4.5]。
简单起见,时间使用距离起始时间(epoch)的秒数来表示。与每个时间都通过ISO8601字符串表示相比这样不是很精确,不过对于采样间隔在一天以内,或者偏移值时整数的时候这样的精度还是绰绰有余。
{
// ...
"someInterpolatableProperty":{
"epoch":"2012-04-30T12: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
]
}
}
最后,使用时间戳采样的属性还有一些附加的可选的子属性,用来控制采样的方式。
{
// ...
"someInterpolatableProperty":{
"epoch":"2012-04-30T12: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
],
"interpolationAlgorithm":"LAGRANGE",
"interpolationDegree":5
},
}
interpolationAlgorithm
定义了采样使用的算法,下面的表格中列出了可能的值。interpolationDegree定义了用来插值所使用的
多项式的次数,1表示线性差值,2表示二次插值法,默认为1.
每个采样值的时间没有必要落在包含它的时间间隔内。但是这些采样值在他们的时间间隔内不会被使用。这样的好处就是对于多次差值有更好的精度。
下表总结了可插值属性的一些子属性:
名称 |
Scope 范围 |
JSON类型 |
说明 |
epoch |
Packet |
string |
使用ISO8601规范来表示日期和时间 |
nextTime |
Packet |
string or number |
在时间间隔内下一个采样的时间,可以通过ISO8061方式,也可以通过与epoch秒数来定义。它决定了不同packet之间的采样是否有停顿。 |
previousTime |
Packet |
String or number |
在时间间隔内前一个采样的时间,可以通过ISO8061方式,也可以通过与epoch秒数来定义,它决定了不同packet之间的采样是否有停顿。 |
InterpolationAlgorithm |
Interval |
String |
用于插值的算法,有LAGTANGE,HERMITE和GEODESIC。默认是LAGRANGE。如果位置不在该采样区间,那么这个属性值会被忽略。 |
|
Interval |
Number |
|
EventSource and Streaming
如果将整个CZML文件安排在一个大的JSON数组中,这使增量加载变得很困难。虽然浏览器允许我们访问没有读取完的流数据,但是解析不完整的数据需要漫长而繁琐的字符串操作。
为了高效,CZML使用浏览器的server-sent events(EventSource)API来处理流数据。在实际操作中,每一个CZML的packet包会被作为单独的一个事件传输到客户端。
event: czml
data: {
// packet one
}
event: czml
data: {
// packet two
}
当浏览器接收到一个packet后就会发出一个事件,事件中会包含刚刚接收到了数据。这样我们就可以通过增量的方式高效的处理CZML数据。
目前为止,我们都是使用一个packet包来描述一个对象,这个packet包含了所有这个对象的图形属性。我们还可以使用其他的方式,例如一个CZML文件或流可以包含多个packet,每个packet都有相同的id,分别描述同一个对象的不同方面的属性。
事实上在大多数情况下我们使用两个packet来描述一个对象。当对象属性跨越多个时间间隔,或者一个时间间隔有很多个时间戳采样时,这样做就很有用了。通过将一个属性定义打包进多个packet,我们可以使数据更快的传输到Cesium中,减少用户等待的时间。
当客户端接收到一个packet,它会遍历packet中的每一个属性。对于每个属性,它会遍历属性定义的每个时间间隔。对于每个时间间隔,它会判断这个时间间隔是否已经定义,假如这个间隔已经定义,将更新已经存在的间隔,如果没有定义,那么就根据这个间隔创建一个新的。
当更新一个已存在的时间间隔时,假如有子属性,那么子属性将覆盖原有的值。有一个例外,就是当已有的属性和新接收到的属性都包含时间戳采样时,新接收到的采样不会覆盖已有的,而是加到已有的采样列表中。
当新的时间间隔与已有的发生重叠时,新的间隔拥有较高优先级,原有的间隔将被截断或者整个移除。这点必须要牢记。
在同一个packet中的时间间隔的时间必须以增序排列,不同packet之间就没有要求。但是对于不连续的采样还是应该考虑合理的插值顺序。
考虑一下,我们有一个需要插值的属性,时间是0到10秒,间隔为1.0秒。第一个packet包含0到3秒,第二个包含8到10秒。在客户端还没有接收到包含4到7秒的packet时,我们可以渲染时间为5的场景吗??
一种方式是我们就是使用已经接收到的两个packet来插值。这可能不太好,因为即使我们使用高次插值,在这个两个packet的间隙中得出的值可能也是错的。所以,我们最好还是先暂停等待中间的那个packet赶快到来。但是我们是怎么知道两个包之间有间隙的呢??我们可以猜想,刚开始的时候间隔是1秒,后来变成了5秒,再后来又变回了1秒,所以中间应该还有一个包。这个猜想只是逗你玩的,万一人家中间就是想少采样几个点呢。
这都不是事儿,CZML提供了previousTime和nextTime子属性,用来处理这种情况。
{ // ... "someInterpolatableProperty": { "epoch": "2012-04-30T12:00:00Z", "cartesian": [ 0.0, 1.0, 2.0, 3.0, 1.0, 4.0, 5.0, 6.0, 2.0, 7.0, 8.0, 9.0, 3.0, 10.0, 11.0, 12.0 ], "previousTime": -1.0, "nextTime": 4.0 } }
它的作用是告诉客户端3.0后下一个时间是4.0,就像我们上面举的那个例子,3的后面是8,根据nextTime我们就知道3和8之间肯定还有一段数据没有接收到,所以在开始插值之前我们就需要先等待数据读取完成。
没有必要同时设置previousTime和nextTime,在不同的情况下选择使用其中最方便的一个就可以了。只要定义其中的一个,在进行插值前Cesium就会首先对数据进行完整性检查。
Availability属性
除了id属性外,CZML的packet还有一个特别的额外属性availability。
{
"id": "PredatorUAV",
"availability": "2012-04-30T12:00:00Z/14:00:00Z",
// ...
}
它用来标示一个对象的数据在什么时候是可用的。假如一个对象在当前的动画时间内是可用,但是客户端现在还没有获取到相应的数据(可能在下一packet里面,但现在还没有获取到),那么Cesium就会先暂停,直到获取到数据为止。这个属性的值可以一个字符串表示的一个时间段,也可以是一个字符串数组表示的多个时间段。
假如availability变化了或者被发现是不正确的,那么随后的packet将会更新它的值。例如,一个SGP4 propagator可能总是可用的,但是随后他发出了一个异常,所以他的值需要调整。如果availability属性没有定义,那么默认是全部时间内都可用的。Availability的范围被限定到一个特定的CZML流中,所以对同一个对象在两个不同的流中可以有不同的availability。在一个流中,只有定义在最后的那个availability起作用,其他的都会被忽略。在某一时刻,如果一个对象是可用的,那么这个对象至少要有一个可用的属性并且在此时间段内需要的属性都要有定义(也就是获取到了数据),不然Cesium就会等待数据直到接收到数据为止。
扩展Extending CZML
可以给CZML增加自定义属性,但是为避免冲突,我们强烈建议你给你的自定义属性加上你特有的前缀。