cesium实现鹰眼图

这个需求算是比较简单的,不涉及到着色器,但鉴于我到的文章都是直接简单的给一个和主视角同步的俯视角小窗 ,而不是给小窗一个大的全局范围,使用entity在这个范围中同步渲染主视角视口位置,所以索性就写一个相关功能的教程,供大家参考

第一步 创建第二个地图

这一段应该没啥好说的,我是用了自己的底图,你可以加载天地图

php 复制代码
async init() {
    this.HawkeyeViewer = new Cesium.Viewer(containerId, {
            animation: false, // 左下角的动画仪表盘
            baseLayerPicker: false, // 右下角的图层选择按钮
            geocoder: false, // 搜索框,
            homeButton: false, // home按钮
            sceneModePicker: false, // 模式切换按钮
            timeline: false, // 底部的时间轴
            navigationHelpButton: false, // 右上角的帮助按钮,
            fullscreenButton: false, // 右下角的全屏按钮
            shouldAnimate: true,
            selectionIndicator: false,
            infoBox: false
        })
        this.HawkeyeViewer.imageryLayers.removeAll() // 清除所有图层
        const env = import.meta.env
        let url = 'abcd1234'
        const layer = await new Cesium.WebMapTileServiceImageryProvider({
            url: url,
            layer: 'nbrmap_nbrmapcgcs2000',
            style: 'default',
            tileMatrixSetID: 'default',
            format: 'image/png',
            tilingScheme: new Cesium.GeographicTilingScheme(),
            maximumLevel: 21,
            tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21']
        })
        this.setimageryLayers(layer) // 重新加载自定义的图层(底图)
        const topLayer = viewer.imageryLayers.get(0)
        topLayer.show = true
        document.querySelector('#'+containerId).querySelector('.cesium-viewer-bottom').remove()
}

第二步 初始化设置

这一步完成鹰眼视角的固定视角和相机前往初始位置,向init函数中添加以下代码

ini 复制代码
        // 前往中心
        this.HawkeyeViewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(121.408887, 29.8313129, 75000.0),
        }) 
        // 阻止鹰眼视角被拖动
        let control = this.HawkeyeViewer.scene.screenSpaceCameraController;
        control.enableRotate = false;
        control.enableTranslate = false;
        control.enableZoom = false;
        control.enableTilt = false;
        control.enableLook = false;

第三步 创建展示主视角位置的实体

当鹰眼图底图创建完成之后,在init方法里调用下面方法

csharp 复制代码
    async createRectangleEntity() {
        if(this.rectangleEntity) {
            this.HawkeyeViewer.entities.remove(this.rectangleEntity)
            this.rectangleEntity = null
        }
        // 先不去设置实体的位置
        this.rectangleEntity = new Cesium.Entity({
            id: 'rectangleEntity',
            polygon: new Cesium.PolygonGraphics({
                hierarchy: new Cesium.PolygonHierarchy([]),
                fill: true,
                material: Cesium.Color.ORANGE.withAlpha(0.3),
            }),
            polyline: new Cesium.PolylineGraphics({
                material: Cesium.Color.ORANGE,
                width: 2
            })
        })
        this.HawkeyeViewer.entities.add(this.rectangleEntity)
    }
    createRectangleEntity()

第四步 获取主视口的范围数据

通过屏幕坐标转笛卡尔3坐标,我们能得到左上和右下的经纬度,拆开之后就可以获取到左下,右上的经纬度,将四个位置转为笛卡尔3并放入数组即是实体位置

kotlin 复制代码
getRange() {
        const leftTop = CusMap.windowPositionToCartesian3({x:0,y:0})
        if(!leftTop) return
        const {clientWidth, clientHeight} = this.mainViewer.canvas
        const rightBottom = CusMap.windowPositionToCartesian3({x: clientWidth, y: clientHeight})
        const [l, t] = CusMap.cartesian3ToDegrees(leftTop)
        const [r, b] = CusMap.cartesian3ToDegrees(rightBottom)
        const leftBottom = Cesium.Cartesian3.fromDegrees(l, b)
        const rightTop = Cesium.Cartesian3.fromDegrees(r, t)
        this.rectangleEntity._p = [leftTop, rightTop, rightBottom, leftBottom, leftTop]
    }

这里列一下坐标转换方法

  1. 屏幕坐标转笛卡尔3
