Cesium | 利用Property机制实现轨迹回放

前言

我们在前面的文章中介绍过Cesium的Property机制,了解了它的作用以及它的用法,这篇文章我们通过一个实际场景来深入学习和复习一下Property机制。

在智慧城市系统中,必不可少的就是轨迹回放或者让模型沿着指定路径行走,而Cesium提供的Property机制能够在很好的实现这样的场景的同时保证性能。下面简单来讲一下Property,更多的知识可以阅读Cesium的Property机制究竟有多香

property

简单来说,Property就是Cesium提供的一种机制,可以让指定的属性随时间自动变化并赋值,操作便捷的同时能够节省性能。在Cesium中Property可以与时间轴进行关联,并根据时间返回对应的属性,我们再通过返回的值去改变我i们想要修改的状态值。

实现

一样的,首先我们整理思路,想要实现轨迹回放,我们需要一组轨迹坐标串、一个定义好时间点状态的Property,以及一个用来在轨迹上行走的model(本文用billboard代替)。

我们拿到了轨迹坐标串后,需要通过计算获得几个关键节点的距离、时间,生成完整的property作为我们实体的position属性。

基本思路如上,接下来我们开始动手实现。思路只提供大致方向,在实现中可能会对某些方面进行部分修改。一般场景下我们得到的都是经纬度坐标,而操作时需要使用笛卡尔坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
let lnglatArr = [
[121.527589, 38.957547],
[121.527825, 38.960166],
[121.536472, 38.959098],
[121.540442, 38.958464],
[121.543489, 38.958131],
[121.542888, 38.955861],
[121.542266, 38.953325],
]

let positions = lnglatArr.map((item) => {
return Cesium.Cartesian3.fromDegrees(item[0] , item[1] , 0.5);
})

接下来我们需要获取回放中每段的时间以及总时间,这里我们需要通过速度和坐标点之间的距离算出所需时间(这是小学知识吧,t = l/s)。

我们需要记录每个点位的时间,用来为property添加节点,这里我们将它写成一个方法方便调用。入参就是我们的坐标串与速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 计算每个点位时间和总时间
getSiteTimes(positions, speed) {
let timeSum = 0;
let times = [];
for (let i = 0; i < positions.length; i++) {
if (i === 0) {
times.push(0);
continue;
}

timeSum += this.spaceDistance([positions[i - 1], positions[i]]) / speed;
times.push(timeSum);
}
return {
timeSum,
siteTimes: times,
};
},

可以看到我们在循环内又定义了一个方法spaceDisatance,这个方法用来计算传入的两个点之间的距离。Cesium为我们提供了相关方法计算两点之间的距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 计算距离
spaceDistance(positions) {
let distance = 0;
for (let i = 0; i < positions.length - 1; i++) {
let point1cartographic = Cesium.Cartographic.fromCartesian(
positions[i]
);
let point2cartographic = Cesium.Cartographic.fromCartesian(
positions[i + 1]
);
/**根据经纬度计算出距离**/
let geodesic = new Cesium.EllipsoidGeodesic();
geodesic.setEndPoints(point1cartographic, point2cartographic);
let s = geodesic.surfaceDistance;
//返回两点之间的距离 如果带高度的话
s = Math.sqrt(
Math.pow(s, 2) +
Math.pow(point2cartographic.height - point1cartographic.height, 2)
);
distance = distance + s;
}
return distance.toFixed(2);
},

现在我们拿到了总时间和每两个点之间的时间,现在可以开始构建property和时间轴了。首先我们需要定义开始时间结束时间并给到viewerclock实例上代表时间段,接着构筑Property实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let timeObj = this.getSiteTimes(positions, speed);
let startTime = Cesium.JulianDate.fromDate(new Date());
let stopTime = Cesium.JulianDate.addSeconds(
startTime,
timeObj.timeSum,
new Cesium.JulianDate()
);

viewer.clock.startTime = startTime.clone();
viewer.clock.stopTime = stopTime.clone();
viewer.clock.currentTime = startTime.clone();

// 生成property
var property = new Cesium.SampledPositionProperty();
for (var i = 0; i < positions.length; i++) {
const time = Cesium.JulianDate.addSeconds(
startTime,
timeObj.siteTimes[i],
new Cesium.JulianDate()
);
property.addSample(time, positions[i]);
}

最后一步,我们添加一个Entity实体,做轨迹回放的物体,这里可以使用任何实体。添加到场景中后需要利用viewer.trackedEntity动态追踪实体。我们的轨迹回放就大功告成了。

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
this.animateEntity = viewer.entities.add({
// 实体可用性,在指定时间内返回有效数据
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: startTime,
stop: stopTime,
}),
]),
// 位置信息随时间变化property
position: property,
// 实体方向
orientation: new Cesium.VelocityOrientationProperty(property),
billboard: {
image: "./static/blueCamera.png",
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
// 轨迹路径
path: {
resolution: 1,
width: 10,
material: Cesium.Color.RED,
},
});

viewer.trackedEntity = this.animateEntity;

效果如下:

animate1.gif

封装

这里简单提一下利用Class进行封装吧,没有什么大的难点,就是把实现步骤拆分,选定参数进行暴露。这里简单书写一下代码结构。

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
export default class TrackedAnimate {
constructor(viewer, cb) {
this.viewer = viewer;
this.cb = cb;
}

startRoam(positions, speed) {
let newPositions = positions.map(item => {
return Cesium.Cartesian3.fromDegrees(item[0], item[1], 0.5);
});
this.addRoamLine(newPositions, speed);
}

//添加漫游路线
addRoamLine(positions, speed) {
this.endRoam();

let timeObj = this.getSiteTimes(positions, speed);
let startTime = Cesium.JulianDate.fromDate(new Date());
let stopTime = Cesium.JulianDate.addSeconds(
startTime,
timeObj.timeSum,
new Cesium.JulianDate()
);

this.viewer.clock.startTime = startTime.clone();
this.viewer.clock.stopTime = stopTime.clone();
this.viewer.clock.currentTime = startTime.clone();

let property = this.getSampledPositionProperty(
positions,
startTime,
timeObj.siteTimes
);
this.addModel(startTime, stopTime, property);

this.viewer.trackedEntity = this.animateEntity;
this.timoutId = setTimeout(e => {
this.endRoam();
}, timeObj.timeSum * 1000);
}

addModel(startTime, stopTime, property) {
//……
}

endRoam() {
//……
}

getSampledPositionProperty(positions, startTime, siteTimes) {
//……
}

//计算每个点位时间与总时间
getSiteTimes(positions, speed) {
//……
}

//计算距离
spaceDistance(positions) {
//……
}
}

调用:

1
2
3
4
5
6
this.trackEntity = new TrackedAnimate(viewer , () => {
console.log('moe_');
});

this.trackEntity.startRoam(this.movePositions, 25);
this.trackEntity.endRoam();

最后

上述我们简单实现了轨迹回放的场景,可以看到在封装中我们还设置了回调函数的参数,后续可以进行很多扩展比如多视角回放,特定位置做特定功能等等,都可以进行扩展。Property的使用场景有很多,这只是其中一部分,感兴趣的可以深入研究共同探讨。


Cesium | 利用Property机制实现轨迹回放
https://moewang0321.github.io/2021/06/10/Cesium利用Property机制实现轨迹回放/
作者
Moe Wang
发布于
2021年6月10日
许可协议