前言

上一篇博客简单得介绍了影像图层并成功在视图上加载出来了,而今天我们来实现一个简单的可视化效果,影像图层卷帘。

前置知识:Cesium 事件详解(鼠标事件、相机事件、键盘事件、场景触发事件)_cesium点击事件_GISer小辉的博客-CSDN博客

【CesiumJS入门】(3)ImageryLayer之图层卷帘-LMLPHP

代码

实现步骤

  1. 创建卷帘分割线的div元素
  2. 加载2个影像图层并分别设置切割的方向(Cesium.SplitDirection)
  3. 为分割线div绑定鼠标事件
  4. 在鼠标位移时修改分割线的位置和场景的分割位置
import * as Cesium from 'cesium'
import { map as viewer } from '@/utils/createCesium.js'

export function ImagerySplit (target = 'cesiumContainer') {
  const cesiumCon = document.getElementById(target) // 获取地球渲染的target
  const slider = document.createElement('div')
  const sliderWidth = '5px' // 分割线的宽度
  slider.id = 'slider'
  slider.style.width = sliderWidth
  slider.style.height = '100%'
  slider.style.position = 'fixed'
  slider.style.left = '50%'
  slider.style.top = '0'
  slider.style.backgroundColor = '#3370FF'
  slider.style.zIndex = '10'
  slider.style.cursor = 'col-resize'

  // 将滑动条添加到页面中的某个元素内
  if (cesiumCon) {
    cesiumCon.appendChild(slider) // 将slider元素添加到father元素的子元素列表中
  } else {
    document.querySelector('body').appendChild(slider)
  }

  // 左边的图层,標準風格
  const left = viewer.imageryLayers.addImageryProvider(
    new Cesium.UrlTemplateImageryProvider({
      url: 'https://tile-{s}.openstreetmap.fr/hot/{z}/{x}/{y}.png',
      subdomains: ['a', 'b', 'c', 'd']
    })
  )
  // 右边的图层,黑夜風格
  const right = viewer.imageryLayers.addImageryProvider(
    new Cesium.UrlTemplateImageryProvider({
      url: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
      subdomains: ['a', 'b', 'c', 'd']
    })
  )

  // 设置切割的方向
  left.splitDirection = Cesium.SplitDirection.LEFT
  right.splitDirection = Cesium.SplitDirection.RIGHT

  // 依据滑动条的默认位置设置场景的分割位置,值为0到1之间
  viewer.scene.splitPosition = slider.offsetLeft / slider.parentElement.offsetWidth

  let moveActive = false

  // 滑动事件: 修改滑动条的位置以及视图分割的位置
  function moveEvnet (movement) {
    if (!moveActive) {
      return
    }

    const relativeOffset = movement.endPosition.x
    const splitPosition = (slider.offsetLeft + relativeOffset) / slider.parentElement.offsetWidth

    slider.style.left = `${100.0 * splitPosition}%`
    viewer.scene.splitPosition = splitPosition
  }

  // 中間那個分割綫的句柄
  const handler = new Cesium.ScreenSpaceEventHandler(slider)

  handler.setInputAction(() => {
    moveActive = true
  }, Cesium.ScreenSpaceEventType.LEFT_DOWN) // 未适配PINCH、PINCH_START等操作

  handler.setInputAction(moveEvnet, Cesium.ScreenSpaceEventType.MOUSE_MOVE)

  handler.setInputAction(() => {
    moveActive = false
    // 不要让分割线超出界面了
    if (parseFloat(slider.style.left.replace('%', '')) > 100) {
      slider.style.left = `calc(100% - ${sliderWidth})`
    } else if (parseFloat(slider.style.left.replace('%', '')) < 0) {
      slider.style.left = '0%'
    }
  }, Cesium.ScreenSpaceEventType.LEFT_UP)
}

写完整一点

ok,结合我们项目已有的方法,让我来写一个完整点的影像图层卷帘方法。

先写一个类

export class splitImagery {
  // !属性
  slider // 滑动分割线的div元素
  sliderWidth // 分割线的宽度
  target // 渲染cesium场景的元素
  leftImagery // 左边的影像
  rightImagery // 右边的影像
  moveActive = false // 开启分割线位移
  imageryLayers = [] // 当前场景已有的影像图层
  
  constructor(target = 'cesiumContainer') {
    this.target = target
  }

  // !方法
  ......
}

然后我们往这个类里头加入方法