javascript 复制代码
static windowPositionToCartesian3(windowPosition, viewer = window.viewer) {
    // 球面
    if (viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider) { // 还是默认的,没有加载地表高程信息
        // 如果选择了椭球或地图,则返回椭球或地图在世界坐标中的点。如果没有选择了椭球或地图,返回undefined。
        return viewer.scene.camera.pickEllipsoid(windowPosition); // windowPosition	Cartesian2	一个像素的x和y坐标。-> Cartesian3
    }
    // 地形
    else {
        const ray = viewer.camera.getPickRay(windowPosition); // 在世界坐标中,通过像素位置windowPosition,从相机位置创建一条射线
        return viewer.scene.globe.pick(ray, viewer.scene); // 拾取射线与地球表面的交点。射线必须以世界坐标表示。→ Cartesian3|undefined
    }
  }
  1. 笛卡尔3转经纬度
ini 复制代码
static cartesian3ToDegrees(cartesian3) {
    const cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
    const longitude = Cesium.Math.toDegrees(cartographic.longitude);
    const latitude = Cesium.Math.toDegrees(cartographic.latitude);
    const height = cartographic.height;
    return [longitude, latitude, height];
  }

第五步 同步实体在鹰眼视角中的位置

createRectangleEntity 方法最后添加以下代码

kotlin 复制代码
        // 完成实体创建后,监听主视角移动事件,获取实体位置
        this.mainViewer.scene.preRender.addEventListener(this.getRange, this) 
        this.updateRectangleEntityPosition() // 调用检测到位置变换就去更新的方法

updateRectangleEntityPosition 使用CallbackProperty实现自动更新

kotlin 复制代码
updateRectangleEntityPosition() {
        this.rectangleEntity.polyline.positions = new Cesium.CallbackProperty(() => {
            if(this.rectangleEntity._p?.length) {
                return this.rectangleEntity._p
            }
        })
        this.rectangleEntity.polygon.hierarchy = new Cesium.CallbackProperty(() => {
            if(this.rectangleEntity._p?.length) {
                return new Cesium.PolygonHierarchy(this.rectangleEntity._p)
            }
        })
    }

最后贴一下完整代码

kotlin 复制代码
import { CusMap } from './map.js'
export default class HawkeyeView {
    constructor(mainViewer, containerId) {
        this.mainViewer = mainViewer
        this.HawkeyeViewer = null
        this.rectangleEntity = null
        this.init(containerId)
    }

