Cesium幾個案例介紹

前言

本文爲你們介紹幾個Cesium的Demo,經過這幾個Demo可以對如何使用Cesium有進一步的瞭解,並能充分理解Cesium的強大之處和新功能。其餘的無需多言,若是還不太瞭解什麼是Cesium,能夠參見個人另外兩篇關於Cesium的博客,下面直接進入正題。css

1、 監聽HTML控件

在Cesium中能夠很方便的監聽前臺HTML控件,相似C#等語言中的MVVM。html

1.1 前臺控件

前臺控件效果以下:前端

代碼以下:json

<div id="toolbar">
    <div>SRTM</div>
    <input min="0" max="100" step="1" data-bind="value: srtm, valueUpdate: 'input'" type="range">
    <input size="5" data-bind="value: srtm" type="text">
    <div>SLOPE</div>
    <input min="0" max="100" step="1" data-bind="value: slope, valueUpdate: 'input'" type="range">
    <input size="5" data-bind="value: slope" type="text">
    <div>Type</div>
    <select data-bind="options: types, optionsText: 'name', value: selectedType, optionsCaption: 'Choose a Type...'"></select>
</div>

首先建立一個div,js監測此div中的控件,重要的是id。canvas

在此div中建立input,一個或多個input對應js中的一個變量,固然此多個input之間也是相互綁定的關係。如:app

<input min="0" max="100" step="1" data-bind="value: srtm, valueUpdate: 'input'" type="range">
<input size="5" data-bind="value: srtm" type="text">

此兩者均對應js端的srtm變量,第一個是range類型,表明一個slide控件,第二個是一個文本框,兩者相互聯動,只選擇其中一個控件也是能夠的。重要的是data-bind屬性中value後的變量名稱需與js中對應。ide

固然也能夠綁定一個下拉列表框:函數

<select data-bind="options: types, optionsText: 'name', value: selectedType, optionsCaption: 'Choose a Type...'"></select>

這裏就對應了js中的兩個變量:types和selectedType。前者表明全部的可選列表及其值,後者表明選擇的結果。this

1.2 後臺

首先建立一個viewModel對象,裏面包含上述建立的各個變量,以下:url

var viewModel = {
    srtm: 10,
    slope: 5,
    types: [{
                name: 'type1',
                values: '100'
            }, {
                name: 'type2',
                values: '200'
            }
    ],
    selectedType: undefined
};

然後對此變量進行監控並綁定到前臺的相應控件:

Cesium.knockout.track(viewModel); // 跟蹤此Model
var toolbar = document.getElementById('toolbar'); // 獲取前端監控div
Cesium.knockout.applyBindings(viewModel, toolbar); // 綁定監控

這樣就能夠監聽控件的變化事件:

Cesium.knockout.getObservable(viewModel, 'srtm').subscribe(function(value) {
    ...
});

能夠對此值進行處理好比發送到後臺或者請求相應的瓦片圖層等等。不過下拉列表框的狀況稍微複雜點:

Cesium.knockout.getObservable(viewModel, 'selectedType').subscribe(function(options) {
    var values = options.values;
    ...
 });

其實也就是多了一步,在定義types的時候除了name變量咱們還定義了values變量,此處就須要經過options.values來取出此值,其餘不變。

2、 根據地形瓦片直接繪製高程、坡度及等高線

這是Cesium 1.4.0版新添加的功能,因此必定要更新到此版本。只須要正確加載地形瓦片,Cesium能夠自動算出高程設色瓦片、坡度設色瓦片以及等高線。其實也不難理解,地形瓦片中包含了空三等信息,根據這些信息天然可以計算出高度圖、坡度圖以及等高線,先來看效果:

加載地形瓦片圖層無需多言,前面已經有過介紹:

viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
    url : 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles',
    requestWaterMask : true,
    requestVertexNormals : true
});

而後就能夠開始計算高程設色瓦片和坡度設色瓦片以及等高線,固然此塊涉及到的東西太多,我只能憑藉我粗淺的理解簡單介紹,若有錯誤,望批評指正:

