• WEB端显示三维地形模型


    注:正常在WEB上显示三维地形首选Cesium,本文内容仅作为研究,展示文章用DEM制作通用三维地形模型中制作的局部三维地形模型

    Cesium是可以很容易的实现在WEB端三维地形的,下面的图是分别是使用基于Cesium的Mars3D和超图的iClient3D出来的效果。不过Cesium终究是基于地球的,比较适合大块区域的展示。和文章用DEM制作通用三维地形模型里做的模型效果来比,还是差点意思,资源占用也很高。加上因为三维模型都是笛卡尔坐标系,我们在制作模型的时候也使用了高斯克吕格投影坐标系,直接整个模型加到球形的Cesium里再缩放到一个县的范围那么大,必然是不能处处对应准的,所以那篇文章的成果就不太适合用Cesium展示了。
    Mars3D实现效果
    SuperMap iClient3D实现效果

    理论上只要支持gltf的webgl库比如three.js等都是可以展示我再上篇文章中生成的地形模型的,我使用的babylon.js,我在之前文章蓝牙Beacon室内定位全栈里有用过,功能比较强大,可以说是一个WebGL的三维引擎也不为过。

    babylon.js的使用比较方便,引入babylon.js和加载gltf的babylonjs.loaders.js,使用canvas进行渲染。

    DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <meta name="description" content="">
            <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
            <title>三维地形title>
            <link rel="stylesheet" media="all" href="./css/index.css">
            <script src="./lib/babylon/babylon.js">script>
            <script src="./lib/babylon/loaders/babylonjs.loaders.js">script>
        head>
        <body>
            <canvas id="renderCanvas">canvas>
        body>
        <script src="./js/projection.js">script>
        <script src="./js/index.js">script>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    首先初始化引擎和场景

    let _canvas,_engine,_scene,_camera;
    
    _canvas = document.getElementById('renderCanvas');
    _engine = new BABYLON.Engine(_canvas, true);
    _scene = createScene();
    _engine.runRenderLoop(function() {
    	if (_scene.activeCamera) {
    		_scene.render();
    	}
    });
    
    //创建场景
    
    const createScene = function() {
        const scene = new BABYLON.Scene(_engine);
        _camera = new BABYLON.ArcRotateCamera('camera', Math.PI/2, Math.PI/4, 30, new BABYLON.Vector3(0, 0, 0));
        _camera.inputs.attached.mousewheel.wheelPrecision = 8;
        _camera.inputs.attached.pointers.panningSensibility = 250;
        _camera.inputs.attached.pointers.angularSensibilityX = 5000;
        _camera.inputs.attached.pointers.angularSensibilityY = 5000;
        _camera.attachControl(_canvas, true);
        const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0));
        return scene;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在创建引擎和场景的时候,为了方便调试,可以在场景中把坐标轴展示出来,很可惜babylon.js不原生支持坐标轴展示,需要手动画上去。

    //创建坐标轴
    const showAxis = function (size) {
        const makeTextPlane = function (text, color, size) {
            const dynamicTexture = new BABYLON.DynamicTexture('DynamicTexture', 50, _scene, true);
            dynamicTexture.hasAlpha = true;
            dynamicTexture.drawText(text, 5, 40, 'bold 36px Arial', color, 'transparent', true);
            const plane = new BABYLON.Mesh.CreatePlane('TextPlane', size, _scene, true);
            plane.material = new BABYLON.StandardMaterial('TextPlaneMaterial', _scene);
            plane.material.backFaceCulling = false;
            plane.material.specularColor = new BABYLON.Color3(0, 0, 0);
            plane.material.diffuseTexture = dynamicTexture;
            return plane;
        }; 
    
        const axisX = BABYLON.Mesh.CreateLines('axisX', [
            new BABYLON.Vector3.Zero(), new BABYLON.Vector3(size, 0, 0), new BABYLON.Vector3(size * 0.95, 0.05 * size, 0),
            new BABYLON.Vector3(size, 0, 0), new BABYLON.Vector3(size * 0.95, -0.05 * size, 0)
        ], _scene);
    
        axisX.color = new BABYLON.Color3(1, 0, 0);
        const xChar = makeTextPlane('X', 'red', size / 10);
        xChar.position = new BABYLON.Vector3(0.9 * size, -0.05 * size, 0);
        
        const axisY = BABYLON.Mesh.CreateLines('axisY', [
            new BABYLON.Vector3.Zero(), new BABYLON.Vector3(0, size, 0), new BABYLON.Vector3(-0.05 * size, size * 0.95, 0),
            new BABYLON.Vector3(0, size, 0), new BABYLON.Vector3(0.05 * size, size * 0.95, 0)
        ], _scene);
        axisY.color = new BABYLON.Color3(0, 1, 0);
        const yChar = makeTextPlane('Y', 'green', size / 10);
        yChar.position = new BABYLON.Vector3(0, 0.9 * size, -0.05 * size);
        
        const axisZ = BABYLON.Mesh.CreateLines('axisZ', [
            new BABYLON.Vector3.Zero(), new BABYLON.Vector3(0, 0, size), new BABYLON.Vector3(0, -0.05 * size, size * 0.95),
    
            new BABYLON.Vector3(0, 0, size), new BABYLON.Vector3(0, 0.05 * size, size * 0.95)
    
        ], _scene);
        axisZ.color = new BABYLON.Color3(0, 0, 1);
        const zChar = makeTextPlane('Z', 'blue', size / 10);
        zChar.position = new BABYLON.Vector3(0, 0.05 * size, 0.9 * size);
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    加载DEM glb文件

    //加载DEM
    BABYLON.SceneLoader.LoadAssetContainer('./','asset/dem.glb' , _scene, function (container) {
    	container.meshes[0].id = '__dem__';
    	container.meshes[0].name = '__dem__';
    	container.addAllToScene();
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    毕竟这是一个地理数据,难免需要加一些其他地理数据,因此需要实现坐标转换。这里需要再次说明,之前所有的数据制作流程都是使用的高斯克吕格投影投影,为什么要是用高斯克吕格投影请看我的文章三维GIS建模不要用墨卡托投影,高斯克吕格投影投影和经纬度之间的坐标转换请看我的文章蓝牙Beacon室内定位全栈移动端展示模型部分。单就我这个DEMO来说,还有一点点不同,我使用的坐标系是CGCS2000_3_Degree_GK_Zone_37而不是CGCS2000_3_Degree_GK_CM_111E(两者有啥区别以及为什么会有两种以后有机会再说),加上我们建模的时候不是直接按图像的尺寸来的,有一个缩放,因此在经纬度转成高斯克吕格投影坐标系之后,还需要做一个转换到当前的三维空间坐标,代码如下:

    //经纬度转场景坐标
    const LngLat2XY = function(lng,lat)
    {
        const prjPosition = _projection.LngLat2XY(lng,lat);
        const dx = (prjPosition[0] + 37500000) - _center[0];
        const dy = prjPosition[1] - _center[1];
        const x = dx/_cellSize/100;
        const y = dy/_cellSize/100;
        return [-x,-y]
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中_center_cellSize从DEM影像数据的属性信息来。
    DEM影像属性

    然后我们就可以按位置加入一些模型了,比如

    //加载模型
    
    BABYLON.SceneLoader.LoadAssetContainer('./','asset/camera.glb' , _scene, function (container) {
    	container.meshes[0].id = 'camera_1';
    	container.meshes[0].name = 'camera_1';
    	container.meshes[0].scaling = new BABYLON.Vector3(15, 15, 15);
    	const xy = LngLat2XY(111.288,30.485)
    	container.meshes[0].position = new BABYLON.Vector3(xy[0], 2, xy[1]);
    	container.addAllToScene();
    	const points = [
    		new BABYLON.Vector3(xy[0], -0.5, xy[1]),
    		new BABYLON.Vector3(xy[0], 2, xy[1])
    	];
    	//位置点虚线
    	const line = BABYLON.MeshBuilder.CreateDashedLines("camera_line", {
    		points: points,
    		dashSize: 50,
    		gapSize: 25,
    		dashNb: 10});
    	line.color = new BABYLON.Color3(0, 0, 1);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    还可以在上面加Geojson数据,但是我没找到怎么让线贴着地形走的方式。

        const request = new XMLHttpRequest();
        request.open('get','./asset/XZQ.geojson');
        request.send();
        request.onload = ()=>{
            if (request.status == 200) {
                const features = JSON.parse(request.responseText).features
                features.forEach(feature => {
                    const coords = feature.geometry.coordinates[0][0]
                    const positons = []
                    for (let index = 0; index < coords.length; index++) {
                        const coord = coords[index];
                        const xy = LngLat2XY(coord[0],coord[1])
                        positons.push(new BABYLON.Vector3(xy[0], 0.5, xy[1]))
                    }
                    const xzqLine = BABYLON.MeshBuilder.CreateLines(`xzqLine_${feature.properties.XZQDM}`, {points: positons});
                    xzqLine.color = new BABYLON.Color3(1, 1, 1);
                });
    
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我想,既然画上去的线没法贴地,那我可不可以给模型贴材质贴图呢。于是,我把要在上面展示的数据转换坐标系到CGCS2000_3_Degree_GK_Zone_37,通过GeoServer发布成WMS服务,将通过WMS请求回来的图片当做材质贴图贴在模型上。代码如下:

     const url = `${_geoserverUrl}/map/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image/png&TRANSPARENT=true&STYLES&LAYERS=map:xzq&SRS=EPSG:4525&WIDTH=${_imgSize[0]}&HEIGHT=${_imgSize[1]}&BBOX=${xmin},${ymin},${xmax},${ymax}`
    let orgMat = container.meshes[1].material;
    orgMat.bumpTexture = new BABYLON.Texture(url, _scene);
    orgMat.bumpTexture.vScale = -1
    
    • 1
    • 2
    • 3
    • 4

    这里需要注意,请求里的WIDTH和HEIGHT要和建模时使用的图片成比例,我是直接使用的原尺寸。请求里面的BBOX的最大值最小值一定要是用DEM影像属性里的范围,不能错。SRS要是用对应的高斯克吕格坐标系,也不能错。

    最终效果就如图了。

    最终展示效果

  • 相关阅读:
    4. 【containerd】pull image 如何配置密码
    第五章Maven依赖的特性-进阶篇
    夜天之书 #98 Rust 程序库生态合作的例子
    CentOS7和CentOS8 Asterisk 20.0.0 简单图形化界面7--对接讯时FXO网关落地
    Redis分布式锁
    【VUE 获取PDF文档流直接打印】
    中国制霸生成器「GitHub 热点速览 v.22.42」
    Kafka-SSL笔记整理
    【vue设计与实现】简单Diff算法 1-减少DOM操作的性能开销
    Java EE企业级开发学习 -- day1
  • 原文地址:https://blog.csdn.net/zxhm001/article/details/126244208