原文链接及内容

这是 Ricardo Morin 的一篇客座文章,他的项目HOWLRikiTraki在我们的演示页面上展示。这篇文章是关于时间动态可视化的,这是HOWL的一个突出功能。

使用 CZML 添加时间动态可视化

可视化的目的是按年份细分野火的历史,以便用户可以使用时间动画一目了然地了解整个历史时期的野火事件,如下图所示。

而下面描述的相同数据的替代静态视图,可能会让用户难以理解。

注:我们可以访问:https://oregonhowl.org/?view=wildfires ,来自己体验这个例子。

首先,在数据中查找 Time 维度

此 OregonHOWL 视图的数据是从野火严重性监测趋势 (MTBS) 网站获取的,并在GeoJSON中重新格式化,以便在基于 Javascript 的 Web 应用程序中轻松使用。源文件由 GeoJSON FeatureCollection 组成,其中每个野火都表示为一个 Point 几何体,如下面的项目所示。显然,时间维度体现在ignitionDate字段上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": "OR4200211850519840705",
"name": "SAND HILL",
"hydrologicUnit": "Alvord Lake",
"acres": 12109.4,
"ignitionDate": "1984-07-05T07:00:00.000Z",
"severityUnburnedAcres": 1470.7,
"severityLowAcres": 9762.5,
"severityModerateAcres": 876.2,
"severityHighAcres": 0,
"severityIncreasedGreenesAcres": 0,
"nonProcessingMaskAcres": 0,
"pdfLink": "http://mtbs.gov/MTBS_Uploads/data/1984/maps/or4200211850519840705_map.pdf",
"kmzLink": "http://mtbs.gov/MTBS_Uploads/data/1984/kmz/or4200211850519840705_19840707.kmz",
"tarLink": "http://mtbs.gov/MTBS_Uploads/data/1984/fire_level_tar_files/or4200211850519840705.tar.gz",
"forestAcres": 0,
"relativeArea": 0.01996
},
"geometry": {
"type": "Point",
"coordinates": [
-118.505,
42.002,
1379.98
]
}
}

...

其次,从源数据构建 CZML 文档

Cesium 提供了三个抽象层,可用于将 3D 可视化合并到应用程序中。最低层暴露在 Cesium 的 Primitive API 中,它提供了对 Cesium 可视化功能的最高级别控制,但代价是更多底层编程,并且需要深入了解 Cesium 的工作原理。下一层是实体(Entity) API,它提供定义高级对象的能力,虽然需要的编程比原始 API 少得多,但它仍然需要相当数量的编码。最后,最高层是 CZML,它为 Cesium 的大量可视化功能提供了一个数据驱动的界面。

但 CZML 到底是什么?引用 Cesium 的 CZML 指南 ,“CZML 是一种 JSON 格式,用于描述时间动态图形场景,主要用于在运行 Cesium 的 Web 浏览器中显示。CZML 文档(document)是一个“ 数据包 ”元素数组,每个元素代表 Cesium 可视化场景中的一个对象(例如,在我们的例子中是一个圆柱体)及其相关属性(例如,颜色、位置、可用性)。值得一提的是,Cesium Sandcastle 网站提供了相当多的可用于学习 CZML 的实时示例。

有两种方法可以将时间维度合并到正在可视化的对象中:a) 数据包的可用性属性(availability property)中; b) 数据包位置属性(position property)的时间维度中。在我们的示例中,由于每个单独的野火的位置在地理上是固定的,因此我们使用数据包的 availability 属性。对于移动对象,我们将使用 location 属性的时间维度。
在我们的示例中,我们使用 AJAX(例如 jQuery.getJSON)从源 JSON 文件加载数据,并将结果传递给函数 makeCZMLAndStatsForListOfFires,该函数迭代 GeoJSON FeatureCollection,为每个 Point 几何体创建一个数据包,并返回一个完整的 CZML 数据结构,准备传递给 Cesium 查看器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function makeCZMLAndStatsForListOfFires (f) {
//第一个数据包是 CZML 头,最重要的部分是clock定义
var mtbsCZML = [
{
id: 'document',
name: 'MTBS',
version: "1.0",
clock: {
interval: '', // 这是我们模拟的时间范围
currentTime: '', // 这是与开始视图相关的时间
multiplier: 10518975,
range: 'LOOP_STOP',
step: 'SYSTEM_CLOCK_MULTIPLIER'
}
}
];

...

f.features.forEach(function (feature) {

...

var czmlItem = {
id: feature.properties.id,
name: 'Fire Name: ' + feature.properties.name,
description:

...

// Here we define the availability for each cylinder, based on the year of the wildfire
//在这里,我们根据野火发生的年份来定义每个cylinder(圆柱体)的可用性
availability: year + '-01-01T00:00:00.000Z' + '/' + year + '-12-31T23:59:59.999Z',
cylinder: {
topRadius: ...,
bottomRadius: ...,
length: ...,
outline: false,
material : {
solidColor : {
color : {
rgba: ...
}
}
}
},

// 这里我们从源 GeoJSON 获取每个圆柱体的位置
position: {
cartographicDegrees: [feature.geometry.coordinates[0], feature.geometry.coordinates[1], feature.geometry.coordinates[2] + cylinderLength/2]
}
};

mtbsCZML.push(czmlItem);
});
// 这里我们设置模拟的整个时间间隔
mtbsCZML[0].clock.interval = statsAll.fromYear + '-01-01T00:00:00.000Z/'+ statsAll.toYear + '-12-31T23:59:59.999Z';
// 在这里,我们设置场景的起始当前时间视图,即报告的最后一个野火年份
mtbsCZML[0].clock.currentTime = statsAll.toYear + '-12-31T23:59:59.999Z';

return {stats: stats, statsAll: statsAll, <b>czml: mtbsCZML</b>};
}

