cesium-ol二三维联动(鹰眼图)

476 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

“金秋十月,我要连续30天更文,做劳模,拿手机摄影神器!点击查看活动详情 “即可成功参与

领导又来了新需求,太难受了/(ㄒoㄒ)/~~,这次的需求是实现 cesium-二三维联,话不多说直接开始吧!

cesium-ol二三维联动(鹰眼图)

cesium除了使用两个viewer联动的方式实现鹰眼图,还有其他方式

这里我使用cesium和ol联动的方式实现鹰眼图(也可以实现简单的二三维联动效果)

实现思路

1、监听cesium地图的移动

2、当移动视图时,计算cesium视图范围,并传递给ol

3、监听ol地图移动,当ol移动时,计算ol的视图范围

4、传递给cesium(cesium会垂直移动)

html

<template>
  <div class="main-view" style="position: relative; display: flex; height: 800px">
    <div class="toolbox">
      <button id="point" style="color: white; margin-bottom: 20px" class="tool-item">联动</button>
      <button id="clear" style="color: white" class="tool-item">关闭</button>
    </div>
    <div id="cesiumContainer"></div>
  </div>
</template>

  #cesiumContainer {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    display: flex;
  }
  .toolbox {
    position: absolute;
    top: 49%;
    left: 30px;
    z-index: 99;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
  }
  .tool-item {
    background-color: #303336;
    cursor: pointer;
  }

  #eye {
    position: absolute;
    width: 581px !important;
    height: 581px !important;
    bottom: 0;
    right: 0;
    z-index: 999;
    border: solid blue 1px;
  }

核心js代码

<script setup>
  import * as Cesium from 'cesium';
  import Cesium2DLinkage3DUtil from './map'; //引入核心js代码
  import { onMounted } from 'vue';
  //加载3D地图
  const init3d = () => {
    const viewer = new Cesium.Viewer('cesiumContainer', {
      geocoder: false,
      homeButton: false,
      sceneModePicker: false,
      baseLayerPicker: false,
      navigationHelpButton: false,
      animation: false,
      timeline: false,
      fullscreenButton: false,
      vrButton: false,
      mapProjection: new Cesium.WebMercatorProjection()
      //  sceneMode: Cesium.SceneMode.SCENE2D
    });
    viewer._cesiumWidget._creditContainer.style.display = 'none'; // 隐藏logo版权
    // 天地图加载
    const td = '02ed99e199228a5d47960a0324894605'; // 一天只能请求一万次啊
    const TDTImgProvider = new Cesium.WebMapTileServiceImageryProvider({
      url:
        'http://t{s}.tianditu.com/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=' +
        td,
      layer: '天地图影像',
      style: 'default',
      format: 'image/jpeg',
      subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
      tileMatrixSetID: 'GoogleMapsCompatible'
    });
    const TDTZJProvider = new Cesium.WebMapTileServiceImageryProvider({
      url:
        'http://t{s}.tianditu.com/cia_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg&tk=' +
        td,
      layer: '天地图中文注记',
      style: 'default',
      format: 'image/jpeg',
      subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
      tileMatrixSetID: 'GoogleMapsCompatible'
    });
    // 地形添加
    viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
      url: 'https://nanchang.3zyun.com/terrain/t'
    });
    viewer.imageryLayers.addImageryProvider(TDTImgProvider);
    viewer.imageryLayers.addImageryProvider(TDTZJProvider);
    //   相机聚焦 将三维球定位到中国 相机飞行
    viewer.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 17850000), // 经纬度 高度
      orientation: {
        heading: Cesium.Math.toRadians(348.4202942851978),
        pitch: Cesium.Math.toRadians(-89.74026687972041),
        roll: Cesium.Math.toRadians(0)
      },
      complete: function callback() {
        // 定位完成之后的回调函数
        console.log(' 定位完成之后的回调函数');
      }
    });
 /*    // 设置相机的位置和方向
    viewer.camera.setView({
      // fromDegrees()方法,将经纬度和高程转换为世界坐标
      destination: Cesium.Cartesian3.fromDegrees(103.84, 31.15, 17850000),
      orientation: {
        // 指向
        heading: Cesium.Math.toRadians(90, 0),
        // 视角
        pitch: Cesium.Math.toRadians(-90),
        roll: 0.0
      }
    }); */

    viewer._cesiumWidget._creditContainer.style.display = 'none'; // 隐藏版权
    //具体实现联动方法(核心方法)
    const linkUtil = new Cesium2DLinkage3DUtil();
    document.getElementById('point').onclick = function () {
      if (linkUtil.isActive) return;
      linkUtil.active(viewer);
    };
    document.getElementById('clear').onclick = function () {
      linkUtil.deactive();
    };
  };
  onMounted(() => {
    init3d(); // 加载
  });
</script>

核心js:

/**
 * 二三维联动,二维openlayers,三维Cesium
 */
import * as Cesium from 'cesium';
import 'ol/ol.css';
import TileGrid from 'ol/tilegrid/TileGrid';
import * as ol from 'ol';
import { Tile as TileLayer } from 'ol/layer';
import { defaults } from 'ol/control';
import XYZ from 'ol/source/XYZ';
import { transformExtent, transform } from 'ol/proj';

export default class Cesium2DLinkage3DUtil {
  constructor(mapId = 'eye') {
    this.mapId = mapId;
    this.isActive = false;
    this.isIn2DMapFlag = false;
    this.mouseMoveEvent = this.mouseMoveEvent.bind(this);
    this.getViewCameraRectrange = this.getViewCameraRectrange.bind(this);
    this.changeCenterListener = this.changeCenterListener.bind(this);
  }

