cesium 根据SampledPositionProperty与timeline实现轨迹回放功能(跟随视角、上帝视角) 详细代码与理解,文末附完整代码
大致原理摘要 引用出处
这个主要是用entity的orientation方向属性实现的,问题就在于怎么获取到实时的四元素,我们知道VelocityOrientationProperty这个是根据当前位置实时计算方向的,但是这个方向在转角时是瞬间转的,达不到均匀的转动效果,所以我们需要两个插值模型SampledPositionProperty, 一个位置和一个方向的,但是现在就出现另外一个问题,那就是orientation应该用哪个。其实我们需要将两个插值模型结合起来,orientation使用回调,获取当前时间下的位置和方向,查看SampledPositionProperty的源码,实现自己需要的方向模型,也就是把SampledPositionProperty的方向替换成我们自己的插值方向,然后回调函数里面返回一个四元素。那么我们就可以计算出orientation了,然后得到需要移动的相机的direction和up,结合position, 相机实时setView就可以了,这样实现的就是视角在中心处沿着路径移动,还可以加上lookDown偏移角度和moveBackward距离,就是我示例上演示的效果了。
一、生成cesium Access Token
cesium ion(获取地址)
1、登录
2、Default Token
二、创建cesium实体
代码与注解
Cesium.Ion.defaultAccessToken = 'token'
var viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false,
timeline: true,// 必须为true显示时间线组件(如不想显示可以使用样式层叠表修改display:none) 否则viewer.timeline.zoomTo会报undefined错误
homeButton: false,
fullscreenButton: false,
infoBox: false,
sceneModePicker: false,
navigationInstructionsInitiallyVisible: false,
navigationHelpButton: false,
animation: false,
shouldAnimate: true // 必须为true开启动画 否则不会达到飞机模型飞行动画效果
});
// viewer.animation.container.style.display = 'none';//隐藏动画控件
// viewer.timeline.container.style.display = 'none';//隐藏时间线控件
// viewer.geocoder.container.style.display = 'none';//隐藏地名查找控件
// viewer.cesiumWidget.creditContainer.style.display = 'none';//隐藏ceisum标识
viewer.scene.globe.enableLighting = true;
三、根据模拟数据配置timeline
代码与注解
let data = [{ longitude: 116.405419, latitude: 39.918034, height: 0, time: 0 },
{ longitude: 120.2821, latitude: 33.918145, height: 0, time: 30 },
{ longitude: 115.497402, latitude: 39.344641, height: 70000, time: 60 },
{ longitude: 107.942392, latitude: 29.559967, height: 70000, time: 110 },
{ longitude: 106.549265, latitude: 29.559967, height: 0, time: 120 }];
// 起始时间
let start = Cesium.JulianDate.fromDate(new Date());
// 结束时间
let stop = Cesium.JulianDate.addSeconds(start, 120, new Cesium.JulianDate());
// 设置始时钟始时间
viewer.clock.startTime = start.clone();
// 设置时钟当前时间
viewer.clock.currentTime = start.clone();
// 设置始终停止时间
viewer.clock.stopTime = stop.clone();
// 时间速率,数字越大时间过的越快
viewer.clock.multiplier = 1;
// 时间轴
viewer.timeline.zoomTo(start, stop);
// 循环执行,即为2,到达终止时间,重新从起点时间开始
// viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
// 摄像机聚焦到开始位置
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.405419, 39.918034, 100000)
})
四、根据模拟数据创建对应时间与坐标的SampledPositionProperty并生成entity实体
代码与注解
let property = createProperty(data);
// 添加entity实体
let planeModel = viewer.entities.add({
// 和时间轴关联
availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
start: start,
stop: stop
})]),
id: 'move',
position: property,
// 根据所提供的位置计算模型的朝向
orientation: new Cesium.VelocityOrientationProperty(property),
// 模型
model: {
uri: './Apps/SampleData/models/CesiumAir/Cesium_Air.glb',
minimumPixelSize: 128
},
path: {
resolution: 1,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.1,
color: Cesium.Color.YELLOW
}),
// leadTime、trailTime 不设置 path全显示
// leadTime:0,// 设置为0时 模型通过后显示path
trailTime: 0,// 设置为0时 模型通过后隐藏path
width: 10
}
});
createProperty
/**
* 计算飞行路径
* @param source 数据坐标
* @returns {SampledPositionProperty|*}
*/
function createProperty(source) {
// 取样位置 相当于一个集合
let property = new Cesium.SampledPositionProperty();
for (let i = 0; i < source.length; i++) {
let time = Cesium.JulianDate.addSeconds(start, source[i].time, new Cesium.JulianDate);
let position = Cesium.Cartesian3.fromDegrees(source[i].longitude, source[i].latitude, source[i].height);
// 添加位置,和时间对应
property.addSample(time, position);
}
return property;
}
五、实现视角处理的timeline的监听方法(设置方向、俯仰和横摇属性存在偏差未解决)
原理:
1、根据clock.onTick事件获取timeline变化时的JulianDate
2、通过entity的position属性getValue方法传入此刻的JulianDate获取世界坐标(Cartesian3)
3、根据viewer.scene.camera.setView方法设置跟随视角
4、根据clock.onStop事件监听轨迹完成,可以处理轨迹完成后的逻辑代码
代码与注解
viewer.clock.onTick.addEventListener((tick) => {
// let q = viewer.entities.getById('move').orientation.getValue(tick.currentTime)
// 根据四元素获取方位角heading ,pitch, roll 设置仰角等
// 此方法设置方向、俯仰和横摇属性存在偏差未解决
// if (q == undefined) return
// let hpr = Cesium.HeadingPitchRoll.fromQuaternion(q)
// let heading = Cesium.Math.toDegrees(hpr.heading);
// let pitch = Cesium.Math.toDegrees(hpr.pitch);
// let roll = Cesium.Math.toDegrees(hpr.roll);
//世界坐标转换为经纬度
let position = viewer.entities.getById('move').position.getValue(tick.currentTime)
let ellipsoid = viewer.scene.globe.ellipsoid
let cartographic = ellipsoid.cartesianToCartographic(position);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
let lng = Cesium.Math.toDegrees(cartographic.longitude);
let alt = cartographic.height;
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(lng, lat, 100000),
// orientation: {
// heading,
// pitch,
// roll
// }
})
})
viewer.clock.onStop.addEventListener((tick) => {
console.log(tick, 'stop')
// 动画执行到结束时间时调用
// 逻辑代码 removeEventListener => onTick
})
创作不易,如对你有所帮助,点赞评论加收藏,三扣…
五、完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cesiumDemoflyMove</title>
<script src="./Cesium/Cesium.js"></script>
<style>
@import url(Cesium/Widgets/widgets.css);
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
</body>
<script>
// 1.cesium 根据SampledPositionProperty与timeline实现轨迹回放功能 详细代码与理解
// 2.cesium 世界坐标与经纬度转换,四元素与方位角转换
Cesium.Ion.defaultAccessToken = 'token'
var viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false,
timeline: true,// 必须为true显示时间线组件(如不想显示可以使用样式层叠表修改display:none) 否则viewer.timeline.zoomTo会报undefined错误
homeButton: false,
fullscreenButton: false,
infoBox: false,
sceneModePicker: false,
navigationInstructionsInitiallyVisible: false,
navigationHelpButton: false,
animation: false,
shouldAnimate: true // 必须为true开启动画 否则不会达到飞机模型飞行动画效果
});
// viewer.animation.container.style.display = 'none';//隐藏动画控件
// viewer.timeline.container.style.display = 'none';//隐藏时间线控件
// viewer.geocoder.container.style.display = 'none';//隐藏地名查找控件
// viewer.cesiumWidget.creditContainer.style.display = 'none';//隐藏ceisum标识
viewer.scene.globe.enableLighting = true;
let data = [{ longitude: 116.405419, latitude: 39.918034, height: 0, time: 0 },
{ longitude: 120.2821, latitude: 33.918145, height: 0, time: 30 },
{ longitude: 115.497402, latitude: 39.344641, height: 70000, time: 60 },
{ longitude: 107.942392, latitude: 29.559967, height: 70000, time: 110 },
{ longitude: 106.549265, latitude: 29.559967, height: 0, time: 120 }];
// 起始时间
let start = Cesium.JulianDate.fromDate(new Date());
// 结束时间
let stop = Cesium.JulianDate.addSeconds(start, 120, new Cesium.JulianDate());
// 设置始时钟始时间
viewer.clock.startTime = start.clone();
// 设置时钟当前时间
viewer.clock.currentTime = start.clone();
// 设置始终停止时间
viewer.clock.stopTime = stop.clone();
// 时间速率,数字越大时间过的越快
viewer.clock.multiplier = 1;
// 时间轴
viewer.timeline.zoomTo(start, stop);
// 循环执行,即为2,到达终止时间,重新从起点时间开始
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
// 摄像机聚焦到开始位置
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.405419, 39.918034, 100000)
})
let property = createProperty(data);;
// 添加entity实体
let planeModel = viewer.entities.add({
// 和时间轴关联
availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
start: start,
stop: stop
})]),
id: 'move',
position: property,
// 根据所提供的位置计算模型的朝向
orientation: new Cesium.VelocityOrientationProperty(property),
// 模型
model: {
uri: './Apps/SampleData/models/CesiumAir/Cesium_Air.glb',
minimumPixelSize: 128
},
path: {
resolution: 1,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.1,
color: Cesium.Color.YELLOW
}),
// leadTime、trailTime 不设置 path全显示
// leadTime:0,// 设置为0时 模型通过后显示path
trailTime: 0,// 设置为0时 模型通过后隐藏path
width: 10
}
});
viewer.clock.onTick.addEventListener((tick) => {
// let q = viewer.entities.getById('move').orientation.getValue(tick.currentTime)
// 根据四元素获取方位角heading ,pitch, roll 设置仰角等
// if (q == undefined) return
// let hpr = Cesium.HeadingPitchRoll.fromQuaternion(q)
// let heading = Cesium.Math.toDegrees(hpr.heading);
// let pitch = Cesium.Math.toDegrees(hpr.pitch);
// let roll = Cesium.Math.toDegrees(hpr.roll);
let position = viewer.entities.getById('move').position.getValue(tick.currentTime)
//世界坐标转换为经纬度
let ellipsoid = viewer.scene.globe.ellipsoid
let cartographic = ellipsoid.cartesianToCartographic(position);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
let lng = Cesium.Math.toDegrees(cartographic.longitude);
let alt = cartographic.height;
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(lng, lat, 100000),
// orientation: {
// heading,
// pitch,
// roll
// }
})
})
viewer.clock.onStop.addEventListener((tick) => {
console.log(tick, 'stop')
// 动画执行到结束时间时调用
// 逻辑代码 removeEventListener => onTick
})
/**
* 计算飞行路径
* @param source 数据坐标
* @returns {SampledPositionProperty|*}
*/
function createProperty(source) {
// 取样位置 相当于一个集合
let property = new Cesium.SampledPositionProperty();
for (let i = 0; i < source.length; i++) {
let time = Cesium.JulianDate.addSeconds(start, source[i].time, new Cesium.JulianDate);
let position = Cesium.Cartesian3.fromDegrees(source[i].longitude, source[i].latitude, source[i].height);
// 添加位置,和时间对应
property.addSample(time, position);
}
return property;
}
</script>
</html>