Cesium几个案例介绍

作者: 魏守峰 | 来源:发表于2017-12-20 15:18 被阅读188次

    前言

    本文为大家介绍几个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的案例,都是一些比较有意思和好玩的功能,后续如果搜集到其他好玩的使用案例,同样也会总结放出。

    相关文章

      网友评论

        本文标题:Cesium几个案例介绍

        本文链接:https://www.haomeiwen.com/subject/uvmgwxtx.html