cesium.js三维城市建筑物巡视开发
一、简介
这个项目基于cesium.js和已有模型,在地图上搭建起了一座完整的建筑物,并实现了初步的用户交互,可以查看单独每一层楼内的线路结构。
项目链接https://gitee.com/hiMyon/city3d/tree/master
二、实现过程
首先参照项目链接中的readme安装nodejs和http-server,搭建环境,然后进入下一步。
(一)加载cesium.js视图
var viewer = new Cesium.Viewer('cesiumContainer', {
shadows: false,
shouldAnimate: true,
animation:false, //是否显示动画控件
homeButton:true, //是否显示home键
//geocoder:false, //是否显示地名查找控件 如果设置为true,则无法查询
baseLayerPicker:true, //是否显示图层选择控件
timeline:false, //是否显示时间线控件
fullscreenButton:true, //是否全屏显示
scene3DOnly:true, //如果设置为true,则所有几何图形以3D模式绘制以节约GPU资源
infoBox:true, //是否显示点击要素之后显示的信息
sceneModePicker:false, //是否显示投影方式控件 三维/二维
navigationInstructionsInitiallyVisible:false,
navigationHelpButton:false, //是否显示帮助信息控件
selectionIndicator:false, //是否显示指示器组件
imageryProvider : new Cesium.UrlTemplateImageryProvider({
url: "http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
})
});
viewer._cesiumWidget._creditContainer.style.display = "none";
viewer.scene.globe.enableLighting = false;
(二)加载模型
var entity;
var index = 1;
//加载四层地下
for (var i = 1; i < 5; i+=1) {
var model = database.assets[i];
index = addModel(model, "underground-", index);
}
index = 1;
//加载四层裙楼
for (var i = 5; i < 9; i+=1) {
var model = database.assets[i];
index = addModel(model, "podium-", index);
}
index = 5;
//加载A栋
for (var i = 9; i < 15; i+=1) {
var model = database.assets[i];
index = addModel(model, "A-", index);
}
index = 5;
//加载B栋
for (var i = 15; i < 17; i+=1) {
var model = database.assets[i];
index = addModel(model, "B-", index);
}
index = 5;
//加载C栋
for (var i = 17; i < 19; i+=1) {
var model = database.assets[i];
index = addModel(model, "C-", index);
}
viewer.trackedEntity = viewer.entities.getById('podium-1');
加载一个模型需要得到以下信息:
- 模型本地资源的url
- 模型在地图上的哪个显示图层上
- 模型在地图上的位置,可以用经纬度和地面高度表示
- 模型的地图上旋转的大小
- 模型在地图上放缩的尺度
为了方便,我们将模型的以上信息统一存储在json文件中,比如地下一层可以这样表示:
{
"model_name": "building_ug1",
"level": 2,
"position": [113.34700, 23.00578, 18],
"rotation": [55, 0, 0],
"scale": 1,
},
然后就可以在代码中进行加载:
//position三个参数分别是经度、纬度和离地面高度
var position = Cesium.Cartesian3.fromDegrees(model.position[0], model.position[1], model.position[2] + j * height);
var hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(model.rotation[0]), Cesium.Math.toRadians(model.rotation[1]), Cesium.Math.toRadians(model.rotation[1]));
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
//某个模型可能同时表示好几层,但是id和name要将它们区分开来
entity = viewer.entities.add({
id: prefix + index,
name: prefix + index,
position: position,
orientation: orientation,
model: {
uri: "models/glb/" + model.model_name + ".glb",
scale: model.scale
}
});
cesium.js加载模型的方式是调用viewer.entities.add方法,传入该模型的位置、尺度、旋转等信息,为了区分每一层楼,在加入时为它们设置一个唯一的id,name是模型在地图上显示的信息,可以设置和id相同。
每层楼中有不同的线路图,这些线路图同样是一些模型,可以写在json中,这里我模仿了3d-tiles的结构,将每层楼的线路模型作为该层楼模型的子结构,这样,如果给地下一层加入火线,其新表示为:
{
"model_name": "building_ug1",
"level": 2,
"position": [113.34700, 23.00578, 18],
"rotation": [55, 0, 0],
"scale": 1,
"fire": [{
"model_name": "fire_fUg1",
"level": 2,
"position": [113.35070, 23.00815, 19],
"rotation": [55, 0, 0],
"scale": 1
}],
},
电线和水线加入的方式与火线相同。
现在考虑更复杂一点的情况,比如有一个模型building_A5-10,它是一个只有一层的模型,但是我们自下往上重复搭建六次则可以搭出5到10层,这六个实体在地图上的区别除了名字和id外就只有离地高度了,这种情况如果在json中用六个结构体来表示就过于冗余了,只需在结构体中增加一个变量times,表示这个模型被重复搭建几次;顺带一提,还有一些模型如fire_fB15-21odd,它表示B栋15到21层的奇数层,所以从下往上搭建时要搭一层跳过一层,通过给结构体增加一个height变量表示每一层的高度,将这个值设置为正常高度的两倍即可。
所有的火线和电线都已经加到json数据中,由于水线模型很难看清以及判断摆放的位置,我只在地下四层中放置了水线,之后可以再修改。
(三)初步用户交互
这里的交互指的是用户可以单独查看每一层楼和楼内部的线路,以及控制线路的显示与否,操作方法在gitee项目的readme中有介绍。
实现其实也挺简单,就是给handler添加action,比如添加鼠标双击动作的响应:
var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function(e){
var pick = viewer.scene.pick(e.position);
if (Cesium.defined(pick)){
//alert('选中:'+ pick.id.name);
console.log('选中' + pick.id.id);
choosing_one = pick.id;
viewer.entities.getById(pick.id.id + '-fire').show = true;
for (let id of entityId_array) {
var entity = viewer.entities.getById(id);
entity.show = false;
}
pick.id.show = true;
if (show_fire) {
viewer.entities.getById(pick.id.id + '-fire').show = true;
}
if (show_electricity) {
viewer.entities.getById(pick.id.id + '-electricity').show = true;
}
if (show_water) {
viewer.entities.getById(pick.id.id + '-water').show = true;
}
viewer.trackedEntity = pick.id;
}
},Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
根据鼠标的点击位置得到被点中的模型实体,即pick,接着隐藏其他实体,切换到该层视角上,然后根据条件判断是否显示该层的某一种线路。