前言
人生一迹,谨以此记录Cesium相关系列知识
问题背景:飞机绕着宝岛飞的过程中,警报提示没油了,需要对接“小红加油机”!
书接上回,上回咱们说到:飞机绕着宝岛飞,通过与地面卫星接收器发出波段连线,告知我的飞机出现了警告。在接到地面控制塔的警告后,我意识到需要尽快处理左侧引擎的异常情况。然而就在此刻,燃油指示灯也开始闪烁,表明剩余的燃油已经无法支撑我返回机场。无奈之下,我请求进行空中加油,这是一次高风险但又必不可少的操作。
幸好,一架专门执行空中加油任务的“小红加油机”恰好在我的航线附近执行任务。我听到地面控制人员焦急但平静的声音:“立即调整航向,准备与加油机对接。”
心跳如鼓在胸腔内敲击,我握紧了操纵杆,将飞机微调至预定位置,目光紧紧锁定着逐渐靠近的加油机。两架飞机在万米高空中,如同两只雄鹰在宝岛上空的云端展开了一场精确的舞蹈。
随着距离的拉近,我能清晰地看到加油机尾部伸出的加油管道,它就像一只等待对接的红色金属脉络。我的飞机缓缓靠近,每一秒都显得异常漫长。终于,在通讯系统中传来一个简洁的“连接成功”,我深吸一口气,放松了紧绷的神经。
现在,我只需保持飞机的稳定,直到加油完毕。空中加油不仅考验着飞行员的技术,更是对心理素质的极大挑战。此刻,我和宝岛之上的天空,共同见证了这惊心动魄的一幕。
一、Cesium加载高德底图
二、场景定位
三、轨迹数据结构
照例由于书接上回,因此部分内容就不作重复叙述了
四、动态模型A与动态模型B(飞机与飞机模拟加油)
4.1 加载动态飞机模型(此处重点在于加载两个动态飞机模型)
具体细节描述,请参考上篇文章。
// 创建一个动态飞机实体对象A
var air1_entity = viewer.entities.add({
name: 'air1_model',
model: {
uri: "Data/airPlane_cesium.gltf",
show: true,
scale: 2000,
}
});
// 创建一个动态飞机实体对象B
var air2_entity = viewer.entities.add({
name: 'air2_model',
model: {
uri: "Data/airPlane_cesium.gltf",
show: true,
scale: 2000,
}
});
此处,先设置好两个飞机的轨迹点位置和时间数组
// 定义一个数组,存储物体运动的时间和位置点
var positionData1 = [
{ time: 0, longitude: 120.49155063, latitude: 25.47218132 },
{ time: 5, longitude: 119.7230824, latitude:23.027839 },
];
var positionData2 = [
{ time: 0, longitude: 119.47517259, latitude: 25.70525443 },
{ time: 10, longitude: 119.60265734, latitude: 23.59670123 },
];
与上一篇文章不一样的是,由于本篇是两个动态飞机,需要两个采样器对象,因此我设置了一个函数用来获取采样器对象,输入的参数是轨迹点数组。
function getPositionSampler(params) {
// 创建一个采样器对象,用于插值计算物体运动的位置
let positionSampler = new Cesium.SampledPositionProperty();
positionSampler.setInterpolationOptions({
interpolationDegree: 2,
interpolationAlgorithm: Cesium.HermitePolynomialApproximation
});
for (let i = 0; i < params.length; i++) {
let data = params[i];
let time = new Cesium.JulianDate.addSeconds(start, data.time, new Cesium.JulianDate());
let position = new Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude);
positionSampler.addSample(time, position);
}
return positionSampler
}
设置初始时间和两个动态飞机模型的位置信息以及朝向信息
var start = Cesium.JulianDate.fromDate(new Date(2023, 2, 29));
air1_entity.position = getPositionSampler(positionData1);
air1_entity.orientation = new Cesium.VelocityOrientationProperty(getPositionSampler(positionData1)); //根据坐标转头
air2_entity.position = getPositionSampler(positionData2);
air2_entity.orientation = new Cesium.VelocityOrientationProperty(getPositionSampler(positionData2)); //根据坐标转头
4.2 设置加油管线(重点!)
这个步骤的重点是在于CallbackProperty属性的使用,该属性是用来返回某些特定的值,可获取到entity某时刻的position值,因此只需要将两个飞机的动态position值赋值给管线即可。当然本文讲如何去设置两端都在动态变化的管线,大家可以根据自己的需求去更改类型,如:polylineVolume管线、wall墙、polyline线等。绘制类型的代码,请参考(cesium官方提供的案例)[sandcastle.cesium.com/?src=Geomet…]
// 创建一个连接线对象
var line_entity = viewer.entities.add({
name: 'line',
polylineVolume: {
positions: new Cesium.CallbackProperty( (time) =>{
var startpos = air1_entity.position.getValue(time);
if(!startpos) return
var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(startpos);
var lon1 = Cesium.Math.toDegrees(cartographic.longitude);
var lat1 = Cesium.Math.toDegrees(cartographic.latitude);
var endpos = air2_entity.position.getValue(time);
if(!endpos) return
var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(endpos);
var lon2 = Cesium.Math.toDegrees(cartographic.longitude);
var lat2 = Cesium.Math.toDegrees(cartographic.latitude);
return Cesium.Cartesian3.fromDegreesArray([lon1,lat1,lon2,lat2])
}, false),
shape: computeCircle(2000.0),
material: Cesium.Color.RED,
}
})
// 计算轨迹管道函数
function computeCircle(radius) {
const positions = [];
for (let i = 0; i < 360; i++) {
const radians = Cesium.Math.toRadians(i);
positions.push(
new Cesium.Cartesian2(
radius * Math.cos(radians),
radius * Math.sin(radians)
)
);
}
return positions;
}
4.3 设置时间
此步骤主要是定义Cesium的时间轴相关参数,包含起始时间、终止时间和当前时间
var stop = Cesium.JulianDate.addSeconds(start, 30, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.shouldAnimate = true;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
五、全部代码及效果图
<!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>我的飞机绕着宝岛飞</title>
<!-- <link rel="stylesheet" href="./CesiumUnminified/Widgets/widgets.css">
<script type="text/javascript" src="./CesiumUnminified/Cesium.js"></script> -->
<link rel="stylesheet" href="./Cesium/Widgets/widgets.css">
<script type="text/javascript" src="./Cesium/Cesium.js"></script>
</head>
<body>
<div id="cesiumContainer"></div>
<script type="text/javascript">
let viewer = new Cesium.Viewer('cesiumContainer');
// 更换底图
let imageryLayers = viewer.imageryLayers;
let map = new Cesium.UrlTemplateImageryProvider({
url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}", //高德地图
minimumLevel: 3,
maximumLevel: 16,
});
imageryLayers.addImageryProvider(map); //添加地图贴图
// 场景定位
viewer.camera.flyTo({
destination : Cesium.Cartesian3.fromDegrees(121.195,21.813,738947.02),
orientation :{
heading : Cesium.Math.toRadians(355.1),
pitch : Cesium.Math.toRadians(-75.3),
roll :0.0
}
});
// 创建一个动态飞机实体对象A
var air1_entity = viewer.entities.add({
name: 'air1_model',
model: {
uri: "Data/airPlane_cesium.gltf",
show: true,
scale: 2000,
},
});
// 创建一个动态飞机实体对象B
var air2_entity = viewer.entities.add({
name: 'air2_model',
model: {
uri: "Data/airPlane2.glb",
show: true,
scale: 10000,
},
});
// 创建一个连接线对象
var line_entity = viewer.entities.add({
name: 'line',
polylineVolume: {
positions: new Cesium.CallbackProperty( (time) =>{
var startpos = air1_entity.position.getValue(time);
if(!startpos) return
var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(startpos);
var lon1 = Cesium.Math.toDegrees(cartographic.longitude);
var lat1 = Cesium.Math.toDegrees(cartographic.latitude);
var endpos = air2_entity.position.getValue(time);
if(!endpos) return
var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(endpos);
var lon2 = Cesium.Math.toDegrees(cartographic.longitude);
var lat2 = Cesium.Math.toDegrees(cartographic.latitude);
return Cesium.Cartesian3.fromDegreesArray([lon1,lat1,lon2,lat2])
}, false),
shape: computeCircle(2000.0),
material: Cesium.Color.RED,
}
})
// 计算轨迹管道函数
function computeCircle(radius) {
const positions = [];
for (let i = 0; i < 360; i++) {
const radians = Cesium.Math.toRadians(i);
positions.push(
new Cesium.Cartesian2(
radius * Math.cos(radians),
radius * Math.sin(radians)
)
);
}
return positions;
}
// 定义一个数组,存储物体运动的时间和位置点
var positionData1 = [
{ time: 0, longitude: 120.49155063, latitude: 25.47218132 },
{ time: 5, longitude: 119.7230824, latitude:23.027839 },
];
var positionData2 = [
{ time: 0, longitude: 119.47517259, latitude: 25.70525443 },
{ time: 10, longitude: 119.60265734, latitude: 23.59670123 },
];
// 起始时间
var start = Cesium.JulianDate.fromDate(new Date(2023, 2, 29));
// 遍历数组,将时间和位置点添加到采样器对象中
function getPositionSampler(params) {
// 创建一个采样器对象,用于插值计算物体运动的位置
let positionSampler = new Cesium.SampledPositionProperty();
positionSampler.setInterpolationOptions({
interpolationDegree: 2,
interpolationAlgorithm: Cesium.HermitePolynomialApproximation
});
for (let i = 0; i < params.length; i++) {
let data = params[i];
let time = new Cesium.JulianDate.addSeconds(start, data.time, new Cesium.JulianDate());
let position = new Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude);
positionSampler.addSample(time, position);
}
return positionSampler
}
air1_entity.position = getPositionSampler(positionData1);
air1_entity.orientation = new Cesium.VelocityOrientationProperty(getPositionSampler(positionData1)); //根据坐标转头
air2_entity.position = getPositionSampler(positionData2);
air2_entity.orientation = new Cesium.VelocityOrientationProperty(getPositionSampler(positionData2)); //根据坐标转头
// 定义时钟参数,设置开始时间、结束时间和当前时间
var stop = Cesium.JulianDate.addSeconds(start, 5, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.shouldAnimate = true;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
</script>
</body>
</html>