1. 程式人生 > >Cesium案例解析(三)——Camera相機

Cesium案例解析(三)——Camera相機

目錄

  • 1. 概述
  • 2. 例項
    • 2.1. Camera.html
    • 2.2. Camera.js
      • 2.2.1. 飛行至某一點
      • 2.2.2. 飛行至某區域
      • 2.2.3. 兩地之間飛行
      • 2.2.4. 設定檢視到某一點
      • 2.2.5. 設定檢視到某區域
  • 3. 其他
    • 3.1. 事件及相應函式
    • 3.2. setReferenceFrame
    • 3.3. viewInICRF
  • 4. 參考

1. 概述

Cesium的Camera案例,展示了其關於漫遊器鏡頭的控制,能夠調整檢視的位置。這裡改進了一下這個例項,使之能夠展示一些自己關注的興趣點的情況,並總結遇到的問題。

2. 例項

2.1. Camera.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <meta name="description" content="Fly to a specified location or view a geographic rectangle.">
    <meta name="cesium-sandcastle-labels" content="Beginner, Tutorials, Showcases">
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Build/Cesium/Cesium.js"></script>
    <style>
        @import url(../Build/Cesium/Widgets/widgets.css);

        html,
        body {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: sans-serif;
            background: #000;
        }

        .fullSize {
            display: block;
            position: absolute;
            top: 0;
            left: 0;
            border: none;
            width: 100%;
            height: 100%;
        }

        #toolbar {
            margin: 5px;
            padding: 2px 5px;
            position: absolute;
        }
    </style>
</head>

<body>
    <div id="cesiumContainer" class="fullSize"></div>
    <div id="toolbar">
        <select id = "camera_select", class="cesium-button">
            <option value="undefined">
                相機選項
            </option>
            <option value="undefined">
                飛行至某一點——武漢大學
            </option>
            <option value="undefined">
                飛行至某區域——武漢市
            </option>
            <option value="undefined">
                設定相機點——華中科技大學
            </option>
            <option value="undefined">
                設定相機區域——上海市
            </option>
            <option value="undefined">
                從武大飛向華科
            </option>
        </select>
    </div>
    <script src="Camera.js"></script>
</body>

</html>

這段程式碼在數字地球展示元件的基礎上新添加了一個檢視控制的下拉列表框,選擇相應的選項能夠將當前的檢視調整到對應的位置。

2.2. Camera.js

//Add your ion access token from cesium.com/ion/ 
Cesium.Ion.defaultAccessToken = '你在Cesium申請的key';

var tdtKey = "你在天地圖申請的key";

'use strict';

//預設BING影像地圖
var viewer = new Cesium.Viewer('cesiumContainer', {
    imageryProvider: Cesium.createWorldImagery({
        style: Cesium.IonWorldImageryStyle.AERIAL
    }),
    baseLayerPicker: false
});

//全球影像中文註記服務
var imageryLayers = viewer.scene.imageryLayers;
var tdtAnnoLayer = imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
    url: "http://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={TileMatrix}&TILEROW={TileRow}&TILECOL={TileCol}&tk=" + tdtKey,
    layer: "tdtAnnoLayer",
    style: "default",
    format: "image/jpeg",
    tileMatrixSetID: "GoogleMapsCompatible"
}));