第三,将 CZML 数据加载到Viewer

可视化数据的最后一步是将 CZML 生成的数据添加到我们的 Cesium 查看器中。

1
2
3
4
5
6
7
8
Cesium.CzmlDataSource.load(statsAndCZML.czml).then(function(dataSource) {  
...
fireListDataSource = dataSource;
...
viewer.dataSources.add(fireListDataSource).then(function() {
...
});
});

最后,进行一些调整

您肯定希望对查看器进行一些调整,使其适应您所需的用户体验。一些示例调整包括:

自定义控制动画的 Widget

虽然 Cesium 包含自己的小部件来控制时间动画,但您可能希望自定义用户界面以适应您网站的美感。在 OregonHOWL.org 站点中,我们将默认小部件替换为自定义用户界面,以提供播放、暂停、开始、结束、加快和减慢动画的功能。

Cesium 非常方便地提供了两个可用于控制动画的视图模型:ClockViewModel 和 AnimationViewModel。使用 ClockViewModel,您可以控制开始、结束和时钟仿真速度。使用 AnimationViewModel,您可以控制播放和暂停。

首先,从查看器的 clocks 创建你的 ClockViewModel:

1
clockViewModel = new Cesium.ClockViewModel(_viewer.clock);

然后,使用上面创建的 ClockViewModel 创建 rAnimationViewModel:

1
animationViewModel = new Cesium.AnimationViewModel(clockViewModel);

最后,附加你的 widget click 事件处理程序:

  • Start - clockViewModel.currentTime = clockViewModel.startTime;
  • End - clockViewModel.currentTime = clockViewModel.stopTime;
  • Faster - clockViewModel.multiplier = factor * clockViewModel.multiplier;
  • Slower - clockViewModel.multiplier = clockViewModel.multiplier / factor;
  • Play - animationViewModel.playForwardViewModel.command();
  • Pause - animationViewModel.pauseViewModel.command();

自定义Viewer底部显示的时间轴

虽然 Cesium 提供了默认时间轴,但您可以创建自定义时间轴以满足您的用户界面要求。对于 OregonHOWL.org,由于我们只在 Wildfires 视图中为年度事件制作动画,因此我们自定义了时间轴标签以仅显示年份,而不是像默认小组件那样显示完整时间戳。我们通过覆盖查看器的时间轴 make Label 函数来实现此目的:

请注意,makeLabel 是 Timeline 的私有函数,因此不在官方文档中。如果 Cesium 开发人员决定在将来更改此功能,此功能可能会受到影响。

1
2
3
4
_viewer.timeline.makeLabel = function(date) {
var gregorianDate = Cesium.JulianDate.toGregorianDate(date);
return gregorianDate.year;
};

在仿真执行时更新 UI

正如您在我们的应用程序中看到的,Viewer顶部的标签会动态显示年份和随着年份的流逝而显示的火灾数量。

为此,只需将事件侦听器附加到查看器的 clock,并在回调中执行更新用户界面元素所需的逻辑。

1
2
3
_viewer.clock.onTick.addEventListener(function(event) {
// If the year changed, update label information
});

请务必检查 time 事件的粒度,以便仅在必要时更新用户界面元素。这很重要,因为更新 DOM 的成本非常高,如果实际上没有任何更改,您不希望执行 DOM 作。在我们的示例中,我们只会在年份发生变化时更改标签上的信息。

整个项目的代码是开源的,可在 Github 上获得。与本文最相关的代码可以在 src 目录下的 wildfire 视图中找到。

OregonHOWL.org 中,有一个不同的视图 (The Journey of Wolf OR-7),它使用对象位置的时间维度和数据包的可用性属性的组合来实现时间模拟。该视图的代码在这里

我希望本文能帮助您轻松地将时间维度整合到 Cesium 应用程序中。

开始使用 Cesium ion