前言
人生一迹,谨以此记录Cesium相关系列知识
问题背景:飞机绕着宝岛飞的过程中,需要用飞机雷达去探查飞机下方情况!
书接上回,上回咱们说到:高空中,我驾驶着飞机,躲避着雷达的搜索,进行显隐变换。当飞机完成了变化,展露出其隐蔽的身影时,机上的操作员已经开始忙碌起来。飞机底部缓缓展开了一个精密的装置,那是一个高度先进的圆锥形雷达系统。它开始自上而下发射着细腻的雷达波,这些波以特定频率穿透了夜色,像是织网的蜘蛛,默默地布下了一张大网。
"目标区域接近,全神贯注!" 队长的声音在耳机中清晰传来。所有人的眼睛都紧盯着屏幕,心跳随着雷达扫描的节奏而加速。突然间,一个异常的信号在屏幕的某个角落跳动,它与周围的模式不同,显得格外突出。
"有异常情况,放大并分析这一区域。" 操作员的手指迅速敲打键盘,调整参数进行重点探查。屏幕上那个异常光点开始变得更加清晰,一个隐藏的地下结构逐渐显露了出来,它的存在仿佛正等待着他们的发现。
问题复现:Cesium飞机动态圆锥形光柱扫描?
(重点思路:在于如何实现从圆锥上面到下面的动态扫描?)
一、更换Cesium底图
将cesium原生BingMap更换为高德地图,避免部分用户加载不出来地球的现象。
arduino
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); //添加地图贴图
二、定位初始场景
目的是将地球定位到自己需要的区域。
css
// 场景定位
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
}
});
三、加载动态飞机Entity模型
此处分为两个步骤,第一步进行动态数据的整理与拟合,第二步进行加载模型。
3.1 动态数据整理与拟合
此处主要运用了Cesium中的SampledPositionProperty类,该类的目的是给定不同时间的位置信息,返回线性插值后平滑的位置数据,优点在于位置轨迹线性变化,不突兀,丝滑具有物理世界的真实感。
ini
// 定义一个数组,存储物体运动的时间和位置点
let positionData = [
{ time: 0, longitude: 120.49155063, latitude: 25.47218132,height:80000 },
{ time: 25, longitude: 119.7230824, latitude:23.027839,height:80000 },
];
let positionSampler = new Cesium.SampledPositionProperty();
positionSampler.setInterpolationOptions({
interpolationDegree: 2,
interpolationAlgorithm: Cesium.HermitePolynomialApproximation
});
let startTime = new Cesium.JulianDate.now()
for (let i = 0; i < positionData.length; i++) {
let data = positionData[i];
let time = new Cesium.JulianDate.addSeconds(startTime, data.time, new Cesium.JulianDate());
let position = new Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude,data.height);
positionSampler.addSample(time, position);
}
3.2 加载gltf格式的Entity模型
此处注意调整scale(模型比例)参数的设置,避免模型看不到的现象,最后记得跳转到模型本身。
less
let model_entity = viewer.entities.add({
name: "model_entity",
position: positionSampler,
orientation : new Cesium.VelocityOrientationProperty(positionSampler),
// 设置朝向
model: {
show: true,
uri: "Data/airPlane_cesium.gltf",
scale: 4000, // 缩放比例
minimumPixelSize: 128, // 最小像素大小
maximumScale: 20000, // 模型的最大比例尺大小。minimumPixelSize的上限
incrementallyLoadTextures: true, // 加载模型后纹理是否可以继续流入
runAnimations: true, // 是否应启动模型中指定的glTF动画
clampAnimations: true, // 指定glTF动画是否应在没有关键帧的持续时间内保持最后一个姿势
shadows: Cesium.ShadowMode.ENABLED,
heightReference: Cesium.HeightReference.NONE,
color:Cesium.Color.WHITE.withAlpha(1)
},
});
viewer.zoomTo(model_entity); //定位到模型
四、创建跟随飞机模型移动的圆锥体
首先圆锥体的类型是"cylinder",参数的含义分别为length(圆锥体的高度)、topRadius(圆锥体顶部半径)、bottomRadius(圆锥体底部半径)、material(圆锥体颜色)。设置圆锥体position的时候,需要注意两点:1、利用Cesium的CallbackProerty属性去动态设置position,2、设置圆锥体height的时候,要设置为飞机模型高度的一半,因为圆锥体的position在圆锥体的中心位置。
javascript
// 飞机扫描轴体
let cylinder_model = viewer.entities.add({
name:'cylinder_entity',
cylinder: {
length: 80000, //圆锥高度
topRadius: 0.0, //顶部半径
bottomRadius: 20000.0, //底部半径
material: Cesium.Color.fromCssColorString(`rgba(255,0,0,0.5)`), //圆的颜色,
},
position: new Cesium.CallbackProperty((time)=>{
let pos = model_entity.position.getValue(time)
if(!pos) return
let cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos);
let lon = Cesium.Math.toDegrees(cartographic.longitude);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
return Cesium.Cartesian3.fromDegrees(lon,lat,40000)
},false)
})
五、创建环圆锥体扫描高亮圆
此处分为两个步骤讲解,第一步:绘制圆形平面,第二步是让圆半径和高度动态改变
5.1 绘制圆形面
注意cesium中无直接绘制圆形,是通过椭圆进行绘制的,entity的类型为ellipse,部分参数解释:semiMinorAxis(短半轴)、semiMajorAxis(长半轴)、height为高度。
javascript
let circle_height = 0
let circle_radius = 0
let circle_model = viewer.entities.add({
name:'circle_entity',
position: new Cesium.CallbackProperty((time)=>{
let pos = model_entity.position.getValue(time)
if(!pos) return
let cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos);
let lon = Cesium.Math.toDegrees(cartographic.longitude);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
return Cesium.Cartesian3.fromDegrees(lon,lat,80000)
},false),
ellipse: {
semiMinorAxis: new Cesium.CallbackProperty(()=>{
return circle_radius
},false), // 短轴
semiMajorAxis:new Cesium.CallbackProperty(()=>{
return circle_radius
},false), // 长轴
material: Cesium.Color.RED.withAlpha(1),
outline: true,
outlineColor: Cesium.Color.RED,
outlineWidth: 10,
height: new Cesium.CallbackProperty(()=>{
return circle_height
},false),
}
})
5.2 动态改变圆半径和高度
此处利用定时去动态改变圆的半径和高度,需要注意的是,此处采用cesium中math.lerp函数对中间值进行插值,该函数的作用是:通过输入参数(a,b,比例)去获取a,b之间对应比例的值。因此圆的半径应该由小到大(0-20000),圆的高度应该由高到低(80000-0)。
ini
// 每100ms更新圆的大小与高度,3000ms为一循环。
let i = 0
let timeInterval = setInterval(()=>{
if(i>3000){
i = 0
}else{
i +=100
}
circle_height = Cesium.Math.lerp(80000,0,i/3000)
circle_radius = Cesium.Math.lerp(0,20000,i/3000)
},100)
六、时间轴相关参数
此处主要设置cesium时间轴的起始时间、终止时间等相关参数,需要注意的是记得最后销毁定时器。
ini
let stopTime = Cesium.JulianDate.addSeconds(startTime, 30, new Cesium.JulianDate());
viewer.clock.startTime = startTime.clone();
viewer.clock.stopTime = stopTime.clone();
viewer.clock.currentTime = startTime.clone();
viewer.clock.shouldAnimate = true;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
viewer.clock.onStop(()=>{clearInterval(timeInterval)})
七、全部代码
xml
<!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
}
});
// 定义一个数组,存储物体运动的时间和位置点
let positionData = [
{ time: 0, longitude: 120.49155063, latitude: 25.47218132,height:80000 },
{ time: 25, longitude: 119.7230824, latitude:23.027839,height:80000 },
];
let positionSampler = new Cesium.SampledPositionProperty();
positionSampler.setInterpolationOptions({
interpolationDegree: 2,
interpolationAlgorithm: Cesium.HermitePolynomialApproximation
});
let startTime = new Cesium.JulianDate.now()
for (let i = 0; i < positionData.length; i++) {
let data = positionData[i];
let time = new Cesium.JulianDate.addSeconds(startTime, data.time, new Cesium.JulianDate());
let position = new Cesium.Cartesian3.fromDegrees(data.longitude, data.latitude,data.height);
positionSampler.addSample(time, position);
}
let model_entity = viewer.entities.add({
name: "model_entity",
position: positionSampler,
orientation : new Cesium.VelocityOrientationProperty(positionSampler),
// 设置朝向
model: {
show: true,
uri: "Data/airPlane_cesium.gltf",
scale: 4000, // 缩放比例
minimumPixelSize: 128, // 最小像素大小
maximumScale: 20000, // 模型的最大比例尺大小。minimumPixelSize的上限
incrementallyLoadTextures: true, // 加载模型后纹理是否可以继续流入
runAnimations: true, // 是否应启动模型中指定的glTF动画
clampAnimations: true, // 指定glTF动画是否应在没有关键帧的持续时间内保持最后一个姿势
shadows: Cesium.ShadowMode.ENABLED,
heightReference: Cesium.HeightReference.NONE,
color:Cesium.Color.WHITE.withAlpha(1)
},
});
viewer.zoomTo(model_entity); //定位到模型
// 飞机扫描轴体
let cylinder_model = viewer.entities.add({
name:'cylinder_entity',
cylinder: {
length: 80000, //圆锥高度
topRadius: 0.0, //顶部半径
bottomRadius: 20000.0, //底部半径
material: Cesium.Color.fromCssColorString(`rgba(255,0,0,0.5)`), //圆的颜色,
},
position: new Cesium.CallbackProperty((time)=>{
let pos = model_entity.position.getValue(time)
if(!pos) return
let cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos);
let lon = Cesium.Math.toDegrees(cartographic.longitude);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
return Cesium.Cartesian3.fromDegrees(lon,lat,40000)
},false)
})
// 圆形高亮
let circle_height = 0
let circle_radius = 0
let circle_model = viewer.entities.add({
name:'circle_entity',
position: new Cesium.CallbackProperty((time)=>{
let pos = model_entity.position.getValue(time)
if(!pos) return
let cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos);
let lon = Cesium.Math.toDegrees(cartographic.longitude);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
return Cesium.Cartesian3.fromDegrees(lon,lat,80000)
},false),
ellipse: {
semiMinorAxis: new Cesium.CallbackProperty(()=>{
return circle_radius
},false), // 短轴
semiMajorAxis:new Cesium.CallbackProperty(()=>{
return circle_radius
},false), // 长轴
material: Cesium.Color.RED.withAlpha(1),
outline: true,
outlineColor: Cesium.Color.RED,
outlineWidth: 10,
height: new Cesium.CallbackProperty(()=>{
return circle_height
},false),
}
})
// 每100ms更新圆的大小与高度
let i = 0
let timeInterval = setInterval(()=>{
if(i>3000){
i = 0
}else{
i +=100
}
circle_height = Cesium.Math.lerp(80000,0,i/3000)
circle_radius = Cesium.Math.lerp(0,20000,i/3000)
},100)
// 设置时间轴相关
let stopTime = Cesium.JulianDate.addSeconds(startTime, 30, new Cesium.JulianDate());
viewer.clock.startTime = startTime.clone();
viewer.clock.stopTime = stopTime.clone();
viewer.clock.currentTime = startTime.clone();
viewer.clock.shouldAnimate = true;
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
viewer.clock.multiplier = 1;
viewer.clock.onStop(()=>{clearInterval(timeInterval)})
</script>
</body>
</html>