Cesium随笔:鹰眼功能

0.前言

leaf2vue.png

本文记录一下Cesium引擎鹰眼功能的实现过程,项目采用vue框架(修改自cesiumlab的开源项目),小地图采用leaflet。阅读本文需要Cesium和Vue相关知识。

gifeditor_20190516_232158.gif

1.基本思路

鹰眼的基本需求是:在三维地图中镶嵌一个迷你二维地图,在迷你地图中绘制三维地图中用户的可视域、鼠标的位置等,并且随着三维地图画面的更新二维地图也跟着实时变化。
首先是二三维地图的实时联动问题,通过监听Cesium的postRender或者鼠标移动事件,将数据变化实时更新到二维地图即可。
然后是二维地图的选择,需求只需要绘制简单的几何图形,因此这里选择简单好用的leaflet地图。
最后是二维地图绘制逻辑,经过试验,笔者采用二维地图实时聚焦在三维地图鼠标坐标的方式,并且在二维地图中绘制出三维摄像头的可见范围,二维地图的缩放级别随着三维摄像机的离地高度改变。

2.leaflet2vue

项目使用leaflet2vue
组件安装:
npm install vue2-leaflet leaflet --save
在小地图组件中使用leaflet:

<template>
    <div class="vue-leaflet">
        <l-map style="position: absolute; top: 110px; right: 10px; padding: 0px; width: 250px; height: 250px" :zoom="zoom"
         :center="center">
            <l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
            <l-marker :lat-lng="marker">
                <l-popup :content="text"></l-popup>
            </l-marker>
            <l-polygon :lat-lngs="viewRect" :color="viewRectColor">
            </l-polygon>
        </l-map>
    </div>
</template>

<script>
    import {
        LMap,
        LTileLayer,
        LMarker,
        LPopup,
        LPolygon
    } from 'vue2-leaflet'

    export default {
        name: 'leafletMini',
        components: {
            LMap,
            LTileLayer,
            LMarker,
            LPopup,
            LPolygon
        },
        data() {
            return {
                url: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
                attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
                text: '光标的位置',
                viewRectColor: 'orange'
            }
        },
        props: ['center', 'marker', 'zoom', 'viewRect']
    }
</script>

在本例中几乎不需要对leaflet进行任何编程,只需要把地图中心、大头针、视域多边形的数据绑定给相应的组件即实现了leaflet负责的功能。

3.Cesium与leaflet联动

在Cesium窗口中添加小地图组件

<template>
    <div style="width: 100%; height: 100%">
        <div style="position: relative; width: 100%; height: 100%" ref="coreViewer">
            <div ref="viewer" style="width: 100%; height: 100%" id="viewer"></div>
     
            <leafletMini :center="minimapCenter" :marker="minimapCrusor" :zoom="minimapZoom" :viewRect="minimapViewRect"/>
        </div>
    </div>
</template>

<script>
import bindMinimap from '../scripts/eagleEye';
import leafletMini from '../components/leafletMini.vue';
import {
        LMap,
        LTileLayer,
        LMarker,
        LPopup
    } from 'vue2-leaflet'
export default {
    name: "CesiumView",
    components: {
        'leafletMini': leafletMini
    },
    mounted() {
        this.$nextTick(() => {
            const viewer = initViewer(this.$refs.viewer);

            this.freezedViewer = Object.freeze({viewer});

            var that = this;
            document.addEventListener(Cesium.Fullscreen.changeEventName, () => {
                that.isFullscreen = Cesium.Fullscreen.fullscreen;
            });
        });
    },
    data() {
        return {
            freezedViewer: undefined,
            minimapCenter: undefined,
            minimapZoom: 2,
            minimapCrusor: L.latLng(0.0, 0.0),
            minimapViewRect:[]
        };
    },
    methods: {
        
        bindEagleEyeOnMinimap(){
            let h=parseInt(window.getComputedStyle(this.$refs.coreViewer).height);
            let w=parseInt(window.getComputedStyle(this.$refs.coreViewer).width);
            bindMinimap(this.freezedViewer && this.freezedViewer.viewer,(x,y,zoom,viewRectCoords)=>{
                this.minimapCenter=L.latLng(y, x);
                this.minimapZoom=zoom;
                this.minimapCrusor=L.latLng(y, x);
                this.minimapViewRect=viewRectCoords;
            },w,h);
        }
    }
};
</script>

