Cesium保姆教程之镜头漫游效果(二)

673 阅读4分钟

上次写的一篇基于czml实现的镜头漫游效果,写完之后发现留下了很多坑,并且由于这段时间工作比较忙,没有及时对上一篇留下的坑进行填坑,这次就要进行填坑了,当然也由于本人的能力有限,只能尽可能的进行填坑,对于文章有任何不对的地方请在评论区指出来。 这次是对cesium中的镜头漫游效果进行封装,形成一个实现类,可进行复用。

封装的漫游类如下:

镜头漫游类

import * as Cesium from "cesium";
export class Roaming {
  /**
     * Creates an instance of Roaming.
     * @param {*} viewer 三维地球
     * @param {*} options.model 模型
     * @param {*} options.time 漫游所需要的时间
     * @param {*} options.data  漫游经过的坐标集合
     * @param {*} options.isPathShow 路径是否显示 默认不显示
     * @memberof Roaming
    */
   constructor (viewer, options) {
     this.viewer = viewer;
     this.entity = undefined;
     this.start = undefined;
     this.stop = undefined;
     this.updateOptionsParams(options)
     this.init(options)
   }

   async init(options) {
     //移除漫游
     this.removeRoaming()
     //根据新的配置更新内部参数
     this.updateOptionsParams(options)
     //获取数据
     const result = await this.loadData(this.data)
     if (result) {
        //路径数据
        const data = result.path
        //创建漫游
        this.createRoaming(data)
     } else {
       this.createRoaming(result)
     }
   }

   /**
    * 更新漫游可配置的内部参数
    * @params {object} options 漫游的配置项
    */
   updateOptionsParams(options) {
     this.viewer = options.viewer || {};
     this.model = options.model || {};
     this.time = options.time;
     this.data = options.data;
     this.multiplier = options.speed || 10;
     this.isPathShow = Cesium.defined(options.isPathShow) ? options.isPathShow : true;
   }

   /**
    * 创建漫游
    * @memberof Roaming
    */
   createRoaming(data) {
     if (data) {
       //加载路径数据
       const positions = this.processData(data);
       //根据路径数据生成漫游路线
       this.property = this.roamingLine(positions, this.time)
       this.createEntity(positions, this.start, this.stop, this.isPathShow);
     }
   }

   /**
    * 读取数据
    * 后端读取或者前端读取
    */
   loadData(url) {
     let dataUrl;
     if (Cesium.defined(url) && typeof url === "string") {
       dataUrl = Cesium.Resource.fetchJson(url)
     } else {
      dataUrl = url
     }
     return dataUrl;
   }