首先來看一下生成等高線:

var contourUniforms = {};
material = Cesium.Material.fromType('ElevationContour');
contourUniforms = material.uniforms;
contourUniforms.width = 1;
contourUniforms.spacing = 500;
contourUniforms.color = Cesium.Color.RED;

很簡單的幾行代碼,其中Cesium.Material.fromType函數定義以下:

Material.fromType = function(type, uniforms) {
    if (!defined(Material._materialCache.getMaterial(type))) {
        throw new DeveloperError('material with type \'' + type + '\' does not exist.');
    }
    
    var material = new Material({
        fabric : {
            type : type
        }
    });

    if (defined(uniforms)) {
        for (var name in uniforms) {
            if (uniforms.hasOwnProperty(name)) {
                material.uniforms[name] = uniforms[name];
            }
        }
    }

    return material;
};

此函數返回一個Material對象,根據ElevationContour能夠知道這是一個等高線類型的材質。uniforms是glsl着色器語言中的變量,用於控制對象顏色、位置等等。因此此處能夠簡單理解爲獲得ElevationContour類型的unifrom值並將此值做用於場景。Cesium根據此uniform生成相應類型的等高線。

理解了這一點,高程設色和坡度設色也就明白了。

高程設色以下:

var shadingUniforms = {};
material = Cesium.Material.fromType('ElevationRamp');
shadingUniforms = material.uniforms;
shadingUniforms.minHeight = -414.0;
shadingUniforms.maxHeight = 8777;

坡度設色以下:

var shadingUniforms = {};
material = Cesium.Material.fromType('SlopeRamp');
shadingUniforms = material.uniforms;

兩者都須要爲shadingUniforms變量添加一個色表:

shadingUniforms.image = getColorRamp(selectedShading);

var elevationRamp = [0.0, 0.045, 0.1, 0.15, 0.37, 0.54, 1.0];
var slopeRamp = [0.0, 0.29, 0.5, Math.sqrt(2)/2, 0.87, 0.91, 1.0];
function getColorRamp(selectedShading) {
    var ramp = document.createElement('canvas');
    ramp.width = 100;
    ramp.height = 1;
    var ctx = ramp.getContext('2d');

    var values = selectedShading === 'elevation' ? elevationRamp : slopeRamp;

    var grd = ctx.createLinearGradient(0, 0, 100, 0);
    grd.addColorStop(values[0], '#000000'); //black
    grd.addColorStop(values[1], '#2747E0'); //blue
    grd.addColorStop(values[2], '#D33B7D'); //pink
    grd.addColorStop(values[3], '#D33038'); //red
    grd.addColorStop(values[4], '#FF9742'); //orange
    grd.addColorStop(values[5], '#ffd700'); //yellow
    grd.addColorStop(values[6], '#ffffff'); //white

    ctx.fillStyle = grd;
    ctx.fillRect(0, 0, 100, 1);

    return ramp;
}

對高程和坡度歸一化後的值設置顏色。這樣就能夠正常顯示高程設色和坡度設色。

3、 同一場景下顯示兩個不一樣的瓦片圖層

不是簡單的兩個圖層疊加,而是真實的分割整個地圖,左右顯示兩個不一樣的瓦片圖層。效果以下:

首先添加兩個圖層,第一個建立Viewer的時候設置基礎圖層,第二個採用layers.addImageryProvider的方式添加(固然也能夠兩個都採用此種方式添加),具體添加圖層的方式參考前面的博客。

layer1 = layers.addImageryProvider(...);
layer2 = layers.addImageryProvider(...);

只須要設置layer1或則layer2的splitDirection屬性便可:

layer2.splitDirection = Cesium.ImagerySplitDirection.LEFT;

固然還須要設置圖層分割的位置:

viewer.scene.imagerySplitPosition = 0.5;

能夠改變此值來改變左右圖層的分割位置,0.5表示在中間。若是須要動態調整分割位置則須要加一個分割器,監聽位置變化事件。總體代碼以下:

前臺:

<!--css-->
#slider {
    position: absolute;
    left: 50%;
    top: 0px;
    background-color: #D3D3D3;
    width: 5px;
    height: 100%;
    z-index: 9999;
}

#slider:hover {
    cursor: ew-resize;
}

<!--html-->
<div id="cesiumContainer">
    <div id="slider"></div>
</div>

後臺:

var viewer = new Cesium.Viewer('cesiumContainer', {
   baseLayerPicker: false,
   imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
       url : 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer'
   })
});

var layers = viewer.imageryLayers;

var balckMarble = layers.addImageryProvider(Cesium.createTileMapServiceImageryProvider({
    url : 'https://cesiumjs.org/blackmarble',
    credit : 'Black Marble imagery courtesy NASA Earth Observatory',
    flipXY : true
}));

balckMarble.splitDirection = Cesium.ImagerySplitDirection.LEFT;

var slider = document.getElementById('slider');
viewer.scene.imagerySplitPosition = (slider.offsetLeft) / slider.parentElement.offsetWidth;

var handler = new Cesium.ScreenSpaceEventHandler(slider);
var moveActive = false;
function move(movement) {
    if(!moveActive) {
        return;
    }
    var relativeOffset = movement.endPosition.x ;
    var splitPosition = (slider.offsetLeft + relativeOffset) / slider.parentElement.offsetWidth;
    slider.style.left = 100.0 * splitPosition + '%';
    viewer.scene.imagerySplitPosition = splitPosition;
}

handler.setInputAction(function() {
    moveActive = true;
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
handler.setInputAction(function() {
    moveActive = true;
}, Cesium.ScreenSpaceEventType.PINCH_START);

handler.setInputAction(move, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction(move, Cesium.ScreenSpaceEventType.PINCH_MOVE);

handler.setInputAction(function() {
    moveActive = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);
handler.setInputAction(function() {
    moveActive = false;
}, Cesium.ScreenSpaceEventType.PINCH_END);

4、 改造geocoder控件

Cesium自帶了geocoder控件,能夠檢索並定位到某個地址,原理很簡單,就是後臺解析此地址,根據解析結果將地圖切換到該位置。Cesium默認採用的是微軟Bing地址解析引擎,若是咱們想要換成其餘的如OSM或者咱們本身的,只須要對此控件簡單改造便可。示例代碼以下:

/**
 * This class is an example of a custom geocoder. It provides geocoding through the OpenStreetMap Nominatim service.
 * @alias OpenStreetMapNominatimGeocoder
 * @constructor
 */
function OpenStreetMapNominatimGeocoder() {
}

/**
 * The function called to geocode using this geocoder service.
 *
 * @param {String} input The query to be sent to the geocoder service
 * @returns {Promise<GeocoderResult[]>}
 */
OpenStreetMapNominatimGeocoder.prototype.geocode = function (input) {
    var endpoint = 'https://nominatim.openstreetmap.org/search?';
    var query = 'format=json&q=' + input;
    var requestString = endpoint + query;
    return Cesium.loadJson(requestString)  //請求url獲取json數據
        .then(function (results) {
            var bboxDegrees;
            return results.map(function (resultObject) {
                bboxDegrees = resultObject.boundingbox;
                return {
                    displayName: resultObject.display_name,
                    destination: Cesium.Rectangle.fromDegrees(
                        bboxDegrees[2],
                        bboxDegrees[0],
                        bboxDegrees[3],
                        bboxDegrees[1]
                    )
                };
            });
        });
};

var viewer = new Cesium.Viewer('cesiumContainer', {
    geocoder: new OpenStreetMapNominatimGeocoder()
});

首先建立了一個OpenStreetMapNominatimGeocoder類,併爲其添加了geocode方法,在此方法中根據輸入拼接請求url,解析結果取出經緯度、名稱等內容。這樣就實現了咱們本身的地名解析器,其實這就是C#等語言中的父類和繼承的關係。

5、 總結

本文介紹了幾個Cesium的案例,都是一些比較有意思和好玩的功能,後續若是蒐集到其餘好玩的使用案例,一樣也會總結放出。

相關文章
相關標籤/搜索