Cesium 顺滑如丝般漫游,视角跟随(锁定视角)_cesium 限制浏览角度-程序员宅基地

技术标签: cesium  

/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
/* eslint-disable no-undef */
export class Roaming {
  /**
     * Creates an instance of Roaming.
     * @param {*} viewer 需要传入
     * @param {*} options.model 模型的相关配置 需要传入
     * @param {*} options.time 漫游时间  需要传入
     * @param {*} options.data  点集合 需要传入
     * @param {*} options.isPathShow 路径是否显示 默认显示
     * @memberof Roaming
     * @example
     *  const options = {
            data: '', // url 或者直接数据,格式和以前一样
            view: { pitch: '', range: '' }, // 默认不传
            model: {
                url: ''// 和cesium中model的配置一致
            },
            isPathShow: true, // 漫游路径是否显示
            speed: '', // 默认可不传
            time: 500// 此次漫游所需要的总时间,单位:秒
        };
     */
  constructor(viewer, options) {
    this.viewer = viewer;
    this.updateOptionsParams(options);
    this.entity = undefined;
    this.start = undefined;
    this.stop = undefined;
    this.isPlay = true; // 鼠标控制漫游是否播放的变量
    this.addRoamingHandler(); // 添加鼠标事件管理器
  }

  init(options) {
    this.removeRoaming();
    // 根据新的配置更新内部参数
    this.updateOptionsParams(options);
    const result = this.loadData(this.data);
    if (result && (typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') { // 判断是否为promise
      result.then((sections) => {
        // 路径数据
        const data = sections.path;
        this.createRoaming(data);
      });
    } else {
      this.createRoaming(result);
    }
  }

  /**
     * 更新漫游可配置的内部参数
     * @param {object} options 漫游的配置项
     */
  updateOptionsParams(options) {
    this.view = options.view;
    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;
    this.multiplier = options.speed || 10;
  }

  /**
     * 创建漫游
     * @param {array} data
     * @memberof Roaming
     */
  createRoaming(data) {
    if (data && Array.isArray(data)) {
      // 加载路径数据
      const positions = this.processData(data);
      // 根据基础路径生成漫游路线
      this.property = this.computeRoamingLineProperty(positions, this.time);
      this.createEntity(this.property, this.start, this.stop, this.isPathShow);
      // TODO 默认是否添加鼠标控制
      this.controlMouseEvent();
    }
  }

  /**
     * 载入数据
     * @param {string|array} url
     * @return {*}
     * @memberof Roam
     */
  loadData(url) {
    let data;
    if (Cesium.defined(url) && typeof url === 'string') { // 如果传入的是字符串,默认是json路径,加载json数据
      data = Cesium.Resource.fetchJson(url); // 不应该用其私有方法
    } else {
      data = url;
    }
    return data;
  }

  /**
     * 处理读取到的数据
     * @param {array} data
     */
  processData(data) {
    const positions = [];
    data.forEach((position) => {
      const car3Position = Cesium.Cartesian3.fromDegrees(position[0], position[1], position[2]); // 给定默认值
      positions.push(car3Position);
    });
    return positions;
  }

  /**
     * 创建位置集合
     * @param {cartesian3} coordinates 点集合
     * @param {*} time 漫游时间
     * @returns
     */
  computeRoamingLineProperty(coordinates, time) {
    const property = new Cesium.SampledPositionProperty();
    const coordinatesLength = coordinates.length;
    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; i += 1) {
      const time1 = Cesium.JulianDate.addSeconds(start, i * increment, new Cesium.JulianDate());
      const position = coordinates[i];
      property.addSample(time1, position);
    }
    return property;
  }

  /**
     * 设置漫游事件系统
     * @param {*} start
     * @param {*} stop
     * @param {*} multiplier
     * @memberof Roaming
     */
  setClockTime(start, stop, multiplier) {
    // 将当前日期转为JulianDate
    this.viewer.clock.startTime = start.clone();
    this.viewer.clock.stopTime = stop.clone();
    this.viewer.clock.currentTime = start.clone();
    this.viewer.clock.multiplier = multiplier;
    // 默认循环漫游
    this.viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
    // 时钟在此模式下前进的间隔当前间隔乘以某个系数
    this.viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER;
  }