创建分割线的div元素

  spilitElement() {
    const cesiumCon = document.getElementById(this.target) // 获取地球渲染的target

    this.slider = document.createElement('div')
    this.sliderWidth = '5px' // 分割线的宽度

    this.slider.id = 'slider'
    this.slider.style.width = this.sliderWidth
    this.slider.style.height = '100%'
    this.slider.style.position = 'fixed'
    this.slider.style.left = '50%'
    this.slider.style.top = '0'
    this.slider.style.backgroundColor = '#3370FF'
    this.slider.style.zIndex = '10'
    this.slider.style.cursor = 'col-resize'

    // 将滑动条添加到页面中的元素内
    if (cesiumCon) {
      cesiumCon.appendChild(this.slider)
    } else {
      document.querySelector('body').appendChild(this.slider)
    }
  }

设置分割线左侧展示的影像

  setLeftImagery(layer = loadImagery.ion('', 3812)) {
    if (this.leftImagery) { // 如果已经有图层了则销毁旧的加载新的
      viewer.imageryLayers.remove(this.leftImagery, true); // 移除图层
    }
    this.leftImagery = layer
    // viewer.imageryLayers.add(layer)
    this.leftImagery.splitDirection = Cesium.SplitDirection.LEFT // 分割方向
    console.log('this.leftImagery: ', this.leftImagery);
  }

设置分割线右侧展示的影像

  setRightImagery(layer = loadImagery.ion('', 3845)) {
    if (this.rightImagery) {
      viewer.imageryLayers.remove(this.rightImagery, true); // 移除图层
    }
    this.rightImagery = layer
    // viewer.imageryLayers.add(layer)
    this.rightImagery.splitDirection = Cesium.SplitDirection.RIGHT // 分割方向
  }

获取当前场景中所有的影像图层并保存到数组中

  saveImageryLayers() {
    const layers = viewer.imageryLayers;
    for (let i = 0; i < layers.length; i++) {
      console.log('layers.get(i): ', layers.get(i));

      this.imageryLayers.push(layers.get(i));
    }

    layers.removeAll(false) // 移除所有 ImageryLayer
  }

重新加载之前保存的影像图层

  reloadImageryLayers() {
    const layers = viewer.imageryLayers;
    layers.removeAll(false) // 移除所有 ImageryLayer
    for (let i = 0; i < this.imageryLayers.length; i++) {
      layers.add(this.imageryLayers[i]);
    }
  }

滑动事件: 修改滑动条的位置以及视图分割的位置

  _moveEvnet(movement) {
    if (!this.moveActive) {
      return
    }

    const relativeOffset = movement.endPosition.x
    const splitPosition = (this.slider.offsetLeft + relativeOffset) / this.slider.parentElement.offsetWidth

    this.slider.style.left = `${100.0 * splitPosition}%`
    viewer.scene.splitPosition = splitPosition
  }

开始卷

  actionSplit() {
    this.saveImageryLayers()
    this.spilitElement()
    this.setLeftImagery()
    this.setRightImagery()
    // 依据滑动条的默认位置设置场景的分割位置,值为0到1之间
    viewer.scene.splitPosition = this.slider.offsetLeft / this.slider.parentElement.offsetWidth

    this.handler = new Cesium.ScreenSpaceEventHandler(this.slider)

    this.handler.setInputAction(() => {
      this.moveActive = true
    }, Cesium.ScreenSpaceEventType.LEFT_DOWN) // 未适配PINCH、PINCH_START等操作

    this.handler.setInputAction(this._moveEvnet.bind(this), Cesium.ScreenSpaceEventType.MOUSE_MOVE)

    this.handler.setInputAction(() => {
      this.moveActive = false
      // 不要让分割线超出界面了
      if (parseFloat(this.slider.style.left.replace('%', '')) > 100) {
        this.slider.style.left = `calc(100% - ${this.sliderWidth})`
      } else if (parseFloat(this.slider.style.left.replace('%', '')) < 0) {
        this.slider.style.left = '0%'
      }
    }, Cesium.ScreenSpaceEventType.LEFT_UP)

  }

不卷了

  stopSplit() {
    if (!this.slider) {
      return
    }
    this.reloadImageryLayers()
  
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)

    const cesiumCon = document.getElementById(this.target) // 获取地球渲染的target
    if (cesiumCon) {
      cesiumCon.removeChild(this.slider)
    } else {
      document.querySelector('body').removeChild(this.slider)
    }
  }

调用

import {splitImagery}from '@/utils/ImageryLayer/splitImagery.js'

const splitInstance = new splitImagery() // 声明实例

splitInstance.actionSplit() // 开启卷帘功能
splitInstance.stopSplit() // 关闭卷帘功能
splitInstance.setLeftImagery(loadImagery.osm()) // 修改卷帘左侧的影像,loadImagery.osm()是上一篇博客写的方法,会加载并返回影像图层

【CesiumJS入门】(3)ImageryLayer之图层卷帘-LMLPHP

06-07 14:57