# cesium-CZML鸟模型飞行轨迹
# 介绍
在现实生活中除了有静态不动的建筑物和自然场景,还有动态的物体(比如动物、人、车辆、飞机等)。
静态的物体在cesium中很好展示,比如倾斜摄影模型、影像切片图层、矢量数据等。
如果要在cesium中要描述一个物体的动态效果,我这有两种思路:
- 第一种方式可以使用定时器,不断的去修改模型的位置,来实现动态轨迹的效果(可以用,但一点都不优雅)
- 使用CZML来实现模型的动态轨迹效果,之前介绍过CZML数据格式了,不清楚可以去看看我之前介绍的文章(好用,使用起来很优雅,推荐!)
# 思路
介绍一下实现动态效果的思路(以鸟的飞行轨迹为例):
- 定义一个packet用来描述过程
- 加载GLTF模型
- 设置模型的轨迹
- 绘制轨迹路线
- 设置模型可见时间
- 执行CZML方法,运行模型
# 核心代码
# 完整的CZML代码
[
{
"id":"document",
"version":"1.0",
"clock": {
"interval":"2012-08-04T16:00:00Z/2012-08-04T17:04:54.9962195740191Z",
"currentTime":"2012-08-0416.00:00",
"multiplier":10
}
},
{
"id":"bird",
"availability":"2012-08-04T16:00:00Z/2012-08-04T17:04:54.9962195740191Z",
"label":{
"fillColor":[
{
"interval":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"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":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"boolean":true
}
],
"style":"FILL",
"text":"候鸟",
"verticalOrigin":"CENTER"
},
"model":{
"gltf":"CesiumMilkTruck/bird.glb",
"minimumPixelSize":100,
"maximumScale":50
},
"orientation" : {
"velocityReference": "#position"
},
"viewFrom": {
"cartesian": [ -2080, -1715, 779 ]
},
"properties" : {
"fuel_remaining" : {
"epoch":"2012-08-04T16:00:00Z",
"number": [
0, 22.5,
1500, 21.2
]
}
},
"path":{
"material":{
"solidColor":{
"color":{
"interval":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"rgba":[
255,255,0,255
]
}
}
},
"width":[
{
"interval":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"number":5.0
}
],
"show":[
{
"interval":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"boolean":true
}
]
},
"position":{
"interpolationAlgorithm":"LAGRANGE",
"interpolationDegree":1,
"epoch":"2012-08-04T16:00:00Z",
"cartographicDegrees": [
0,118.67161273956299,32.17281545662875,0,
10,118.67247104644775,32.171616706674456,100,
20,118.67345809936525,32.170781204952114,150,
30,118.67478847503664,32.17009100209673,200,
40,118.67603302001952,32.16892854336097,250,
50,118.67659091949461,32.16820199911794,300,
60,118.67817878723145,32.16700318844847,300,
70,118.67916584014891,32.16631295696736,300,
80,118.68109703063963,32.16482349226913,300,
90,118.6821699142456,32.164024257270405,300,
100,118.68315696716307,32.16366096631692,300,
110,118.68628978729248,32.16286172112037,300,
120,118.68774890899657,32.16162651020843,300,
130,118.68968009948729,32.1605002739009,300,
140,118.69156837463379,32.15944668539858,300,
150,118.69431495666502,32.15853840967127,300,
160,118.69706153869627,32.159192369107465,300,
170,118.69800567626953,32.16024596055014,300,
180,118.69852066040039,32.16173550008009,300,
190,118.69843482971193,32.16315235654848,250,
200,118.69671821594238,32.164096915287274,200,
210,118.69431495666502,32.164387546775345,150,
220,118.69251251220705,32.164278560075964,100,
230,118.69229793548583,32.163334003220285,50,
240,118.69208335876465,32.1618444898214,0
]
}
}
]
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# CZML介绍
# 创建一个CZML标识
{
"id":"document",
"version":"1.0",
"clock": {
"interval":"2012-08-04T16:00:00Z/2012-08-04T17:04:54.9962195740191Z",
"currentTime":"2012-08-0416.00:00",
"multiplier":10
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 定义一个场景中对象,设置可见的时间范围
{
"id":"bird",
"availability":"2012-08-04T16:00:00Z/2012-08-04T17:04:54.9962195740191Z"
}
1
2
3
4
2
3
4
# 设置模型对象
"model":{
"gltf":"CesiumMilkTruck/bird.glb",
"minimumPixelSize":100,
"maximumScale":50
},
1
2
3
4
5
2
3
4
5
# 为模型创建一个标签
"label":{
"fillColor":[
{
"interval":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"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":"2012-08-04T16:00:00Z/2012-08-04T18:00:00Z",
"boolean":true
}
],
"style":"FILL",
"text":"候鸟",
"verticalOrigin":"CENTER"
}
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
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
# 设置模型运动方向
"orientation" : {
"velocityReference": "#position"
}
1
2
3
2
3
# 设置相机位置
"viewFrom": {
"cartesian": [ -2080, -1715, 779 ]
},
1
2
3
2
3
# 设置路线
这里使用cartographicDegrees,每四个表示一个时刻的位置和高度,这四个分别为时间(秒)、经度、纬度、高度
"position":{
"interpolationAlgorithm":"LAGRANGE",
"interpolationDegree":1,
"epoch":"2012-08-04T16:00:00Z",
"cartographicDegrees": [
0,118.67161273956299,32.17281545662875,0,
10,118.67247104644775,32.171616706674456,100,
20,118.67345809936525,32.170781204952114,150,
30,118.67478847503664,32.17009100209673,200,
40,118.67603302001952,32.16892854336097,250,
50,118.67659091949461,32.16820199911794,300,
60,118.67817878723145,32.16700318844847,300,
70,118.67916584014891,32.16631295696736,300,
80,118.68109703063963,32.16482349226913,300,
90,118.6821699142456,32.164024257270405,300,
100,118.68315696716307,32.16366096631692,300,
110,118.68628978729248,32.16286172112037,300,
120,118.68774890899657,32.16162651020843,300,
130,118.68968009948729,32.1605002739009,300,
140,118.69156837463379,32.15944668539858,300,
150,118.69431495666502,32.15853840967127,300,
160,118.69706153869627,32.159192369107465,300,
170,118.69800567626953,32.16024596055014,300,
180,118.69852066040039,32.16173550008009,300,
190,118.69843482971193,32.16315235654848,250,
200,118.69671821594238,32.164096915287274,200,
210,118.69431495666502,32.164387546775345,150,
220,118.69251251220705,32.164278560075964,100,
230,118.69229793548583,32.163334003220285,50,
240,118.69208335876465,32.1618444898214,0
]
}
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
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
# 设置属性
properties这里可以设置属性,比如飞机飞行的油量剩余,鸟飞行过程中的体力剩余等
"properties" : {
"fuel_remaining" : {
"epoch":"2012-08-04T16:00:00Z",
"number": [
0, 22.5,
1500, 21.2
]
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Use correct character set. -->
<meta charset="utf-8"/>
<!-- Tell IE to use the latest, best version. -->
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!-- Make the application on mobile take up the full browser screen and disable user scaling. -->
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
<title>cesium-czml-模型动画</title>
<script src="../lib/Cesium-1.89/Build/Cesium/Cesium.js"></script>
<script src="../../../static/lib/vue.min.js"></script>
<style>
@import url(../lib/Cesium-1.89/Build/Cesium/Widgets/widgets.css);
html,
body, #temp {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="temp">
<div style="display: -webkit-flex;display: flex;width: 100%;height: 100%">
<div style="width: 90%;height: 100%">
<div id="cesiumContainer"></div>
</div>
<div style="width: 10%;height: 100%;background-color: #d3d3d3;padding: 30px">
<button class="btn" @click="startCzml">开启鸟动画</button>
<button class="btn" @click="restartCzml">重启鸟动画</button>
<button class="btn" @click="removeCzml">移除CZML模型</button>
</div>
</div>
</div>
<script>
let EarthComp = new Vue({
el: "#temp",
data: {
_viewer: undefined,
czmlPath: "czml/",
dataSource: undefined,
vehicleEntity: undefined,
partsToLoad: undefined
},
mounted: function () {
let that = this;
this.earthInit();
},
methods: {
/**
* 地球初始化
*/
earthInit() {
//天地图token
let TDT_tk = "tdt_token";
//Cesium token
let cesium_tk = "token";
//标注
let TDT_CIA_C = "http://{s}.tianditu.gov.cn/cia_c/wmts?service=wmts&request=GetTile&version=1.0.0" +
"&LAYER=cia&tileMatrixSet=c&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}" +
"&style=default&format=tiles&tk=" + TDT_tk;
// 添加mapbox自定义地图实例
let layer = new Cesium.MapboxStyleImageryProvider({
url: 'https://api.mapbox.com/styles/v1',
username: 'sungang',
styleId: 'styleId',
accessToken: 'accessToken',
scaleFactor: true
});
//初始页面加载
Cesium.Ion.defaultAccessToken = cesium_tk;
let viewer = new Cesium.Viewer('cesiumContainer', {
geocoder: false, // 位置查找工具
baseLayerPicker: false,// 图层选择器(地形影像服务)
animation: true, // 左下角仪表盘(动画器件)
timeline: true, // 底部时间线
homeButton: false,// 视角返回初始位置
fullscreenButton: false, // 全屏
sceneModePicker: false,// 选择视角的模式(球体、平铺、斜视平铺)
navigationHelpButton: false, //导航帮助按钮
imageryProvider: layer, //影像地图
shouldAnimate: true,// 开启动画
});
//调用影响中文注记服务
viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
url: TDT_CIA_C,
layer: "tdtImg_c",
style: "default",
format: "tiles",
tileMatrixSetID: "c",
subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"],
tilingScheme: new Cesium.GeographicTilingScheme(),
tileMatrixLabels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"],
maximumLevel: 50,
show: false
}))
this._viewer = viewer;
// 去除版权信息
this._viewer._cesiumWidget._creditContainer.style.display = "none";
},
/**
* 开启czml动画
*/
startCzml: function () {
let that = this;
that.partsToLoad = [
{
url: "MultipartVehicle_part1.czml",
range: [0, 4500],
requested: false,
loaded: false,
}
];
// 添加一个空白的 CzmlDataSource 来保存多个实体。
let dataSource = new Cesium.CzmlDataSource();
that.dataSource = dataSource;
that._viewer.dataSources.add(dataSource);
that.processPart(that.partsToLoad[0]);
// 在时钟自然到达之前加载一个新部分。
// 请注意,这无法预测用户何时可以快进。
let preloadTimeInSeconds = 100;
that._viewer.clock.onTick.addEventListener(function (clock) {
// 从一开始的时间偏移来识别需要加载的部分
let timeOffset = Cesium.JulianDate.secondsDifference(
clock.currentTime,
clock.startTime
);
// 过滤仅需要立即加载的部分,
// 然后,处理需要加载的每个部分。
that.partsToLoad
.filter(function (part) {
return (
!part.requested &&
timeOffset >= part.range[0] - preloadTimeInSeconds &&
timeOffset <= part.range[1]
);
})
.forEach(function (part) {
that.processPart(part);
});
if (that.vehicleEntity) {
let fuel = that.vehicleEntity.properties.fuel_remaining.getValue(
clock.currentTime
);
if (Cesium.defined(fuel)) {
let fuel_ = "Fuel: " + fuel.toFixed(2) + " gal";
console.log(fuel_);
}
}
});
},
/**
* 辅助函数将一个部分标记为请求,并将其处理到数据源中。
* @param part
*/
processPart: function (part) {
let that = this;
part.requested = true;
that.dataSource.process(that.czmlPath + part.url).then(function () {
part.loaded = true;
// 用相机跟随模型
if (!that._viewer.trackedEntity) {
that._viewer.trackedEntity = that.vehicleEntity = that.dataSource.entities.getById(
"bird"
);
}
});
},
/**
* 重启czml动画
*/
restartCzml: function () {
let that = this;
// 把东西放回起始位置
this._viewer.clock.currentTime = this._viewer.clock.startTime;
this._viewer.clock.shouldAnimate = true;
that.partsToLoad.forEach(function (part) {
part.requested = false;
part.loaded = false;
});
that.dataSource.entities.removeAll();
that.processPart(that.partsToLoad[0]);
},
/**
* 移除czml模型
*/
removeCzml: function () {
this.dataSource.entities.removeAll();
}
},
})
</script>
</body>
</html>
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# 代码解释
# 开启动画
创建一个CzmlDataSource来保存多个实体,然后设置时间监听,进行监听
startCzml: function () {
let that = this;
that.partsToLoad = [
{
url: "MultipartVehicle_part1.czml",
range: [0, 3600],
requested: false,
loaded: false,
}
];
// 添加一个空白的 CzmlDataSource 来保存多个实体。
let dataSource = new Cesium.CzmlDataSource();
that.dataSource = dataSource;
that._viewer.dataSources.add(dataSource);
that.processPart(that.partsToLoad[0]);
// 在时钟自然到达之前加载一个新部分。
// 请注意,这无法预测用户何时可以快进。
let preloadTimeInSeconds = 100;
that._viewer.clock.onTick.addEventListener(function (clock) {
// 从一开始的时间偏移来识别需要加载的部分
let timeOffset = Cesium.JulianDate.secondsDifference(
clock.currentTime,
clock.startTime
);
// 过滤仅需要立即加载的部分,
// 然后,处理需要加载的每个部分。
that.partsToLoad
.filter(function (part) {
return (
!part.requested &&
timeOffset >= part.range[0] - preloadTimeInSeconds &&
timeOffset <= part.range[1]
);
})
.forEach(function (part) {
that.processPart(part);
});
// 获取数据properties中的数据,并打印出来,可以表示鸟的剩余体力
if (that.vehicleEntity) {
let fuel = that.vehicleEntity.properties.fuel_remaining.getValue(
clock.currentTime
);
if (Cesium.defined(fuel)) {
let fuel_ = "Fuel: " + fuel.toFixed(2) + " gal";
console.log(fuel_);
}
}
});
},
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
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
# CZML辅助函数
加载czml并设置相加跟随
/**
* 辅助函数将一个部分标记为请求,并将其处理到数据源中。
* @param part
*/
processPart: function (part) {
let that = this;
part.requested = true;
that.dataSource.process(that.czmlPath + part.url).then(function () {
part.loaded = true;
// 用相机跟随模型
if (!that._viewer.trackedEntity) {
that._viewer.trackedEntity = that.vehicleEntity = that.dataSource.entities.getById(
"bird"
);
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 重启CZML方法
将时间设置回开始时间,重新使用辅助函数
/**
* 重启czml动画
*/
restartCzml: function () {
let that = this;
// 把东西放回起始位置
this._viewer.clock.currentTime = this._viewer.clock.startTime;
this._viewer.clock.shouldAnimate = true;
that.partsToLoad.forEach(function (part) {
part.requested = false;
part.loaded = false;
});
that.dataSource.entities.removeAll();
that.processPart(that.partsToLoad[0]);
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 移除CZML动画实体
/**
* 移除czml模型
*/
removeCzml: function () {
this.dataSource.entities.removeAll();
}
1
2
3
4
5
6
2
3
4
5
6
# 效果