   /**
    * 处理数据
    * 源数据这里采用的是{longitude:,latitude:,height:}
    * 转换成[longitude, latitude, height]
    */
   processData(data) {
     const coordinates = [];    
     data.forEach(position => {
      const car3Position = Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, position.height)
      coordinates.push(car3Position)
     })
     return positions
   }

   /**
    * 创建位置集合,将时间和位置绑定
    */
   roamingLine(coordinates, time) {
     const property = new Cesium.SampledPositionProperty()
     const coordinatesLength = coordinates.lenghth;

     const tempTime = time - (time % coordinatesLength);
     //间隔时间
     const increment = tempTime / coordinatesLength;
     const start = Cesium.JulianDate.now()
     const stop = Cesium.JulianDate.addSeconds(start, tempTime, new Cesium.JulianDate())
     this.start = start;
     this.stop = stop
     this.setClockTime(start, stop, this.multiplier)
     for (let i = 0; i < coordinatesLength.lenghth; i++) {
       const timePerPosition = Cesium.JulianDate.addSeconds(start, i * increment, new Cesium.JulianDate())
       const position = coordinates[i]
       property.addSample(timePerPosition, position)
     }
     return property
   }

   /**
    * 设置漫游事件系统
    */
   setClockTime(start, stop, multiplier) {
     //将当前时间转为JulianDate
     this.viewer.clock.startTime = start.clone()
     this.viewer.clock.stop = stop.clone()
     this.viewer.clock.currentTime = start.clone()
     this.viewer.clock.multiplier = multiplier;
     //默认漫游结束后停止
     this.viewer.clock.clockStep = Cesium.ClockRange.LOOP_STOP
     // 时钟在此模式下前进的间隔当前间隔乘以某个系数
    this.viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER;
   }

   /**
    * 创建entity
    * @param {*} position roamingLine计算的属性
    * @param {*} start 开始时间节点
    * @param {*} stop 结束时间节点
    * @param {*} isPathShow path路径是否显示
    * @memberof Roaming 
    */
   createEntity(position, start, stop, isPathShow) {
     this.entity = this.viewer.entities.add({
       availability: new Cesium.TimeIntervalCollection([new Cesium.TimeInterval({
         start, stop
       })]),
       //位置
       position: position,
       //计算朝向
       orientation: new Cesium.VelocityOrientationProperty(position),
       //加载模型
       model: {
        uri: this.model,
        // 模型最小刻度
        minimumPixelSize: 64,
        maximumSize: 128,
        // 设置模型最大放大大小
        maximumScale: 200,
        // 模型是否可见
        show: false,
        // 模型轮廓颜色
        silhouetteColor: Cesium.Color.WHITE,
        // 模型颜色  ,这里可以设置颜色的变化
        // color: color,
        // 仅用于调试,显示模型绘制时的线框
        debugWireframe: false,
        // 仅用于调试。显示模型绘制时的边界球。
        debugShowBoundingVolume: false,
        scale: 20,
        runAnimations: false, // 是否运行模型中的动画效果
       },
       path: {
        resolution: 1,
        material: new Cesium.PolylineGlowMaterialProperty({
          glowPower: 0.1,
          color: Cesium.Color.YELLOW,
        }),
        width: 10,
        show: isPathShow,
      },
     })
     this.entity.position.setInterpolationOptions({ // 点插值
      interpolationDegree: 5,
      interpolationAlgorithm: Cesium.LagrangePolynomialApproximation,
    });
    this.addSceneEvent(time => {
      this.getRoamingPosition(time)
    })
   }

   /**
    * @description 设置路劲是否可见
    * @param {boolean} visible
    * @memberof Roaming
    */
   setRoamingPathVisibility(visible) {
    if (this.entities) {
      this.entity.path.show = visible
    }
    //更新全局漫游路径是否可见参数
    this.isPathShow = visible
   }

   /**
    * @description 设置漫游模型是否可见
    * @param {boolean} visible
    * @memberof Roaming
    */
   setRoamingModelVisibility(visible) {
    if (this.entities) {
      this.entity.model.show = visible
    }
   }

   /**
    * @description 设置相机位置
    * @param {cartesian3} position
    * @param {object} options
    * @memberof Roaming
    */
   setCameraPosition(position, options) {
     if (position) {
       //最新传进来的坐标(后一个位置)
       this.position2 = this.cartesian3ToWGS84(position)
       let heading = 0;
       //前一个位置点位
       if (this.position1) {
         //计算前一个点位与第二个点位的偏航角
         heading = this.bearing(this.position1.latitude, this.position1.longitude, this.position2.latitude, this.position2.longitude)
       }
       this.position1 = this.cartesian3ToWGS84(position)
       if (position) {
         const dynamicHeading = Cesium.Math.toRadians(heading)
         const pitch = Cesium.Math.toRadians(options.pitch || -20.0)
         const range = options.range || 2000.0
         this.viewer.camera.lookAt(position, new Cesium.HeadingPitchRange(dynamicHeading, pitch, range))
       }
     }
   }

   /**
    * @name bearing 计算两点的角度heading
    * @param startLat 初始点的latitude
    * @param startLng 初始点的longitude
    * @param destLat 第二个点的latitude
    * @param destLng 第二个点的longitude
    * @return {number} heading值
    */
   bearing(startLat, startLng, destLat, destLng) {
     startLat = this.toRadians(startLat)
     startLng = this.toRadians(startLng)
     destLat = this.toRadians(destLat)
     destLng = this.toRadians(destLng)
     const y = Math.sin(destLng - startLat) * Math.cos(destLat)
     const x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng)
     const brng = Math.atan2(y, x)
     const brngDgr = this.toDegrees(brng)
     return (brngDgr + 360) % 360
   }

   /**
    * @description  坐标转换
    * @param point radius下的WGS84坐标
    * @return degrees下的WGS84坐标
    */
   cartesian3ToWGS84(point) {
     const cartographic = Cesium.Cartographic.fromCartesian(point)
     const lat = Cesium.Math.toDegrees(cartographic.latitude)
     const lng = Cesium.Math.toDegrees(cartographic.longitude)
     const alt = cartographic.height
     return {
       longitude: lng,
       latitude: lat,
       height: alt
     }
   }

   /**
    * @description 监听场景事件
    * @param callback
    */
   addSceneEvent(callback) {
     //addEventListner() -> Event.RemoveCallback
     //监听之前先销毁 不销毁上一次的话会导致最后的镜头无法跳转
     if (this.handler instanceof Function) {
       this.handler()
       this.handler = null
     }
     this.handler = this.viewer.scene.preRender.addEventListener((time) => {
      callback(time)
     })
   }

   /**
    * @description 根据时刻获取漫游位置
    * @param {object} time
    * @memberof Roam
    */
   getRoamingPosition(time) {
     if (this.entity) {
       const position = this.entity.position.getValue(time)
       this.setCameraPosition(position, this.viewer || {})
     }
   }

   /**
    * @description 漫游的暂停和继续
    * @param {boolean} state false为暂停 true为继续
    */
   pauseOrContinue(state) {
     if (state) {
       //继续播放
       if (!this.handler && this.entity) {
         this.addSceneEvent((time) => {
           this.getRoamingPosition(time)
         })
       }
     } else if (this.handler) {
       //停止监听屏幕绘制事件(停止相机变化)
       this.handler()
       this.handler = null
       //解锁相机视角
       this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
     }
     this.viewer.clock.shouldAnimate = state
   }

   /**
    * 改变飞行速度
    * @param {number} value 整数类型
    */
   changeRoamingSpeed(value) {
     this.viewer.clock.multiplier = value
   }

   /**
    * 移除漫游
    */
   removeRoaming() {
     if (this.entity !== undef) {
       if (this.handler instanceof Function) {
         this.handler()
         this.handler = null
       }
       //清除实体
       this.viewer.entities.remove(this.entity)
       //清除内部数据
       this.data = null
       //解锁相机视角
       this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
       this.entity = null
     }
   }
}

如需实现某些功能,只需自己通过调用构造函数的方式进行调用相关方法即可。