  /**
     * 创建entity
     * @param {*} position computeRoamingLineProperty计算的属性
     * @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,
      // 计算朝向
      orientation: new Cesium.VelocityOrientationProperty(position),
      // 加载模型
      model: { // 模型路径
        uri: this.model.url,
        // 模型最小刻度
        minimumPixelSize: 64,
        maximumSize: 128,
        // 设置模型最大放大大小
        maximumScale: 200,
        // 模型是否可见
        show: true,
        // 模型轮廓颜色
        silhouetteColor: Cesium.Color.WHITE,
        // 模型颜色  ,这里可以设置颜色的变化
        // color: color,
        // 仅用于调试,显示模型绘制时的线框
        debugWireframe: false,
        // 仅用于调试。显示模型绘制时的边界球。
        debugShowBoundingVolume: false,
        scale: 20,
        runAnimations: true, // 是否运行模型中的动画效果
        ...this.model,
      },
      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);
    });
  }

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

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

  /**
     * 设置相机位置
     * @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 第二个点的latitude
   * @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 - startLng) * 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;
  }

  /**
   * cartographic 转Degrees下地理坐标
   * @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,
    };
  }

  /**
     * 监听场景渲染事件
     * @param callback
     */
  addSceneEvent(callback) {
    // addEventListener() → Event.RemoveCallback
    // 监听之前先销毁
    if (this.handler instanceof Function) {
      this.handler();
      this.handler = null;
    }
    this.handler = this.viewer.scene.preRender.addEventListener((scene, time) => {
      callback(time);
    });
  }

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

  /**
     * 添加屏幕事件管理器
     * @memberof Roaming
     */
  addRoamingHandler() {
    if (!this.roamingHandler) {
      this.roamingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
    }
  }