实现联动的代码:eagleEye.js

import Cesium from 'Cesium';
//启动鹰眼功能
function bindMinimap(cesiumViewer, funcWithCursorPos,windowWidth,windowHeight) {
    var handler = new Cesium.ScreenSpaceEventHandler(cesiumViewer.scene.canvas);
    handler.setInputAction(function(movement) {
        let dynamicPosition = undefined;
        let ray = cesiumViewer.camera.getPickRay(movement.endPosition);
        dynamicPosition = cesiumViewer.scene.globe.pick(ray, cesiumViewer.scene);
        let corners=getViewRect(cesiumViewer,cesiumViewer.scene.camera,windowWidth,windowHeight);
        if (Cesium.defined(dynamicPosition)) {
            funcWithCursorPos(Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(dynamicPosition).longitude),
                Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(dynamicPosition).latitude),
                getZoomLevel(cesiumViewer.scene.camera),
                corners
            );
        }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
//计算相机视域
function getViewRect(cesiumViewer,camera,windowWidth,windowHeight){
    let cornerPos = undefined;
    let ray=undefined;
    let positions=[];
    
    ray = cesiumViewer.camera.getPickRay(new Cesium.Cartesian2(0,0));
    cornerPos = cesiumViewer.scene.globe.pick(ray, cesiumViewer.scene);
    if (!Cesium.defined(cornerPos)){
        return [];
    }
    positions.push([Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).latitude),
    Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).longitude)]);
    
    ray = cesiumViewer.camera.getPickRay(new Cesium.Cartesian2(0,windowHeight));
    cornerPos = cesiumViewer.scene.globe.pick(ray, cesiumViewer.scene);
    if (!Cesium.defined(cornerPos)){
        return [];
    }
    positions.push([Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).latitude),
    Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).longitude)]);
    
    ray = cesiumViewer.camera.getPickRay(new Cesium.Cartesian2(windowWidth,windowHeight));
    cornerPos = cesiumViewer.scene.globe.pick(ray, cesiumViewer.scene);
    if (!Cesium.defined(cornerPos)){
        return [];
    }
    positions.push([Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).latitude),
    Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).longitude)]);
    
    ray = cesiumViewer.camera.getPickRay(new Cesium.Cartesian2(windowWidth,0));
    cornerPos = cesiumViewer.scene.globe.pick(ray, cesiumViewer.scene);
    if (!Cesium.defined(cornerPos)){
        return [];
    }
    positions.push([Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).latitude),
    Cesium.Math.toDegrees(Cesium.Cartographic.fromCartesian(cornerPos).longitude)]);
    
    return positions
}
//计算地图缩放等级
function getZoomLevel(camera) {
    let h = camera.positionCartographic.height;
    if (h <= 100) { //0.01
        return 19;
    } else if (h <= 300) { //0.02
        return 18;
    } else if (h <= 660) { //0.05
        return 17;
    } else if (h <= 1300) { //0.1
        return 16;
    } else if (h <= 2600) { //0.2
        return 15;
    } else if (h <= 6400) { //0.5
        return 14;
    } else if (h <= 13200) { //1
        return 13;
    } else if (h <= 26000) { //2
        return 12;
    } else if (h <= 67985) { //5
        return 11;
    } else if (h <= 139780) { //10
        return 10;
    } else if (h <= 250600) { //20
        return 9;
    } else if (h <= 380000) { //30
        return 8;
    } else if (h <= 640000) { //50
        return 7;
    } else if (h <= 1280000) { //100
        return 6;
    } else if (h <= 2600000) { //200
        return 5;
    } else if (h <= 6100000) { //500
        return 4;
    } else if (h <= 11900000) { //1000
        return 3;
    } else {
        return 2;
    }
}
export default bindMinimap;

4.总结

Vue绑定数据很方便,通过Props就能把Cesium主窗口的数据绑定给miniMap子窗口,不需要写多余的代码。包括小地图的中心位置、鼠标坐标、视域范围都是这样同步过来的,且感受不到任何延迟。
具体绑定过程:
1.在Cesium组件声明小地图数据

image.png

2.在小地图组件中声明Props
image.png

3.在Cesium窗口中向Minimap绑定Props和自己的data
image.png

4.在miniMap组件把数据绑定到leaflet
image.png

经过一番折腾,在Cesium窗口中改变绑定的这些data,leaflet小地图就能实时变化了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容