《我的飞机绕着宝岛飞》第四章(飞机圆锥形雷达侦查的原理)

前言

人生一迹,谨以此记录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>
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax