前言
本文為大家介紹幾個Cesium的Demo,通過這幾個Demo能夠對如何使用Cesium有進一步的了解,並能充分理解Cesium的強大之處和新功能。其他的無需多言,如果還不太了解什么是Cesium,可以參見我的另外兩篇關於Cesium的博客,下面直接進入正題。
一、 監聽HTML控件
在Cesium中可以很方便的監聽前台HTML控件,類似C#等語言中的MVVM。
1.1 前台控件
前台控件效果如下:
代碼如下:
<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。
在此div中創建input,一個或多個input對應js中的一個變量,當然此多個input之間也是相互綁定的關系。如:
<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中對應。
當然也可以綁定一個下拉列表框:
<select data-bind="options: types, optionsText: 'name', value: selectedType, optionsCaption: 'Choose a Type...'"></select>
這里就對應了js中的兩個變量:types和selectedType。前者代表所有的可選列表及其值,后者代表選擇的結果。
1.2 后台
首先創建一個viewModel對象,里面包含上述創建的各個變量,如下:
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來取出此值,其他不變。
二、 根據地形瓦片直接繪制高程、坡度及等高線
這是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;
}
對高程和坡度歸一化后的值設置顏色。這樣就可以正常顯示高程設色和坡度設色。
三、 同一場景下顯示兩個不同的瓦片圖層
不是簡單的兩個圖層疊加,而是真實的分割整個地圖,左右顯示兩個不同的瓦片圖層。效果如下:
首先添加兩個圖層,第一個創建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);
四、 改造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#等語言中的父類和繼承的關系。
五、 總結
本文介紹了幾個Cesium的案例,都是一些比較有意思和好玩的功能,后續如果搜集到其他好玩的使用案例,同樣也會總結放出。