0.前言
本文記錄一下Cesium引擎鷹眼功能的實(shí)現(xiàn)過程岁疼,項(xiàng)目采用vue框架(修改自cesiumlab的開源項(xiàng)目)赤炒,小地圖采用leaflet蟹略。閱讀本文需要Cesium和Vue相關(guān)知識(shí)缓淹。
1.基本思路
鷹眼的基本需求是:在三維地圖中鑲嵌一個(gè)迷你二維地圖哈打,在迷你地圖中繪制三維地圖中用戶的可視域、鼠標(biāo)的位置等讯壶,并且隨著三維地圖畫面的更新二維地圖也跟著實(shí)時(shí)變化料仗。
首先是二三維地圖的實(shí)時(shí)聯(lián)動(dòng)問題,通過監(jiān)聽Cesium的postRender或者鼠標(biāo)移動(dòng)事件伏蚊,將數(shù)據(jù)變化實(shí)時(shí)更新到二維地圖即可立轧。
然后是二維地圖的選擇,需求只需要繪制簡單的幾何圖形躏吊,因此這里選擇簡單好用的leaflet地圖氛改。
最后是二維地圖繪制邏輯,經(jīng)過試驗(yàn)比伏,筆者采用二維地圖實(shí)時(shí)聚焦在三維地圖鼠標(biāo)坐標(biāo)的方式胜卤,并且在二維地圖中繪制出三維攝像頭的可見范圍,二維地圖的縮放級(jí)別隨著三維攝像機(jī)的離地高度改變赁项。
2.leaflet2vue
項(xiàng)目使用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: '© <a >OpenStreetMap</a> contributors',
text: '光標(biāo)的位置',
viewRectColor: 'orange'
}
},
props: ['center', 'marker', 'zoom', 'viewRect']
}
</script>
在本例中幾乎不需要對(duì)leaflet進(jìn)行任何編程澈段,只需要把地圖中心、大頭針舰攒、視域多邊形的數(shù)據(jù)綁定給相應(yīng)的組件即實(shí)現(xiàn)了leaflet負(fù)責(zé)的功能败富。
3.Cesium與leaflet聯(lián)動(dòng)
在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>
實(shí)現(xiàn)聯(lián)動(dòng)的代碼:eagleEye.js
import Cesium from 'Cesium';
//啟動(dòng)鷹眼功能
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);
}
//計(jì)算相機(jī)視域
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
}
//計(jì)算地圖縮放等級(jí)
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.總結(jié)
Vue綁定數(shù)據(jù)很方便,通過Props就能把Cesium主窗口的數(shù)據(jù)綁定給miniMap子窗口摩窃,不需要寫多余的代碼兽叮。包括小地圖的中心位置、鼠標(biāo)坐標(biāo)偶芍、視域范圍都是這樣同步過來的充择,且感受不到任何延遲德玫。
具體綁定過程:
1.在Cesium組件聲明小地圖數(shù)據(jù)
2.在小地圖組件中聲明Props
3.在Cesium窗口中向Minimap綁定Props和自己的data
4.在miniMap組件把數(shù)據(jù)綁定到leaflet
經(jīng)過一番折騰匪蟀,在Cesium窗口中改變綁定的這些data,leaflet小地圖就能實(shí)時(shí)變化了宰僧。