千言万语,道不尽那年那月。万水千山,隔不断缕缕师恩。
祝老师们节日快乐!您辛苦了!

Cesium + Openlayers 二三维联动

为何不用 Cesium 二维模式

Cesium 虽然支持二维模式,但它毕竟的专注方向还是在三维方面。在二维模式下,跟专注于做二维的 OpenLayers 或者 Leaflet,以及
二维起步,现在开始支持三维的 MapboxGL 相比。Cesium的二维模式,总感觉差了那么点意思。我在实际项目的使用中,用 Cesium
二维模式加载自己的切片服务,由于 Cesium 层级的概念,跟二维的层级概念有些区别。因此同样的服务,统一的比例尺下,看的效果
Cesium 总是有点差强人意。 看下对比效果:

Cesium加载展示


Openlayers加载展示

所以项目中期,二维部分的功能用 Openlayers 代替了,说多了都是泪 o(╥﹏╥)o。

废话不多说,先看结果:

上菜

想体验的道友们,可以访问 这里。左侧工具条第三个。

原理

  1. js动态创建二维地图容器。在三维界面左侧。调整宽度样式,整体呈两侧布局。

  2. 由于 Cesium 不能直接获取当前层级,因此根据当前视图范围来同步二维界面。 利用 Cesium 中的 preRender 渲染事件,实时获取三维当前视窗,同时设置二维的视图范围。

  3. 同样二维中监听中心点 change:center 事件,实时设置三维的视窗

  4. 根据当前鼠标在二维或者三维的视口判断,来防止无限循环同步设置。

    实现

  5. 初始化二维地图容器,创建地图视图

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

    this.mapDiv.style.width = '0%';
    this.mapDiv.style.height = '100%';
    this.mapDiv.style.position = 'relative';
    this.mapDiv.style.visibility = 'hidden'
    // insertBefor
    const viewerContainer = this.viewer.cesiumWidget.container.parentElement.parentElement
    viewerContainer.parentNode.insertBefore(this.mapDiv, viewerContainer)
    }

    /**
    * 初始化地图视图
    */
    init2DMap(){
    // const originView = GlobeView.Map2DViewer.getView()
    //
    const layer = new ol.layer.Tile({
    source:new ol.source.XYZ({
    url:'https://server.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}'
    })
    })
    this.olMap = new ol.Map({
    layers:[layer],
    target: this.mapId,
    view: new ol.View({
    center: [13818313.960985335, 6519709.935426011],
    zoom: 8,
    projection: 'EPSG:3857',
    maxZoom:22
    })
    });

    this.olMap.updateSize()

    }

    注意样式调整两侧布局,我用的是弹性盒子布局。即: display: flex

  6. 三维监听事件处理

    /**
    * 三维监听事件处理
    */
    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(ol.proj.transform([center.lon,center.lat],'EPSG:4326','EPSG:3857'));
    } else {
    // console.log(west, south, east, north);
    // this.olMap.getView().fit([transform(west,'EPSG:4326','EPSG:3857'), transform(south,'EPSG:4326','EPSG:3857'), transform(east,'EPSG:4326','EPSG:3857'), transform(north,'EPSG:4326','EPSG:3857')]);
    this.olMap.getView().fit(ol.proj.transformExtent([west,south, east, north],'EPSG:4326','EPSG:3857'));

    }

    }
    }
  7. 二维监听事件处理

    /**
    * 二维监听事件处理
    */
    changeCenterListener(){
    if (this.isIn2DMapFlag) {
    const bounds = this.olMap.getView().calculateExtent();
    const boundsTansform = ol.proj.transformExtent(bounds,'EPSG:3857','EPSG:4326')
    this.viewer.camera.setView({
    destination: Cesium.Rectangle.fromDegrees(
    boundsTansform[0],
    boundsTansform[1],
    boundsTansform[2],
    boundsTansform[3],
    )
    });
    }
    }
  8. 判断当前鼠标是否在二维视图中

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

    //初始化
    const util = new Cesium2DLinkage3DUtil()
    //启动联动
    linkUtil.active(viewer)
    //关闭联动
    linkUtil.deactive()

源码

完整代码都放到 github 上,需要的移步Cesium-demo-view

音乐小憩