  /**
   * 初始化ol地图容器,插入三维容器的左侧
   */
  init2DDiv() {
    this.mapDiv = document.createElement('div');
    this.mapDiv.setAttribute('id', this.mapId);
    this.mapDiv.style.width = 1500 + 'px';

    // insertBefore
    const viewerContainer = this.viewer.cesiumWidget.container.parentElement.parentElement;
    viewerContainer.parentNode.insertBefore(this.mapDiv, viewerContainer);
  }

  /**
   * 初始化ol地图视图
   */

  init2DMap() {
    const resolutions = [];
    const tilegrid = new TileGrid({
      origin: [0, 0], // 设置原点坐标
      resolutions // 设置分辨率
    });
    // 普通地图
    const tiandituVecLayer = new TileLayer({
      projection: 'EPSG:3857',
      opacity: 1,
      tileGrid: tilegrid,
      source: new XYZ({
        url: 'https://t3.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=82ddfa50085e53ebbeb11b6c91988f6a'
      })
    });
    // 普通地图标记
    const tiandituCvaLayer = new TileLayer({
      projection: 'EPSG:3857',
      opacity: 1,
      tileGrid: tilegrid,
      source: new XYZ({
        url: 'https://t3.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=82ddfa50085e53ebbeb11b6c91988f6a'
      })
    });

    this.olMap = new ol.Map({
      layers: [tiandituVecLayer, tiandituCvaLayer],
      target: this.mapId,
      view: new ol.View({
        center: [103.84, 31.15], // 地图中心点
        zoom: 8,
        projection: 'EPSG:3857',
        maxZoom: 22
      }),
      // 设置地图控件,默认的三个控件都不显示
      controls: defaults({
        attribution: false,
        rotate: false,
        zoom: false
      })
    });

    this.olMap.updateSize();
  }

  /**
   * 二维监听事件处理
   */
  changeCenterListener() {
    if (this.isIn2DMapFlag) {
      const bounds = this.olMap.getView().calculateExtent();
      const boundsTansform = transformExtent(bounds, 'EPSG:3857', 'EPSG:4326');
      this.viewer.camera.setView({
        heading: Cesium.Math.toRadians(120.0), // 方向
        pitch: Cesium.Math.toRadians(-10), // 倾斜角度
        roll: this.viewer.camera.roll,
        destination: Cesium.Rectangle.fromDegrees(boundsTansform[0], boundsTansform[1], boundsTansform[2], boundsTansform[3])
      });
    }
  }

  /**
   * 三维监听事件处理
   */
  getViewCameraRectrange() {
    const rectangle = this.viewer.camera.computeViewRectangle();
    // 弧度转为经纬度
    const west = (rectangle.west / Math.PI) * 180;

    const north = (rectangle.north / Math.PI) * 180;

    const east = (rectangle.east / Math.PI) * 180;

    const south = (rectangle.south / Math.PI) * 180;
    // 三维联动二维界面
    if (!this.isIn2DMapFlag) {
      if (north > 87 && south < -87) {
        const center = this.getCenterPosition(this.viewer);
        this.olMap.getView().setZoom(0);
        this.olMap.getView().setCenter(transform([center.lon, center.lat], 'EPSG:4326', 'EPSG:3857'));
      } else {
        this.olMap.getView().fit(transformExtent([west, south, east, north], 'EPSG:4326', 'EPSG:3857'));
      }
    }
  }

  /**
   * 判断鼠标是否在二维地图
   * @param x
   * @param y
   * @return {boolean}
   */
  isMouseIn2DMap(x, y) {
    const y1 = this.mapDiv.offsetTop; // div上面两个的点的y值
    const y2 = y1 + this.mapDiv.clientHeight; // div下面两个点的y值
    const x1 = this.mapDiv.offsetLeft; // div左边两个的点的x值
    const x2 = x1 + this.mapDiv.clientWidth; // div右边两个点的x的值
    return !(x < x1 || x > x2 || y < y1 || y > y2);
  }

  addListener() {
    this.olMap.getView().on('change:center', this.changeCenterListener);
    this.olMap.updateSize();
    this.viewer.cesiumWidget.container.parentElement.parentElement.parentElement.addEventListener('mousemove', this.mouseMoveEvent);
    this.viewer.scene.preRender.addEventListener(this.getViewCameraRectrange);
  }

  removeListener() {
    this.olMap.getView().un('change:center', this.changeCenterListener);
    this.viewer.scene.preRender.removeEventListener(this.getViewCameraRectrange);
    this.viewer.cesiumWidget.container.parentElement.parentElement.parentElement.removeEventListener('mousemove', this.mouseMoveEvent);
    this.mapDiv.style.width = '0%';
    this.mapDiv.parentNode.removeChild(this.mapDiv);
    this.olMap = null;
  }

  mouseMoveEvent(e) {
    this.isIn2DMapFlag = this.isMouseIn2DMap(e.pageX, e.pageY);
  }

  getCenterPosition(viewer) {
    const result = viewer.camera.pickEllipsoid(new Cesium.Cartesian2(viewer.canvas.clientWidth / 2, viewer.canvas.clientHeight / 2));
    const curPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(result);
    const lon = (curPosition.longitude * 180) / Math.PI;
    const lat = (curPosition.latitude * 180) / Math.PI;
    return {
      lon,
      lat
    };
  }

  active(viewer) {
    this.viewer = viewer;
    this.isActive = true;
    this.init2DDiv();
    this.init2DMap();
    this.addListener();
  }

  deactive() {
    this.removeListener();
    this.isActive = false;
    this.viewer = undefined;
  }
}

实现效果:

image.png

image.png

最后实现了cesium-ol二三维联动(鹰眼图),点击关闭退出联动状态。

但还是有点问题:cesium和ol平面视角联动还算正常的,但是当cesium为三维视角时联动的效果就出现问题。下次解决