赞
踩
现有的gis开发方向较流行的是webgis开发,其中Cesium是一款开源的WebGIS库,主要用于实时地球和空间数据的可视化和分析。它提供了丰富的地图显示和数据可视化功能,并能实现三维可视化开发。Cesium作为三维GIS框架,其最大的优势就是支持各种格式的三维模型数据,包括倾斜摄影三维模型、BIM三维模型、glb三维模型等,并且用户可以使用关键帧动画等动态地模拟三维场景。本文将介绍Cesium中常用的三维模型操作并举例说明。
3D Tiles数据格式是Cesium支持的一种标准,包括BIM模型、倾斜摄影三维模型等都需要通过一定手段或软件处理成3D Tiles模型后才可以在Cesium中加载。在3D Tiles模型制作过程中,如果原始数据具有高程信息,那么生产的3D Tiles模型数据也会存在高程数据,但是有时由于原始数据中的高程信息不准确,生产出来的3D Tiles模型并不能很好地贴合地形,而是悬浮在空中,因此在Cesium中加载倾斜摄影三维模型之后,需要手动地对模型进行高度调整以达到贴合地形的要求。Cesium API为我们提供了相关的类和方法,使得我们可以在3D Tiles模型数据加载完成之后,动态地调整3D Tiles模型整体的高度。下面以大雁塔倾斜摄影三维模型为例,展示如何在Cesium中动态地调整3D Tiles模型高度。
(1)实现代码
5_1_3D Tiles模型高度调整.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.0"> <title>三维模型篇_3D Tiles模型高度调整</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } .heightAdjustDiv { position: absolute; top: 10px; left: 20px; background-color: rgba(0, 0, 0, 0.6); } </style> </head> <body> <div id="cesiumContainer"> </div> <div class="heightAdjustDiv"> <label style="color: white;">高度</label> <br /> <input type="range" min="-100" max="100" step="1" oninput="change()" id="R" value="0"> <input type="text" style="width:70px; " id="heightValue" value="0" onchange="change2()"> </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { geocoder: true, //是否显示地名查找工具 homeButton: true, //是否显示首页位置工具 sceneModePicker: true, //是否显示视角模式切换工具 baseLayerPicker: false, //是否显示默认图层选择工具 navigationHelpButton: true, //是否显示导航帮助工具 animation: false, //是否显示动画工具 timeline: false, //是否显示时间轴工具 fullscreenButton: true, //是否显示全屏按钮工具 terrainProvider: Cesium.createWorldTerrain() }); viewer.scene.globe.depthTestAgainstTerrain = true; var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: './倾斜摄影/大雁塔3DTiles/tileset.json' })); viewer.zoomTo(tileset); const R = document.getElementById("R"); //当滑动条变化时调用该函数 function change() { //拿到滑动条当前值 var height = Number(R.value); //文本框显示当前值 heightValue.value = height; //判断是否为数字,不是数字则return if (isNaN(height)) { return; } //将3D Tiles外包围球中心点从笛卡尔空间直角坐标转换为弧度表示 const cartographic = Cesium.Cartographic.fromCartesian( tileset.boundingSphere.center //3D Tiles外包围球中心 ); //3D Tiles外包围球中心点原始坐标 const surface = Cesium.Cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, ); //3D Tiles外包围球中心点坐标偏移 const offset = Cesium.Cartesian3.fromRadians( cartographic.longitude, cartographic.latitude, height ); //计算两个笛卡尔分量的差异 const translation = Cesium.Cartesian3.subtract( offset, surface, new Cesium.Cartesian3() ); //创建一个表示转换的Matrix4 tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); } function change2() { var height = Number(heightValue.value); R.value = height; change(); } </script> </body> </html>
(2)结果显示
变换前:
变换后:
下面 以大雁塔倾斜摄影三维模型为例,展示如何在Cesium中动态地对3D Tiles模型进行旋转平移操作。
(1)实现代码
5_2_3D Tiles模型旋转平移.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.0"> <title>三维模型篇_3D Tiles模型旋转平移</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <!-- <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script> --> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } .adjust3DTilesDiv { position: absolute; top: 10px; left: 20px; background-color: rgba(0, 0, 0, 0.6); } </style> </head> <body> <div id="cesiumContainer"> </div> <div class="adjust3DTilesDiv"> <label style="color: white;">X轴旋转</label> <br /> <input type="range" min="-100" max="100" step="1" oninput="rotation()" id="Rx" value="0"> <input type="text" style="width:70px; " id="RxValue" value="0" onchange="rotationX()"> <br> <label style="color: white;">Y轴旋转</label> <br /> <input type="range" min="-100" max="100" step="1" oninput="rotation()" id="Ry" value="0"> <input type="text" style="width:70px; " id="RyValue" value="0" onchange="rotationY()"> <br> <label style="color: white;">Z轴旋转</label> <br /> <input type="range" min="-100" max="100" step="1" oninput="rotation()" id="Rz" value="0"> <input type="text" style="width:70px; " id="RzValue" value="0" onchange="rotationZ()"> <br> <label style="color: white;">经度平移</label> <br /> <input type="range" min="-100" max="100" step="1" oninput="translation()" id="Tlon" value="0"> <input type="text" style="width:70px; " id="TlonValue" value="0" onchange="translationLon()"> <br> <label style="color: white;">纬度平移</label> <br /> <input type="range" min="-100" max="100" step="1" oninput="translation()" id="Tlat" value="0"> <input type="text" style="width:70px; " id="TlatValue" value="0" onchange="translationLat()"> <br> </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { geocoder: true, //是否显示地名查找工具 homeButton: true, //是否显示首页位置工具 sceneModePicker: true, //是否显示视角模式切换工具 baseLayerPicker: true, //是否显示默认图层选择工具 navigationHelpButton: true, //是否显示导航帮助工具 animation: false, //是否显示动画工具 timeline: false, //是否显示时间轴工具 fullscreenButton: true, //是否显示全屏按钮工具 //terrainProvider: Cesium.createWorldTerrain() }); var tileset = viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: './倾斜摄影/大雁塔3DTiles/tileset.json' })); viewer.zoomTo(tileset); var cartographic; var params; tileset.readyPromise.then(function () { cartographic = Cesium.Cartographic.fromCartesian( tileset.boundingSphere.center //倾斜摄影模型外包围球中心 ); console.log('cartographic',cartographic); params = { tx: Cesium.Math.toDegrees(cartographic.longitude), //模型中心X轴坐标(经度,单位:十进制度) ty: Cesium.Math.toDegrees(cartographic.latitude), //模型中心Y轴坐标(纬度,单位:十进制度) tz: 0, //模型中心Z轴坐标(高程,单位:米) rx: 0, //X轴(经度)方向旋转角度(单位:度) ry: 0, //Y轴(纬度)方向旋转角度(单位:度) rz: 0 ,//Z轴(高程)方向旋转角度(单位:度) }; }) //平移旋转函数 function update3dtilesMaxtrix(params) { //旋转 let mx = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(params.rx)); let my = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(params.ry)); let mz = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(params.rz)); let rotationX = Cesium.Matrix4.fromRotationTranslation(mx); let rotationY = Cesium.Matrix4.fromRotationTranslation(my); let rotationZ = Cesium.Matrix4.fromRotationTranslation(mz); //平移 let position = Cesium.Cartesian3.fromDegrees(params.tx, params.ty, params.tz); console.log('position',position); let m = Cesium.Transforms.eastNorthUpToFixedFrame(position); //旋转、平移矩阵相乘 Cesium.Matrix4.multiply(m, rotationX, m); Cesium.Matrix4.multiply(m, rotationY, m); Cesium.Matrix4.multiply(m, rotationZ, m); //返回结果矩阵 return m; } //Rx、Ry、Rz旋转滑动条 function rotation(){ //拿到x轴旋转滑动条当前值 var rx = Number(Rx.value); //x轴旋转文本框显示当前值 RxValue.value = rx; //拿到y轴旋转滑动条当前值 var ry = Number(Ry.value); //x轴旋转文本框显示当前值 RyValue.value = ry; //拿到z轴旋转滑动条当前值 var rz = Number(Rz.value); //x轴旋转文本框显示当前值 RzValue.value = rz; //判断是否为数字,不是数字则return if (isNaN(rx)&&isNaN(ry)&&isNaN(rz)) { return; } params.rx = rx; params.ry = ry; params.rz = rz; tileset._root.transform = update3dtilesMaxtrix(params); } //X轴文本框 function rotationX(){ var rx = Number(RxValue.value); Rx.value = rx; rotation(); } //Y轴文本框 function rotationY(){ var ry = Number(RyValue.value); Ry.value = ry; rotation(); } //Z轴文本框 function rotationZ(){ var rz = Number(RzValue.value); Rz.value = rz; rotation(); } //平移滑动条 function translation(){ //拿到经度平移滑动条当前值 var tLon = Number(Tlon.value); //经度平移文本框显示当前值 TlonValue.value = tLon; //拿到纬度平移滑动条当前值 var tLat = Number(Tlat.value); //纬度平移文本框显示当前值 TlatValue.value = tLat; //判断是否为数字,不是数字则return if (isNaN(tLon)&&isNaN(tLat)) { return; } params.tx = Cesium.Math.toDegrees(cartographic.longitude) + tLon/500; params.ty =Cesium.Math.toDegrees(cartographic.latitude) + tLat/500; tileset._root.transform = update3dtilesMaxtrix(params); } //经度文本框 function translationLon(){ var tLon = Number(TlonValue.value); Tlon.value = tLon; translation(); } //纬度文本框 function translationLat(){ var tLat = Number(TlatValue.value); Tlat.value = tLat; translation(); } </script> </body> </html>
(2)结果显示
变换前:
变换后:
在Cesium中调整3D Tiles模型的位置还包括对3D Tiles模型进行整体缩放。基本思路为先通过缩放比例计算出一个Matrix4的矩阵实例,然后与3D Tiles模型的初始变换矩阵相乘,得到缩放后的矩阵并赋给3D Tiles模型根节点中的transform。下面以大雁塔倾斜摄影三维模型为例,展示如何在Cesium中动态地对3D Tiles模型进行缩放操作。
(1)实现代码
5_3_3D Tiles模型缩放.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.0"> <title>三维模型篇_3D Tiles模型缩放</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } .adjust3DTilesDiv { position: absolute; top: 10px; left: 20px; background-color: rgba(0, 0, 0, 0.6); } </style> </head> <body> <div id="cesiumContainer"> </div> <div class="adjust3DTilesDiv"> <label style="color: white;">缩放倍数</label> <br /> <input type="range" min="0.01" max="10" step="0.01" oninput="changeScale()" id="Scale" value="1"> <input type="text" style="width:70px; " id="scaleValue" value="1" onchange="changeScale2()"> <br> </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { geocoder: true, //是否显示地名查找工具 homeButton: true, //是否显示首页位置工具 sceneModePicker: true, //是否显示视角模式切换工具 baseLayerPicker: true, //是否显示默认图层选择工具 animation: false, //是否显示动画工具 timeline: false, //是否显示时间轴工具 fullscreenButton: true, //是否显示全屏按钮工具 }); //加载三维模型 var tileset = viewer.scene.primitives.add( new Cesium.Cesium3DTileset({ url: './倾斜摄影/大雁塔3DTiles/tileset.json' }) ); //定位过去 viewer.zoomTo(tileset); var m; var mStar; tileset.readyPromise.then(function (argument) { //得到外包围盒中心点坐标 var cartographic = Cesium.Cartographic.fromCartesian(tileset.boundingSphere.center); //坐标变换为Cartesian3类型 var surface = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, cartographic.height); //以提供的原点为中心计算4x4变换矩阵 m = Cesium.Transforms.eastNorthUpToFixedFrame(surface); //记录模型初始转移矩阵,放大缩小以此为基础 mStar = tileset._root.transform }); //缩放滑动条 function changeScale() { //缩放 var scale = Number(Scale.value); scaleValue.value = scale; if (scale) { const _scale = Cesium.Matrix4.fromUniformScale(scale); Cesium.Matrix4.multiply(mStar, _scale, m); tileset._root.transform = m; } else{ return; } } //缩放值文本框 function changeScale2() { var scale = Number(scaleValue.value); Scale.value = scale; changeScale(); } </script> </body> </html>
(2)结果显示
变换前:
变换后:
目前,使用倾斜摄影技术可以快速、高效地生成逼真的实景三维模型,但是这样生成的实景三维模型实际上是连续三角网贴图的结果。它建立了一个连续的TIN网(不规则三角网),并不区分建筑、地面、树木等特征。因此,在此基础上生成的模型的地理要素是集成于一体的,并不能被区分开,也就无法对各部分对象进行分别管理,实用性就大大降低了。对于这种模型,我们不能选中单个建筑的数据。要想数据能够被有效管理,模型就必须具备“单体化”的能力。
在Cesium中,我们将倾斜摄影三维模型转换为3D Tiles数据格式之后,可以通过在3D Tiles模型上动态叠加分类瓦片层来实现单体化效果。其原理是根据分类的矢量表面数据生成分类瓦片,并使其附着在实景三维模型表面,监听鼠标指针的位置,实现当鼠标指针移动至相应的位置时高亮显示该分类瓦片,从而使得模型单体化。
下面以大雁塔倾斜摄影三维模型转换得到的3D Tiles模型为例,讲解制作单体化分类瓦片的过程并在Cesium中实现3D Tiles模型单体化。
在Cesium中,3D Tiles模型是通过动态叠加分类瓦片数据来实现单体化的,而分类瓦片数据是由矢量数据通过Cesium实验室处理得到的,所以我们需要先进行矢量图层的制作。下面以大雁塔卫星影像为底图来进行矢量图层的制作。
(1)打开ArcGIS Pro并加载大雁塔卫星影像。
(2)在ArcGIS Pro中连接到文件夹,并创建新Shapefile,设置名称为“大雁塔1”,要素类型为“面”,空间参考为大雁塔影像空间参考。
创建结果如下:
(3)在创建完成后,“大雁塔”图层会在左侧图层目录中被打开,右击该图层,在弹出的快捷菜单中选择相应命令,打开属性表并单击属性表左上角的下拉按钮,在弹出的下拉列表中选择“添加字段”选项。
(4)为矢量图层添加4个字段:name,类型为text,用于记录矢量化的建筑的名称;minheight,类型为float,用于记录某一层底面的绝对高程;maxheight,类型为float,用于记录某一层顶面的绝对高程;descrip,类型为text,用于描述建筑。添加字段后的结果如下。
(5)在编辑菜单要素页中点击创建,开始进行矢量化。我们对照大雁塔底座的大小进行矢量化,并且在矢量化时,要注意单体化矢量面尽量大一些,只要保证不和旁边的单体化矢量面相交即可。因为我们的倾斜数据、底图数据本身都有误差,如果太过紧凑,反而不能完全包含倾斜数据,导致效果不好。另一个需要注意的是单体化矢量面应尽量简单一些,不要有过多的顶点,因为多边形顶点越多,生产的封闭体就越复杂,渲染效率就越低。绘制的矢量面如下。
(6)矢量化一个面之后,我们可以通过Cesium实验室预览转换好的大雁塔三维模型3D Tiles数据中大雁塔的每一层高度,并填写矢量数据属性表中的字段值。
大雁塔基座底面高度为425.32米,大雁塔基座顶面高度为430.12米。所以,在“大雁塔”图层的属性表中填写该矢量面的属性:name为“大雁塔基座”,minheight为“425.32”,maxheight为“430.12”,descrip为“塔座”,如下。
(7)由于大雁塔共有8层,所以可以复制7个同样的图层,依次将前一层的maxheight作为下一层的minheight进行填充,然后在Cesium实验室中预览下一层的顶面高度并填充到maxheight中,接着填充name、descrip,最终属性表结果如下。至此,矢量数据制作完成。
在矢量图层制作完成之后,使用Cesium实验室工具将数据处理成3D Tiles数据。
(1)打开Cesium实验室,选择“矢量数据切片”→“矢量楼块切片”选项。
(2)选择输入文件为制作好的矢量图层;在“建筑高度”选项组中选中“高度字段”单选按钮,选择“maxheight”字段作为建筑高度;在“底面高度”选项组中选中“高度字段”选项,选择“minheight”字段作为底面高度;在“其他选项”选项组中勾选“绝对高度”复选框,勾选后,建筑的模型高度=建筑高度-底面高度;将属性字段全部勾选并设置输出路径。
矢量楼块切片结果如下。
(1)实现代码
5_4_3D Tiles模型单体化.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.0"> <title>三维模型篇_3D Tiles模型单体化</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <div id="cesiumContainer"> </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { geocoder: true, //是否显示地名查找工具 homeButton: true, //是否显示首页位置工具 sceneModePicker: true, //是否显示视角模式切换工具 baseLayerPicker: false, //是否显示默认图层选择工具 navigationHelpButton: true, //是否显示导航帮助工具 animation: false, //是否显示动画工具 timeline: false, //是否显示时间轴工具 fullscreenButton: true, //是否显示全屏按钮工具 terrainProvider: Cesium.createWorldTerrain() }); viewer.scene.globe.depthTestAgainstTerrain = true;//开启深度监测 var tileset = viewer.scene.primitives.add( new Cesium.Cesium3DTileset({ url: './倾斜摄影/大雁塔3DTiles/tileset.json' }) ); viewer.zoomTo(tileset); //加载分类瓦片 var classifytileset = new Cesium.Cesium3DTileset({ url: './RasterImage/单体化切片1/tileset.json', classificationType: Cesium.ClassificationType.CESIUM_3D_TILE }); //设置分类瓦片透明度 classifytileset.style = new Cesium.Cesium3DTileStyle({ color: 'rgba(255, 255, 255,0.01)' }); viewer.scene.primitives.add(classifytileset); var highlighted = { feature: undefined, originalColor: new Cesium.Color(), }; //鼠标移动 let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction(function onMouseMove(movement) { if (Cesium.defined(highlighted.feature)) { highlighted.feature.color = highlighted.originalColor; highlighted.feature = undefined; } // 拾取新要素 var pickedFeature = viewer.scene.pick(movement.endPosition); if (!Cesium.defined(pickedFeature)) { return; } // 高亮显示 highlighted.feature = pickedFeature; Cesium.Color.clone(pickedFeature.color, highlighted.originalColor); pickedFeature.color = Cesium.Color.LIME.withAlpha(0.5); }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); </script> </body> </html>
(2)结果显示
在Cesium中,鼠标单击事件、鼠标移动事件等的使用非常频繁。在很多时候,当我们想要获取鼠标单击的位置或者鼠标移动经过的位置时,会出现不同的情况,例如,要获取鼠标单击位置的屏幕坐标、鼠标单击位置对应的椭球面位置、加载地形数据后对应的经纬度和高程,以及鼠标单击位置的3D Tiles数据及信息。本节将详细介绍3D Tiles要素拾取,实现鼠标移动时高亮显示拾取的3D Tiles要素,并提示相关属性信息。
下面使用的3D Tiles数据可以根据4.1和4.2中的步骤自行制作,相关属性信息需要自行写入。
(1)实现代码
5_5_3D Tiles要素拾取.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>三维模型篇_3D Tiles要素拾取</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> </head> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> <body> <div id="cesiumContainer"></div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer('cesiumContainer', { geocoder: true, //是否显示地名查找工具 homeButton: true, //是否显示首页位置工具 sceneModePicker: true, //是否显示视角模式切换工具 baseLayerPicker: true, //是否显示默认图层选择工具 animation: false, //是否显示动画工具 timeline: false, //是否显示时间轴工具 fullscreenButton: false, //是否显示全屏按钮工具 }); var tileSet = viewer.scene.primitives.add( new Cesium.Cesium3DTileset({ url: "./3D格式数据/Tileset/tileset.json" }) ); //定位过去 viewer.zoomTo(tileSet); // 创建div const newDiv = document.createElement("div"); viewer.container.appendChild(newDiv); newDiv.style.display = "none"; newDiv.style.position = "absolute"; newDiv.style.bottom = "0"; newDiv.style.left = "0"; newDiv.style.padding = "4px"; newDiv.style.backgroundColor = "white"; //创建高亮要素对象 const highlighted = { feature: undefined, originalColor: new Cesium.Color(), }; let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction(function (event) { //捕捉要素 const pickedFeature = viewer.scene.pick(event.endPosition); console.log('pickedFeature',pickedFeature); //当未捕捉到要素时隐藏div if (!Cesium.defined(pickedFeature)) { newDiv.style.display = "none"; return; } //否则若捕捉到要素 else { //高亮显示 if (Cesium.defined(highlighted.feature)) { highlighted.feature.color = highlighted.originalColor; highlighted.feature = undefined; } highlighted.feature = pickedFeature; Cesium.Color.clone(pickedFeature.color, highlighted.originalColor); pickedFeature.color = Cesium.Color.LIME.withAlpha(0.5); //提示高度 newDiv.style.display = "block"; //加5 是为了不让div遮挡左键点击 newDiv.style.bottom = `${viewer.canvas.clientHeight - event.endPosition.y + 5}px`; newDiv.style.left = `${event.endPosition.x}px`; const name = "Height:" + pickedFeature.getProperty("Height").toFixed(2) + "m"; newDiv.textContent = name; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); </script> </body> </html>
(2)结果显示
在Cesium中,3D Tiles要素风格的使用场景非常多。无论是想要根据某个属性进行分级、分类渲染,还是自定义个性化渲染,都离不开相关的Cesium3DTileStyle属性,只需使用Cesium3DTileStyle属性,根据3D Tiles要素属性进行样式风格设置并赋值给Cesium3DTileset即可。在前面的3D Tiles要素拾取中,我们已经讲解了通过获取Cesium3DTileset中的某个要素来进行样式修改,而下面以Cesium内置的OSM建筑白膜数据为例展示通过Cesium3DTileStyle属性来根据特定需求渲染Cesium3DTileset中的要素。
(1)实现代码
5_6_3D Tiles要素风格.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.0"> <title>三维模型篇_3D Tiles要素风格</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } .toolbar { position: absolute; top: 10px; left: 20px; background-color: rgb(0, 0, 0, 0); } </style> </head> <body> <div id="cesiumContainer"> </div> <div class="toolbar"> <select id="dropdown" onchange="change()"> <option value="0">null</option> <option value="1">按建筑类型设置颜色</option> <option value="2">按到指定位置的距离选择颜色</option> <option value="3">交互渲染</option> <option value="4">building属性为dormitory</option> <option value="5">building属性为apartments</option> </select> </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { /* terrainProvider: Cesium.createWorldTerrain(), */ timeline: false, animation: false, vrButton: true, sceneModePicker: false, infoBox: true, scene3DOnly: false, }); //添加OSM建筑白膜数据 var osmBuildingsTileset = Cesium.createOsmBuildings(); viewer.scene.primitives.add(osmBuildingsTileset); //调整相机视角 viewer.scene.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(114.39564, 30.52214, 2000), //destination:Cesium.Cartesian3.fromDegrees(-74.0012351579127,40.715093849131,1000), }); //按建筑类型渲染 function colorByBuildingType() { let osmBuildingsStyle = new Cesium.Cesium3DTileStyle({ color: { conditions: [ ["${building} === 'university'", "color('skyblue', 0.8)"], ["${building} === 'dormitory'", "color('cyan', 0.9)"], ["${building} === 'yes'", "color('purple', 0.7)"], ], }, }); osmBuildingsTileset.style = osmBuildingsStyle; } //按建筑类型显示 function showByBuildingType(buildingType) { switch (buildingType) { case "dormitory": osmBuildingsTileset.style = new Cesium.Cesium3DTileStyle({ show: "${building} === 'dormitory'", }); break; case "apartments": osmBuildingsTileset.style = new Cesium.Cesium3DTileStyle({ show: "${building} === 'apartments'", }); break; default: break; } } //按到指定位置的距离分级渲染 function colorByDistanceToCoordinate(pickedLongitude, pickedLatitude) { var osmBuildingsStyle = new Cesium.Cesium3DTileStyle({ defines: { //自定义字段 distance: "distance(vec2(${feature['cesium#longitude']}, ${feature['cesium#latitude']}), vec2(" + pickedLongitude + "," + pickedLatitude + "))", }, color: { conditions: [ ["${distance} > 0.014", "color('blue')"], ["${distance} > 0.010", "color('green')"], ["${distance} > 0.006", "color('yellow')"], ["${distance} > 0.0001", "color('red')"], ["true", "color('white')"], ], }, }); osmBuildingsTileset.style = osmBuildingsStyle; } //获取点击位置坐标 var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); function getCoordinate() { handler.setInputAction(function (click) { var pickedFeature = viewer.scene.pick(click.position) var pickedLongitude = parseFloat(pickedFeature.getProperty("cesium#longitude")); var pickedLatitude = parseFloat(pickedFeature.getProperty("cesium#latitude")); /* console.log( typeof pickedLongitude); console.log(typeof pickedLatitude); */ colorByDistanceToCoordinate(pickedLongitude, pickedLatitude) }, Cesium.ScreenSpaceEventType.LEFT_CLICK); } //交互渲染 function interactiveRendering(feature) { var selected = feature.getProperty('elementId'); //请注意属性字段的类型 若属性字段为string类型的 则条件要加 '' var condition = "${elementId} === " + selected; console.log('condition',condition); osmBuildingsTileset.style = new Cesium.Cesium3DTileStyle({ color: { conditions: [ [condition, "color('cyan', 0.9)"], ] } }) } //获取点击要素 function getFeature() { handler.setInputAction(function (evt) { var pickedFeature = viewer.scene.pick(evt.position) interactiveRendering(pickedFeature); }, Cesium.ScreenSpaceEventType.LEFT_CLICK) } var dropdown = document.getElementById('dropdown'); function change() { switch (dropdown.value) { case '0': osmBuildingsTileset.style = new Cesium.Cesium3DTileStyle({}); handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)//移除事件 break; case '1': colorByBuildingType() handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)//移除事件 break; case '2': getCoordinate() break; case '3': getFeature() break; case '4': showByBuildingType('dormitory') handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)//移除事件 break; case '5': /* var osmBuildingsTileset = Cesium.createOsmBuildings(); viewer.scene.primitives.add(osmBuildingsTileset); */ showByBuildingType('apartments') handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)//移除事件 break; } } </script> </body> </html>
(2)结果显示
当3D模型被加载到场景中后,会默认展示3D模型建模时的材质。如果需要修改模型材质,则可以通过其属性信息进行修改。
(1)实现代码
5_7_模型着色.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>三维模型篇_模型着色</title> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <script type="text/javascript" src="./Build/Cesium/Cesium.js"></script> </head> <body> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } #toolbar { background: rgba(245, 240, 240, 0.8); top: 4px; border-radius: 4px; position: absolute; } #toolbar input { vertical-align: middle; padding-top: 2px; padding-bottom: 2px; } </style> <div id="cesiumContainer" class="fullSize"></div> <div id="toolbar"> <table> <tbody> <tr> <td>Model Color</td> </tr> <tr> <td>Mode</td> <td> <select id="mode" onchange="changeMode()"> <option value="Highlight">Highlight</option> <option value="Replace">Replace</option> <option value="Mix">Mix</option> </select> </td> </tr> <tr> <td>Color</td> <td> <select id="color" onchange="changeColor()"> <option value="White">White</option> <option value="Red">Red</option> <option value="Green">Green</option> <option value="Blue">Blue</option> <option value="Yellow">Yellow</option> <option value="Gray">Gray</option> </select> </td> </tr> <tr> <td>Alpha</td> <td> <input type="range" min="0.0" max="1.0" step="0.01" value="1" id="alpha" oninput="changeAlpha()"> <input type="text" size="5" value="1" id="alphaValue" onchange="changeAlpha2()"> </td> </tr> <tr> <td>Mix</td> <td> <input type="range" min="0.0" max="1.0" step="0.01" value="0.5" id="mix" oninput="changeMix()"> <input type="text" size="5" value="0.5" id="mixValue" onchange="changeMix2()"> </td> </tr> <tr> <td>Model Silhouette</td> </tr> <tr> <td>Color</td> <td> <select id="sColor" onchange="changeSColor()"> <option value="Red">Red</option> <option value="Green">Green</option> <option value="Blue">Blue</option> <option value="Yellow">Yellow</option> <option value="Gray">Gray</option> </select> </td> </tr> <tr> <td>Alpha</td> <td> <input type="range" min="0.0" max="1.0" step="0.01" value="1" id="sAlpha" oninput="changeSAlpha()"> <input type="text" size="5" value="1" id="sAlphaValue" onchange="changeSAlpha2()"> </td> </tr> <tr> <td>Size</td> <td> <input type="range" min="0.0" max="10.0" step="0.01" value="2" id="size" oninput="changeSSize()"> <input type="text" size="5" value="2" id="sizeValue" onchange="changeSSize2()"> </td> </tr> </tbody> </table> </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { infoBox: false, selectionIndicator: false, shadows: true, shouldAnimate: true, animation: false, //是否显示动画工具 timeline: false, //是否显示时间轴工具 }); var mode = document.getElementById('mode'); //颜色模式 var color = document.getElementById('color'); //填充色 var alpha = document.getElementById('alpha'); //填充色透明度 var mix = document.getElementById('mix'); //混合比例 var sColor = document.getElementById('sColor'); //边框颜色 var sAlpha = document.getElementById('sAlpha'); //边框透明度 var size = document.getElementById('size'); //边框尺寸 function getColorBlendMode(colorBlendMode) { return Cesium.ColorBlendMode[colorBlendMode.toUpperCase()]; } function getColor(colorName, alpha) { const color = Cesium.Color[colorName.toUpperCase()]; //将字符串转为大写 return Cesium.Color.fromAlpha(color, parseFloat(alpha)); } var entity = viewer.entities.add({ name: '飞机', position: Cesium.Cartesian3.fromDegrees(104, 40, 5), model: { uri: './3D格式数据/glb/Cesium_Air.glb', minimumPixelSize: 2, maximumScale: 200, color: getColor(color.value, alpha.value), colorBlendMode: getColorBlendMode(mode.value), colorBlendAmount: parseFloat(mix.value), silhouetteColor: getColor( sColor.value, sAlpha.value ), silhouetteSize: parseFloat(size.value), }, }); viewer.zoomTo(entity); //#region 各种相应事件 //改变colorBlendMode function changeMode() { entity.model.colorBlendMode = getColorBlendMode(mode.value); } //模型颜色 function changeColor() { entity.model.color = getColor(color.value, alpha.value); } //模型颜色透明度滑动条 function changeAlpha() { //拿到滑动条当前值 let modelAlpha = Number(alpha.value); //文本框显示当前值 alphaValue.value = modelAlpha; entity.model.color = getColor(color.value, modelAlpha); } //模型颜色透明度文本框 function changeAlpha2() { let modelAlpha = Number(alphaValue.value); alpha.value = modelAlpha; changeAlpha(); } //混合时颜色强度滑动条 function changeMix() { //拿到滑动条当前值 let modelMix = Number(mix.value); //文本框显示当前值 mixValue.value = modelMix; entity.model.colorBlendAmount = parseFloat(modelMix); } //混合时颜色强度文本框 function changeMix2() { let modelMix = Number(mixValue.value); mix.value = modelMix; changeMix(); } //外轮廓线颜色 function changeSColor() { entity.model.silhouetteColor = getColor(sColor.value, sAlpha.value); } //外轮廓线透明度滑动条 function changeSAlpha() { //拿到滑动条当前值 let silhouetteAlpha = Number(sAlpha.value); //文本框显示当前值 sAlphaValue.value = silhouetteAlpha; entity.model.silhouetteColor = getColor(sColor.value, silhouetteAlpha); } //外轮廓线透明度文本框 function changeSAlpha2() { let silhouetteAlpha = Number(sAlphaValue.value); sAlpha.value = silhouetteAlpha; changeSAlpha(); } //外轮廓线尺寸滑动条 function changeSSize() { let silhouetteSize = Number(size.value); sizeValue.value = silhouetteSize; entity.model.silhouetteSize = parseFloat(silhouetteSize); } //外轮廓线尺寸文本框 function changeSSize2() { let silhouetteSize = Number(sizeValue.value); size.value = silhouetteSize; entity.model.silhouetteSize = parseFloat(silhouetteSize); } //#endregion </script> </body> </html>
(2)结果显示
在实际应用中,3D模型高度是一个绕不开的话题,例如,要在倾斜摄影模型上添加一个旗帜标注,这时添加旗帜标注的位置肯定要以倾斜摄影模型在该位置的高度为准,因此,我们需要获取精确的3D模型高度。在Cesium中获取3D模型高度的方法不止一种,下面举例说明如何通过sampleHeight方法获取较为精确的3D模型高度。
(1)实现代码
5_8_贴合3D模型.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="Clamp a point and label to a model using the sampleHeight function."> <meta name="cesium-sandcastle-labels" content="Showcases"> <title>贴合3D模型</title> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <script type="text/javascript" src="./Build/Cesium/Cesium.js"></script> </head> <body> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> <div id="cesiumContainer" > </div> <script> Cesium.Ion.defaultAccessToken = '你的token'; var viewer = new Cesium.Viewer("cesiumContainer", { infoBox: false, selectionIndicator: false, shadows: true, animation:false, shouldAnimate: true, }); //开启深度检测 viewer.scene.globe.depthTestAgainstTerrain = true; //定义变量 var longitude = 114.40074; var latitude = 30.51978; var range = 0.0001; var duration = 8.0; var cartographic = new Cesium.Cartographic(); //添加模型 var entity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(longitude, latitude), model: { uri: "./3D格式数据/glb/GroundVehicle.glb", }, }); //定位过去 viewer.zoomTo(entity); //添加点和label标签 var point = viewer.entities.add({ position: new Cesium.CallbackProperty(updatePosition, false), point: { pixelSize: 10, color: Cesium.Color.YELLOW, disableDepthTestDistance: Number.POSITIVE_INFINITY, //正无穷大,设置到达距地面多少米后禁用深度测试 }, label: { showBackground: true, font: "18px monospace", horizontalOrigin: Cesium.HorizontalOrigin.LEFT, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(5, 5), }, }); function updatePosition(time) { const offset = (time.secondsOfDay % duration) / duration; cartographic.longitude = Cesium.Math.toRadians((longitude - range + offset * range * 2.0)); cartographic.latitude = Cesium.Math.toRadians(latitude); let height; if (viewer.scene.sampleHeightSupported) { height = viewer.scene.sampleHeight(cartographic); } if (Cesium.defined(height)) { cartographic.height = height; point.label.text = `${Math.abs(height).toFixed(2).toString()} m`; point.label.show = true; } else { cartographic.height = 0.0; point.label.show = false; } return Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude,cartographic.height); } </script> </body> </html>
(2)结果显示
本节将通过CZML结构模拟小车模型按照预设路线进行移动的过程。CZML是一种在Cesium中用来描述动态场景的JSON架构的语言。CZML结构主要用于在运行Cesium的Web浏览器中显示,它描述了线条、点、广告牌、模型和其他图形基元,并指定它们如何随时间变化。正是由于CZML的存在,用户可以简单、方便地构建出众多与时间相关的动态场景。
CZML可以准确地描述值随时间变化的属性,比如,我们想要模拟小车移动,则只要在CZML中定义小车在两个不同时间点的位置,并使用CZML定义的插值算法,就可以准确地在客户端显示小车在这两个时间点之间的位置。下面的实现思路正是基于此的,即首先动态获取小车在某个时间点的位置,并计算在该位置的倾斜摄影模型高度,然后将其赋给小车,使其贴合在3D Tiles模型上来达到预期效果。
(1)实现代码
5_9_小车移动(贴3DTiles).html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>三维模型篇_贴3DTiles</title> <script src="./Build/Cesium/Cesium.js"></script> <link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css"> <style> html, body, #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style> </head> <body> <div id="cesiumContainer"></div> <script> //#region 初始化 Cesium.Ion.defaultAccessToken = '你的token' var viewer = new Cesium.Viewer("cesiumContainer", { timeline: true, animation: true, }); //#endregion var tileset = viewer.scene.primitives.add( new Cesium.Cesium3DTileset({ url: './倾斜摄影/大雁塔3DTiles/tileset.json' })); viewer.zoomTo(tileset); /* const entity2 = viewer.entities.add({ position:new Cesium.Cartesian3 ( -1715529.0193483282 , 4993383.694752825 , 3566984.256377016 ), model: { uri: "./3D格式数据/glb/GroundVehicle.glb", }, }); viewer.zoomTo(entity2); */ //定义CZML结构 var czml = [ { "id": "document", "version": "1.0", "clock": { "interval": "2022-04-14T15:18:00Z/2022-04-14T15:18:15Z", "currentTime": "2022-04-14T15:18:00Z", } }, { "id": "CesiumMilkTruck", "model": { "gltf": "./RasterImage/CZML/CesiumMilkTruck/CesiumMilkTruck.glb" }, "position": { "cartesian": [ "2022-04-14T15:18:00Z", -1715306.5175099864, 4993455.496718319, 3566986.1689425386, "2022-04-14T15:18:12Z", -1715529.0193483282, 4993383.694752825, 3566984.256377016, "2022-04-14T15:18:15Z", -1715541.2997855775, 4993376.825711799, 3566988.324779788 ] }, }, { "id": "Polyline", "polyline": { "positions": { "cartesian": [ -1715306.5175099864, 4993455.496718319, 3566986.1689425386, -1715529.0193483282, 4993383.694752825, 3566984.256377016, -1715541.2997855775, 4993376.825711799, 3566988.324779788 ] }, "material": { "polylineOutline": { "color": { "rgba": [125, 255, 128, 255] }, "outlineWidth": 0 } }, "width": 5, "clampToGround": true } } ] var entity; //获取小车模型 var positionProperty; //获取小车位置属性 //创建CZML实例的Promise var dataSourcePromise = Cesium.CzmlDataSource.load(czml); viewer.dataSources.add(dataSourcePromise).then(function (dataSource) { //获取小车模型 entity = dataSource.entities.getById("CesiumMilkTruck"); //设定小车朝向 entity.orientation = new Cesium.VelocityOrientationProperty(entity.position);//设置模型朝向按照指定路线 //获取小车位置 positionProperty = entity.position; }); //渲染监听 function start() { //开启动画 viewer.clock.shouldAnimate = true; //渲染监听模型实时位置、高度并贴在3DTiles之上 viewer.scene.postRender.addEventListener(function () { var position = positionProperty.getValue(viewer.clock.currentTime); console.log(position); entity.position = viewer.scene.clampToHeight(position, [entity]); entity.position = position; }); viewer.zoomTo(entity); } tileset.initialTilesLoaded.addEventListener(start);//3D Tiles渲染完成后调用 //#endregion </script> </body> </html>
(2)结果显示
参考资料:
[1] 郭明强. 《WebGIS之Cesium三维软件开发》; 2023-04-01 [accessed 2024-01-26].
[2] githubhanjunjun. Cesium项目应用(1)-3dtiles单体化。附带源码下载; 2020 [accessed 2024-01-26].
[3] Gisleung. Cesium实现建筑物单体化(分栋分层); 2022-05-31 [accessed 2024-01-26].
[4] Cesium实验室. CesiumLab V1.4 分类3dtiles生成(倾斜单体化、楼层房间交互); 2018-12-06 [accessed 2024-01-26].
[5] easyCesium. Cesium 案例分析 --单体化分析(分栋、分层); 2019-05-24 [accessed 2024-01-26].
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。