var camera_select = document.getElementById("camera_select");
if (camera_select) {
    camera_select.onchange = function gradeChange() {
        switch (camera_select.selectedIndex) {
            case 1:
                viewer.camera.flyTo({
                    destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
                    orientation: {
                        heading: Cesium.Math.toRadians(0.0),
                        pitch: Cesium.Math.toRadians(-90.0),
                        roll: Cesium.Math.toRadians(0.0)
                    }
                });
                break;
            case 2:
                viewer.camera.flyTo({
                    destination: Cesium.Rectangle.fromDegrees(113.683333, 29.966667, 115.083333, 31.366667)
                });
                break;
            case 3:
                viewer.camera.setView({
                    destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
                    orientation: {
                        heading: Cesium.Math.toRadians(0.0),
                        pitch: Cesium.Math.toRadians(-90.0),
                        roll: Cesium.Math.toRadians(0.0)
                    }
                });
                break;
            case 4:
                viewer.camera.setView({
                    destination: Cesium.Rectangle.fromDegrees(120.86667, 30.66667, 122.2, 31.883333)
                });
                break;
            case 5: {
                var whdxOptions = {
                    destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
                    duration: 5,
                    orientation: {
                        heading: Cesium.Math.toRadians(0.0),
                        pitch: Cesium.Math.toRadians(-90.0),
                        roll: Cesium.Math.toRadians(0.0)
                    }
                };
                var hzkjdxOptions = {
                    destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
                    orientation: {
                        heading: Cesium.Math.toRadians(0.0),
                        pitch: Cesium.Math.toRadians(-90.0),
                        roll: Cesium.Math.toRadians(0.0)
                    },
                    duration: 5,
                   //flyOverLongitude: Cesium.Math.toRadians(60.0)
                };

                whdxOptions.complete = function () {
                    setTimeout(function () {
                        viewer.camera.flyTo(hzkjdxOptions);
                    }, 1000);
                };

                // if (adjustPitch) {
                //     tokyoOptions.pitchAdjustHeight = 1000;
                //     laOptions.pitchAdjustHeight = 1000;
                // }

                viewer.camera.flyTo(whdxOptions);
            }
            break;
        default:
            break;
        }
    }
}

這段程式碼首先添加了Cesium.Viewer預設的Bing影像地圖和天地圖的中文標註;然後根據id獲取HTML頁面的下拉列表框控制元件camera_select;最後根據選項調整相應的相機檢視。這裡展示了幾種調整檢視的方式。

2.2.1. 飛行至某一點

設定相機鏡頭逐漸從當前位置飛行到某一點是通過Cesium.Camera的flyTo()函式實現的,其具體的函式定義如下:

圖1:Cesium.Camera的flyTo()函式定義

該函式傳入了鍵值對配置物件,其中destination、orientation這兩項,分別表示相機鏡頭的位置和姿態。本例中相應的程式碼如下:

viewer.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
    orientation: {
        heading: Cesium.Math.toRadians(0.0),
        pitch: Cesium.Math.toRadians(-90.0),
        roll: Cesium.Math.toRadians(0.0)
    }
});

這段設定相機檢視的程式碼意思就是,將相機的位置移動到經緯度位置(114.35231209, 30.53542614),離地面5000米的點;航向角(heading)設定為0度,俯仰角(pitch)設定為-90度,滾轉角(roll)設定為0度。實際頁面的顯示效果為逐漸飛往某一點:

圖2:飛行至武漢大學附近

此時數字地球會顯示在武漢大學附近,視線看上去會垂直與地面,並且東西南北方向也基本上與常規地圖一致。實際上當不設定姿態引數orientation只設置位置引數destination也能達到同樣的效果,說明(0.0,-90.0,0.0)的三個姿態角是設定相機檢視的預設值。在Cesium的設定中,heading、pitch、roll的定義如下:

這說明航向角(heading)是繞Z後負方向旋轉的角度,俯仰角(pitch)是繞Y軸負方向旋轉的角度,滾轉角(roll)是繞X軸正方向旋轉的角度。那麼問題來了,這個定義裡面的X、Y、Z軸的指的是什麼呢?我這裡認為這個函式蘊含了一種檢視變換,使得基於相機的視空間座標系成為一種類似於一種北東地站心座標系(NED)座標系,XYZ軸指的正是這個視空間座標系的XYZ軸。在這個視空間座標系中,Z軸垂直球面向下(Down),Y軸沿緯線指東(East),X軸沿經線向北(North),而位於視空間座標系原點的相機的姿態為由南看向北。在這種情況下,只需要使相機繞Y軸正向旋轉90度,也就是俯仰角(pitch)設為90,就可以得到視線垂直於地圖,東西南北向正常的檢視。

2.2.2. 飛行至某區域

flyTo()函式另外一個很有用的功能就是根據設定的範圍顯示檢視,這在顯示特定空間的檢視時特別有用,例如載入的三維模型的範圍,一個地區的範圍等等。實現也很很簡單,只需要給位置引數destination傳入一個Cesium.Rectangle物件即可:

viewer.camera.flyTo({
    destination: Cesium.Rectangle.fromDegrees(113.683333, 29.966667, 115.083333, 31.366667)
});

將武漢市的經緯度範圍傳入,實際的顯示結果如下:

圖3:飛行至武漢市

