前言
之前写过一篇 vue2.6内嵌超图11i发布的场景 的文章;当初的背景是前端使用的vue2,所以使用了超图发布在npm上的vue2对应的 @supermap/vue-iclient3d-webgl,这个前端依赖是超图5年前发布的,本地搭建的超图iserver是11i的,但vue2版本依赖的@supermap/vue-iclient3d-webgl只支持到10i,所以当时魔搭了一下,先装@supermap/iclient3d-vue-for-webgl,将包内public地下的cesium文件夹拷贝到项目public下,然后uninstall,再安装@supermap/vue-iclient3d-webgl,这样就解决了vue2项目内嵌超图11i发布的场景的问题
问题
超图官网示例展示的飞行案例是基于fpf文件的,但fpf文件是基于超图的idesktop手动打点(用过),或者基于路线生成(没用过)的。
项目做出来后总不能让用户自己去idesktop上搭建场景 →打点→导出fpf文件→上传到项目→web端漫游吧?所以就有了这篇文章!
思路
既然idesktop能创建并导出fpf,我只需要记录每次点击的场景的x,y,z,方位角,倾斜角,然后创建站点,添加到RouteCollection,再通过flymanager调用就行了,也省的生成文件再加载到内存中了;于是就搜索相关的问题,在超图官网问答版块找到了一篇类似的问题,直指优快云,但示例代码需要积分下载,就PASS掉了。
解决思路
这里笼统说下,省的各位同行浪费时间
web场景中打点 →项目内置一个空的fpf文件 →将打点数据给到RouteCollection→调用flyManager.play()
详细步骤
重点:如果你参考我之前的文章引用的@supermap/vue-iclient3d-webgl,那么你需要先安装超图发布的iclient3d-webgl
npm install @supermap/iclient3d-webgl
然后从下载的包中将Cesium拷贝到你的前端项目的public下,替换到原来从@supermap/vue-iclient3d-webgl拷贝的Cesium包
原因:@supermap/vue-iclient3d-webgl是5年前发布的,依赖的cesium比较旧,导致漫游相机视角异常和飞行结束后场景假死状态
准备一个不包含任何站点的fpf文件内嵌在项目中,因为超图官网说
我的fpf文件
<?xml version="1.0" encoding="UTF-8"?>
<SceneRoute xmlns="http://www.supermap.com.cn/ugc60">
<route name="飞行路线_1" speed="120" lineType="0" showroutestop="True" showrouteline="True"
altitudefree="False" headingfree="False" tiltfree="False" flycircle="False" alongline="True">
<style>
<geostyle3d>
<linecolor>RGBA(147,112,219,100)</linecolor>
<linewidth>2</linewidth>
<altitudeMode>Absolute</altitudeMode>
<bottomAltitude>0.00</bottomAltitude>
</geostyle3d>
</style>
</route>
</SceneRoute>
然后在你的场景中添加绘制marker的函数,进入页面时,初始化marker函数,当点击添加站点时,调用handlerMarker.activate()激活打点功能;代码如下:
// 标注工具初始化
initMarker() {
//标注相关
common.initHandler('Marker ', Cesium.ClampMode.S3mModel)
handlerMarker.activeEvt.addEventListener(function (isActive) {
if (isActive == true) {
viewer.enableCursorStyle = false;
viewer._element.style.cursor = '';
document.body.classList.remove('drawCur');
document.body.classList.add('drawCur');
} else {
viewer.enableCursorStyle = true;
document.body.classList.remove('drawCur');
}
});
handlerMarker.movingEvt.addEventListener((windowPosition) => {
tooltip.showAt(windowPosition, '<p>点击绘制地标</p>');
});
// 标绘结束后的回调,这里将每次点击的点位数据保存到vue中,用于后续添加飞行站点
handlerMarker.drawEvt.addEventListener(result => this.drawListener(result));
},
// 场景新增漫游节点,激活marker功能
addNewPoint() {
// 激活marker
handlerMarker.activate()
},
将flyManager定义到vue之外,因为官网说了
初始化flyManager 示例代码
<script>
// 定义在vue之外
var flyManager;
export default {
data() {
return {
}
},
mounted() {
initFlyManager();
},
methods: {
//漫游相关
initFlyManager() {
let routes = new Cesium.RouteCollection(viewer.entities);
let fpfUrl = '/fpf/exportFlyManager.fpf';
routes.fromFile(fpfUrl);
//初始化飞行管理
flyManager = new Cesium.FlyManager({
scene: scene,
routes: routes
});
flyManager.readyPromise.then(() => { // 飞行路线就绪
var currentRoute = flyManager.currentRoute;
currentRoute.isLineVisible = false;//不显示路线
currentRoute.isStopVisible = false;//不显示站点标记
})
// 飞行过程中,始终看向下一个站点
flyManager.stopArrived.addEventListener(function (routeStop) {
flyManager.viewToStop(routeStop)
});
},
}
}
</script>
之后在你的vue页面添加用于保存站点的pointObj属性,在 handlerMarker的回调函数中,将经度、纬度、高程、方位角、倾斜角属性保存在pointObj中;
代码如下:
//标注处理逻辑
drawListener(result) {
let that = this;
tooltip.setVisible(false);
if (result && result.object.position) {
let descriptionEntity = that.createEntity(null, that.note,result.object.position, result.object.image, result.object.scale);
viewer.entities.add(descriptionEntity);
// 飞行站点添加逻辑
if (that.pageVal === 0) {
console.log(result)
// 将鼠标当前点坐标转化成经纬度
var cartographic = Cesium.Cartographic.fromCartesian(result.object.position);
that.pointObj.longitude = Cesium.Math.toDegrees(cartographic.longitude);
that.pointObj.latitude = Cesium.Math.toDegrees(cartographic.latitude);
that.pointObj.altitude = cartographic.height + 2;//因为点击的是路面,+2用于模拟行车高度视角
// 获取当前相机的朝向信息
const headingRadians = viewer.camera.heading; // 弧度
const pitchRadians = viewer.camera.pitch; // 弧度
// 将弧度转换为度数
that.pointObj.heading = Cesium.Math.toDegrees(headingRadians);
that.pointObj.tilt = Cesium.Math.toDegrees(pitchRadians) + 90;
// 给树节点添加子节点
that.$refs.flyRouteTree.data[0].children.push({
label: '站点' + (that.$refs.flyRouteTree.data[0].children.length + 1),
id: descriptionEntity.id,// 将新增的maker自动生成的uuid 绑定到树上,便于后续删除
data: {...that.pointObj}
})
// 最后也要关闭标注工具
handlerMarker.clear();
handlerMarker.deactivate();
}
}
},
//创建entity 标注
createEntity(id, title, position, image, scale) {
return new Cesium.Entity({
id: id,
position: position,
label: {
text: title, // 标注的文本
font: '14px', // 字体样式
pixelOffset: new Cesium.Cartesian2(0, -30), // 文本偏移量,负值将文本向上移动
disableDepthTestDistance: Number.POSITIVE_INFINITY // 防止标签被地形遮挡
},
billboard: {
image: image,
scale: scale,
},
});
},
注意:添加站点至少要3个站点,因为两个站点漫游的话, 导致飞行时相机视角转圈;
重点:每次新建飞行路线时,需要将上一次添加的站点清空,超图官网文档removeStop老是不行,也没有clear()
点击开始漫游时遍历树节点的数据,将站点添加到stopCollection中;代码如下:
// 新建的飞行路径的漫游按钮触发
flyStart() {
if (this.newFlyRoute[0].children.length === 0) {
this.$message.error('请先添加漫游站点');
return;
}
if (this.newFlyRoute[0].children.length < 3) {
this.$message.error('漫游节点太少,无法漫游');
return;
}
// 添加站点数据到flyManager.stopCollection
this.setFlyStopData(this.newFlyRoute);
flyManager.readyPromise.then(() => { // 飞行路线就绪
if (flyManager._flyStatus === 2) {// 防止重复点击按钮
flyManager.play()
}
})
},
// 封装站点数据到flymanager.stopCollection
setFlyStopData(collection) {
// 上述的空的fpf获取的
let routes = flyManager.routes.get(0);
//新建飞行站点时,如果flymanager.stopCollection为空时,则添加路线名称;
// 保存后再新建飞行路线时, 将添加到场景中的站点和路线清空
if (routes.stopCount !== 0) {
routes.clear()// 这个函数在超图官网没有,打断点看cesium源码看到的
routes._addedStops = [];// 这个属性在超图官网没有,打断点看cesium源码看到的
routes._stopMarkCollection = []// 这个属性在超图官网没有,打断点看cesium源码看到的
viewer.entities.removeAll()
}
let collectionData = collection[0];
for (let i = 0; i < collectionData.children.length; i++) {
let single = collectionData.children[i];
// 创建飞行站点
let routeStop = new Cesium.RouteStop({
index: i,
duration: 5,// 两个站点之间的飞行时间设置为固定的5秒
point: Cesium.Cartesian3.fromDegrees(single.data.longitude, single.data.latitude, single.data.altitude),
stopName: single.label,
heading: single.data.heading,
tilt: single.data.tilt,
});
//添加站点
routes.addStop(routeStop)
}
},
通过上述代码,就将idesktop上创建飞行路线的功能嵌入到了web端;飞行暂停啊,停止啊之类的功能直接调用flyManager.pause(),flyManager.stop()就行了