  /**
     * 监听鼠标事件
     */
  controlMouseEvent() {
    // 防止重复注册
    this.cancelMouseEvent();
    this.addRoamingHandler();
    // 左键单击停止;
    this.roamingHandler.setInputAction(() => {
      // 暂停漫游
      this.pauseOrContinue(!this.isPlay);
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    // 右键单击继续
    this.roamingHandler.setInputAction(() => {
      // 开启漫游
      this.pauseOrContinue(this.isPlay);
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  }

  /**
     * 取消鼠标事件
     */
  cancelMouseEvent() {
    // TODO 销毁后需要重新new 需要判断它是否存在
    if (this.roamingHandler && !this.roamingHandler.isDestroyed()) {
      this.roamingHandler.destroy();
      this.roamingHandler = null;
    }
  }

  /**
     * 漫游的暂停和继续
     * @param {boolean} state false为暂停,ture为继续
     */
  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 {*} value  整数类型
     */
  changeRoamingSpeed(value) {
    this.viewer.clock.multiplier = value;
  }

  /**
     * 移除漫游
     */
  removeRoaming() {
    if (this.entity !== undefined) {
      if (this.handler instanceof Function) {
        this.handler();
        this.handler = null;
      }
      // 清空实体
      this.viewer.entities.remove(this.entity);
      // 清空内部数据
      this.data = null;
      // 销毁鼠标事件
      this.cancelMouseEvent();
      // 解锁相机视角
      this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
      this.entity = null;
    }
  }
}

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u_ZJL_FFF/article/details/113632881

智能推荐

Docker 快速上手学习入门教程_docker菜鸟教程-程序员宅基地

文章浏览阅读2.5w次,点赞6次,收藏50次。官方解释是,docker 容器是机器上的沙盒进程,它与主机上的所有其他进程隔离。所以容器只是操作系统中被隔离开来的一个进程,所谓的容器化,其实也只是对操作系统进行欺骗的一种语法糖。_docker菜鸟教程

电脑技巧:Windows系统原版纯净软件必备的两个网站_msdn我告诉你-程序员宅基地

文章浏览阅读5.7k次,点赞3次,收藏14次。该如何避免的,今天小编给大家推荐两个下载Windows系统官方软件的资源网站,可以杜绝软件捆绑等行为。该站提供了丰富的Windows官方技术资源,比较重要的有MSDN技术资源文档库、官方工具和资源、应用程序、开发人员工具(Visual Studio 、SQLServer等等)、系统镜像、设计人员工具等。总的来说,这两个都是非常优秀的Windows系统镜像资源站,提供了丰富的Windows系统镜像资源,并且保证了资源的纯净和安全性,有需要的朋友可以去了解一下。这个非常实用的资源网站的创建者是国内的一个网友。_msdn我告诉你

vue2封装对话框el-dialog组件_<el-dialog 封装成组件 vue2-程序员宅基地

文章浏览阅读1.2k次。vue2封装对话框el-dialog组件_

MFC 文本框换行_c++ mfc同一框内输入二行怎么换行-程序员宅基地

文章浏览阅读4.7k次,点赞5次,收藏6次。MFC 文本框换行 标签: it mfc 文本框1.将Multiline属性设置为True2.换行是使用"\r\n" (宽字符串为L"\r\n")3.如果需要编辑并且按Enter键换行,还要将 Want Return 设置为 True4.如果需要垂直滚动条的话将Vertical Scroll属性设置为True,需要水平滚动条的话将Horizontal Scroll属性设_c++ mfc同一框内输入二行怎么换行

redis-desktop-manager无法连接redis-server的解决方法_redis-server doesn't support auth command or ismis-程序员宅基地

文章浏览阅读832次。检查Linux是否是否开启所需端口,默认为6379,若未打开,将其开启:以root用户执行iptables -I INPUT -p tcp --dport 6379 -j ACCEPT如果还是未能解决,修改redis.conf,修改主机地址:bind 192.168.85.**;然后使用该配置文件,重新启动Redis服务./redis-server redis.conf..._redis-server doesn't support auth command or ismisconfigured. try

实验四 数据选择器及其应用-程序员宅基地

文章浏览阅读4.9k次。济大数电实验报告_数据选择器及其应用

随便推点

灰色预测模型matlab_MATLAB实战|基于灰色预测河南省社会消费品零售总额预测-程序员宅基地

文章浏览阅读236次。1研究内容消费在生产中占据十分重要的地位,是生产的最终目的和动力,是保持省内经济稳定快速发展的核心要素。预测河南省社会消费品零售总额,是进行宏观经济调控和消费体制改变创新的基础,是河南省内人民对美好的全面和谐社会的追求的要求,保持河南省经济稳定和可持续发展具有重要意义。本文建立灰色预测模型,利用MATLAB软件,预测出2019年~2023年河南省社会消费品零售总额预测值分别为21881...._灰色预测模型用什么软件

log4qt-程序员宅基地

文章浏览阅读1.2k次。12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置Log4Qt七、在Qt工程中引入Log4Qt库模块的方法八、获取示例中的源代码一、为啥要使用第三方Log库,而不用平台自带的Log库首先要说明的是,在平时开发和调试中开发平台自带的“打印输出”已经足够了。但_log4qt

100种思维模型之全局观思维模型-67_计算机中对于全局观的-程序员宅基地

文章浏览阅读786次。全局观思维模型,一个教我们由点到线,由线到面,再由面到体,不断的放大格局去思考问题的思维模型。_计算机中对于全局观的

线程间控制之CountDownLatch和CyclicBarrier使用介绍_countdownluach于cyclicbarrier的用法-程序员宅基地

文章浏览阅读330次。一、CountDownLatch介绍CountDownLatch采用减法计算;是一个同步辅助工具类和CyclicBarrier类功能类似,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。二、CountDownLatch俩种应用场景: 场景一:所有线程在等待开始信号(startSignal.await()),主流程发出开始信号通知,既执行startSignal.countDown()方法后;所有线程才开始执行;每个线程执行完发出做完信号,既执行do..._countdownluach于cyclicbarrier的用法

自动化监控系统Prometheus&Grafana_-自动化监控系统prometheus&grafana实战-程序员宅基地

文章浏览阅读508次。Prometheus 算是一个全能型选手,原生支持容器监控,当然监控传统应用也不是吃干饭的,所以就是容器和非容器他都支持,所有的监控系统都具备这个流程,_-自动化监控系统prometheus&grafana实战

React 组件封装之 Search 搜索_react search-程序员宅基地

文章浏览阅读4.7k次。输入关键字,可以通过键盘的搜索按钮完成搜索功能。_react search