    async init(containerId) {
        this.HawkeyeViewer = new Cesium.Viewer(containerId, {
            animation: false, // 左下角的动画仪表盘
            baseLayerPicker: false, // 右下角的图层选择按钮
            geocoder: false, // 搜索框,
            homeButton: false, // home按钮
            sceneModePicker: false, // 模式切换按钮
            timeline: false, // 底部的时间轴
            navigationHelpButton: false, // 右上角的帮助按钮,
            fullscreenButton: false, // 右下角的全屏按钮
            shouldAnimate: true,
            selectionIndicator: false,
            infoBox: false
        })
        this.HawkeyeViewer.imageryLayers.removeAll()
        const env = import.meta.env
        let url = env.VITE_APP_SERVER_URL + 'map/wmts?tilename=yx&imgtype=png&level={TileMatrix}&row={TileRow}&col={TileCol}'
        const layer = await new Cesium.WebMapTileServiceImageryProvider({
            url: url,
            layer: 'nbrmap_nbrmapcgcs2000',
            style: 'default',
            tileMatrixSetID: 'default',
            format: 'image/png',
            tilingScheme: new Cesium.GeographicTilingScheme(),
            maximumLevel: 21,
            tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21']
        })
        this.setimageryLayers(layer)
        const topLayer = viewer.imageryLayers.get(0)
        topLayer.show = true
        this.HawkeyeViewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(121.408887, 29.8313129, 75000.0),
        })
        let control = this.HawkeyeViewer.scene.screenSpaceCameraController;
        control.enableRotate = false;
        control.enableTranslate = false;
        control.enableZoom = false;
        control.enableTilt = false;
        control.enableLook = false;
        this.createRectangleEntity()
        document.querySelector('#'+containerId).querySelector('.cesium-viewer-bottom').remove()
    }

    setimageryLayers (options) {
      const layer = this.HawkeyeViewer.scene.imageryLayers
      if(Array.isArray(options) | options instanceof Cesium.ImageryLayer || options instanceof Cesium.ImageryProvider || options instanceof Cesium.WebMapTileServiceImageryProvider) {
        const addImageryLayer = (imagery) =>{
          if (imagery instanceof Cesium.ImageryLayer){
            layer.add(imagery)
          } else if (imagery instanceof Cesium.ImageryProvider){
            layer.addImageryProvider(imagery)
            if (Array.isArray(options)) {
              options.forEach(item => {
                addImageryLayer(item)
              })
            } else {
              addImageryLayer(options)
            }
          } else if (imagery instanceof Cesium.WebMapTileServiceImageryProvider){
            layer.addImageryProvider(imagery)
          }
        }
        addImageryLayer(options)
      }
    }

    getRange() {
        const leftTop = CusMap.windowPositionToCartesian3({x:0,y:0})
        if(!leftTop) return
        const {clientWidth, clientHeight} = this.mainViewer.canvas
        const rightBottom = CusMap.windowPositionToCartesian3({x: clientWidth, y: clientHeight})
        const [l, t] = CusMap.cartesian3ToDegrees(leftTop)
        const [r, b] = CusMap.cartesian3ToDegrees(rightBottom)
        const leftBottom = Cesium.Cartesian3.fromDegrees(l, b)
        const rightTop = Cesium.Cartesian3.fromDegrees(r, t)
        this.rectangleEntity._p = [leftTop, rightTop, rightBottom, leftBottom, leftTop]
    }

    async createRectangleEntity() {
        if(this.rectangleEntity) {
            this.HawkeyeViewer.entities.remove(this.rectangleEntity)
            this.rectangleEntity = null
        }
        this.rectangleEntity = new Cesium.Entity({
            id: 'rectangleEntity',
            polygon: new Cesium.PolygonGraphics({
                hierarchy: new Cesium.PolygonHierarchy([]),
                fill: true,
                material: Cesium.Color.ORANGE.withAlpha(0.3),
            }),
            polyline: new Cesium.PolylineGraphics({
                material: Cesium.Color.ORANGE,
                width: 2
            })
        })
        this.HawkeyeViewer.entities.add(this.rectangleEntity)
        this.mainViewer.scene.preRender.addEventListener(this.getRange, this)
        this.updateRectangleEntityPosition()
    }

    updateRectangleEntityPosition() {
        this.rectangleEntity.polyline.positions = new Cesium.CallbackProperty(() => {
            if(this.rectangleEntity._p?.length) return this.rectangleEntity._p
        })
        this.rectangleEntity.polygon.hierarchy = new Cesium.CallbackProperty(() => {
            if(this.rectangleEntity._p?.length) return new Cesium.PolygonHierarchy(this.rectangleEntity._p)
        })
    }

    destroy() {
        this.mainViewer.scene.preRender.removeEventListener(this.getRange)
        this.HawkeyeViewer.entities.remove(this.rectangleEntity)
        this.rectangleEntity = null
        this.HawkeyeViewer.destroy()
        this.HawkeyeViewer = null
    }
}
相关推荐
知识分享小能手3 小时前
Vue3 学习教程,从入门到精通,Axios 在 Vue 3 中的使用指南(37)
前端·javascript·vue.js·学习·typescript·vue·vue3
烛阴7 小时前
精简之道:TypeScript 参数属性 (Parameter Properties) 详解
前端·javascript·typescript
开发者小天9 小时前
为什么 /deep/ 现在不推荐使用?
前端·javascript·node.js
找不到工作的菜鸟10 小时前
Three.js三大组件:场景(Scene)、相机(Camera)、渲染器(Renderer)
前端·javascript·html
定栓10 小时前
vue3入门-v-model、ref和reactive讲解
前端·javascript·vue.js
binqian11 小时前
【异步】js中异步的实现方式 async await /Promise / Generator
开发语言·前端·javascript
前端李二牛11 小时前
异步任务并发控制
前端·javascript
你也向往长安城吗12 小时前
推荐一个三维导航库:three-pathfinding-3d
javascript·算法
karrigan12 小时前
async/await 的优雅外衣下:Generator 的核心原理与 JavaScript 执行引擎的精细管理
javascript
wycode12 小时前
Vue2实践(3)之用component做一个动态表单(二)
前端·javascript·vue.js