2.2.3. 兩地之間飛行

flyTo()函式還可以傳入一個配置項complete,可以給其設定一個飛行結束後再執行的函式,通過這個配置項可以實現兩地或多地飛行:

var whdxOptions = {
    destination: Cesium.Cartesian3.fromDegrees(114.35231209, 30.53542614, 5000.0),
    duration: 5,
    orientation: {
        heading: Cesium.Math.toRadians(0.0),
        pitch: Cesium.Math.toRadians(-90.0),
        roll: Cesium.Math.toRadians(0.0)
    }
};
var hzkjdxOptions = {
    destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
    orientation: {
        heading: Cesium.Math.toRadians(0.0),
        pitch: Cesium.Math.toRadians(-90.0),
        roll: Cesium.Math.toRadians(0.0)
    },
    duration: 5,
    //flyOverLongitude: Cesium.Math.toRadians(60.0)
};

whdxOptions.complete = function () {
    setTimeout(function () {
        viewer.camera.flyTo(hzkjdxOptions);
    }, 1000);
};

// if (adjustPitch) {
//     tokyoOptions.pitchAdjustHeight = 1000;
//     laOptions.pitchAdjustHeight = 1000;
// }

viewer.camera.flyTo(whdxOptions);

這段程式碼分別定義了兩個飛行配置項whdxOptions和hzkjdxOptions,並且給whdxOptions的complete項配置了一個函式,表示完成1S之後,自動進行hzkjdxOptions的飛行。執行結果如下圖所示:

圖4:武大飛行至華科

2.2.4. 設定檢視到某一點

設定當前檢視通過setView()函式實現的,它跟flyTo()最大的不同是沒有持續時間,沒有飛行過程,是立即生效的。其具體的配置選項也比較相似,都是需要設定位置以及姿態:

viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(114.40782845, 30.51011682, 5000.0),
    orientation: {
        heading: Cesium.Math.toRadians(0.0),
        pitch: Cesium.Math.toRadians(-90.0),
        roll: Cesium.Math.toRadians(0.0)
    }
});

2.2.5. 設定檢視到某區域

設定具體的顯示範圍,也是立即生效,這兩個部分因為與flyTo()函式比較類似,就不再具體講解了。

viewer.camera.setView({
    destination: Cesium.Rectangle.fromDegrees(120.86667, 30.66667, 122.2, 31.883333)
});

3. 其他

3.1. 事件及相應函式

Cesium.Camera還提供了當前檢視發生變化的事件changed、檢視發生移動的事件moveStart/moveEnd,它們都可以通過addEventListener()給其新增相應的響應函式。

3.2. setReferenceFrame

自帶案例Camera中還提供了另外一種檢視控制方式:

function setReferenceFrame() {
    Sandcastle.declare(setReferenceFrame);

    var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
    var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

    // View in east-north-up frame
    var camera = viewer.camera;
    camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
    camera.lookAtTransform(transform, new Cesium.Cartesian3(-120000.0, -120000.0, 120000.0));    

    // Show reference frame.  Not required.
    referenceFramePrimitive = scene.primitives.add(new Cesium.DebugModelMatrixPrimitive({
        modelMatrix: transform,
        length: 100000.0
    }));
}

這段程式碼的意思是選定一個經緯度的點,可以計算出以該點為中心的東北天(ENU)站心座標系與地心座標系的轉換矩陣,將這個矩陣傳入給Cesium.Camera的lookAtTransform函式,從而達到設定檢視的目的。但是這樣做會導致當前世界座標系發生變化,當前漫遊器的鍵鼠互動操作不再以地心座標系原點為中心,而以站心座標系的原點為中心,導致這個時候的鍵鼠互動操作難以操作。

3.3. viewInICRF

Cesium預設是基於ITRF,也就是國際地球地心參考框架。自帶案例還提供了一種將其轉換為ICRF參考框架的檢視設定方式。關於ICRF我也不是很瞭解,查閱網上資料只知道是一種原點在太陽系的質心的天文參考框架,留待以後需要用到的時候再研究。

4. 參考

[1]. 北東地/東北天兩種導航座標系與姿態轉換
[2]. Cesium中的相機—HeadingPitchRoll
[3]. Cesium類HeadingPitchRoll及heading、pitch、roll等引數