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

前言

人生一迹,谨以此记录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>
相关推荐
z千鑫8 分钟前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
m0_7482561440 分钟前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
小白学前端6662 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203982 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
outstanding木槿2 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08213 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
隐形喷火龙3 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241123 小时前
Selenium之Web元素定位
前端·selenium·测试工具
风无雨3 小时前
react杂乱笔记(一)
前端·笔记·react.js
前端小魔女3 小时前
2024-我赚到自媒体第一桶金
前端·rust