food/src/views/layout/components/main/planes/swarm.vue

258 lines
8.7 KiB
Vue
Raw Normal View History

<template>
<div class="h-100">
<!-- 地图组件 -->
<map-box ref="mapbox" v-if="swarmReady" :enableShowNofly="true" :enableGuided="true" :enblueScale="!$store.state.app.isWideScreen"
@longPress="handleLongPress" @map-ready="onMapReady">
<template #content>
<div v-show="mapReady">
<SwarmControllerTabs :planes="planeList"/>
</div>
</template>
</map-box>
<!-- 弹出框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="320px" top="30vh" @close="closeCallback"
@open="openCallback">
<!-- 点飞设置弹出框 -->
<template v-if="dialogItem == 'guidedBox'">
<el-form label-position="left">
<el-form-item label="中心纬度" label-width="80px">
<el-input v-model="guidedLonLat.lat" placeholder="请输维度" label="纬度"></el-input>
</el-form-item>
<el-form-item label="中心经度" label-width="80px">
<el-input v-model="guidedLonLat.lon" placeholder="请输经度" label="经度"></el-input>
</el-form-item>
<el-form-item label="中心高度" label-width="80px">
<el-input-number v-model="guidedAlt" label="高度设置"></el-input-number>
<font class="m-l-5"></font>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="dialogVisible = false">关闭</el-button>
<el-button size="medium" type="primary" @click="onSwarmFlyTo">飞至</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import MapBox from '@/components/MapBox'
import SwarmControllerTabs from '@/components/SwarmControllerTabs.vue'
import { waitForMapCanvasReady } from '@/utils'
export default {
name: 'Swarm',
data () {
return {
dialogTitle: '', // 弹出框 标题
dialogItem: '', // 弹出框 项目类型判断
dialogVisible: false, // 弹出框 显隐
guidedLonLat: {}, // 点飞 的经纬度
guidedAlt: '', // 点飞的高度
isReserveGuidedMaker: false, // 关闭指点飞行操作窗口时 标记是否删除图标
mapReady: false, // 地图加载完成后 回调时 设置此值 让地图组件插槽内容 滞后显示
swarmReady: false// 判断有没有选中两架以上集群 用于控制地图组件渲染
}
},
components: {
MapBox,
SwarmControllerTabs
},
computed: {
// 选中的集群控制飞机s
planeList () {
const allPlanes = this.$store.state.airList
const idArr = this.$store.state.app.swarmIdArr
return allPlanes.filter(plane => idArr.includes(plane.id))
},
// 过滤出所有飞机状态列表s
planeStatus () {
return this.planeList.map(plane => plane.planeState)
},
/**
* @description: 侧边栏显隐
*/
isCollapse () {
return this.$store.state.app.isCollapse
}
},
created () {
// 集群控制至少需要2架飞机
if (this.$store.state.app.swarmIdArr.length < 2) {
this.$router.replace('/register/index')// 如果集群控制飞机数量小于2架 则跳转列表页
} else {
this.swarmReady = true // 只有当数据条件满足才渲染 <map-box>
}
},
methods: {
/** 弹出框 关闭事件回调 */
closeCallback () {
if (this.dialogItem === 'guidedBox' && this.isReserveGuidedMaker === false) { // 关闭点飞窗口时
this.$refs.mapbox.delGuidedMarker()// 删除所有点飞的地图标记
}
this.dialogVisible = false
this.dialogItem = ''
},
/** 弹出框 打开事件回调 */
openCallback () {
},
// 地图长按事件 记录地图经纬度
handleLongPress (lonLat) {
this.isReserveGuidedMaker = false
this.dialogTitle = '集群指点'
this.dialogVisible = true
this.dialogItem = 'guidedBox'
this.guidedLonLat = lonLat // 点击的新位置中心点
// 获取所有飞机的高度并求平均
const validHeights = this.planeList.map(p => {
const pos = p?.planeState?.position
const last = Array.isArray(pos) && pos.length > 0 ? pos[pos.length - 1] : null
return Array.isArray(last) ? last[2] || 0 : 0
}).filter(h => typeof h === 'number')
const avgAlt = validHeights.length
? (validHeights.reduce((sum, h) => sum + h, 0) / validHeights.length).toFixed(1)
: 0
this.guidedAlt = Number(avgAlt)
},
// 集群指点飞行 保持所有飞机拓扑关系飞到指定位置
onSwarmFlyTo () {
const targetLon = Number(this.guidedLonLat.lon)
const targetLat = Number(this.guidedLonLat.lat)
const targetAlt = Number(this.guidedAlt)
if (
isNaN(targetLon) || isNaN(targetLat) || isNaN(targetAlt) ||
targetLon < -180 || targetLon > 180 ||
targetLat < -90 || targetLat > 90
) {
this.$message.warning('请输入有效的经纬度(经度-180~180纬度-90~90和高度')
return
}
// 所有飞机当前中心点
const currentCenter = this.getSwarmCenter()
if (!currentCenter) {
this.$message.error('无法获取飞行器中心位置,检查飞机是否都已定位')
return
}
// 计算每架飞机相对于当前中心的偏移量
const commands = this.planeList.map(p => {
const pos = p?.planeState?.position
const last = Array.isArray(pos) && pos.length > 0 ? pos[pos.length - 1] : null
if (!Array.isArray(last)) return null
const offsetLon = last[0] - currentCenter.lon
const offsetLat = last[1] - currentCenter.lat
const offsetAlt = last[2] - currentCenter.alt
const newLon = targetLon + offsetLon
const newLat = targetLat + offsetLat
const newAlt = targetAlt + offsetAlt
return {
id: p.id,
cmd: `{guidedMode:{lon:${newLon.toFixed(7)},lat:${newLat.toFixed(7)},alt:${newAlt.toFixed(1)}}}`
}
}).filter(Boolean)
// 发送控制指令
commands.forEach(({ id, cmd }) => {
this.publishFun(cmd, id)
})
this.isReserveGuidedMaker = true
this.dialogVisible = false
},
// 获取集群重心点
getSwarmCenter () {
const positions = this.planeList.map(p => {
const pos = p?.planeState?.position
const last = Array.isArray(pos) && pos.length > 0 ? pos[pos.length - 1] : null
return Array.isArray(last) ? last : null
}).filter(Boolean)
if (!positions.length) return null
const sum = positions.reduce((acc, pos) => {
acc.lon += pos[0]
acc.lat += pos[1]
acc.alt += pos[2] || 0
return acc
}, { lon: 0, lat: 0, alt: 0 })
const count = positions.length
return {
lon: sum.lon / count,
lat: sum.lat / count,
alt: sum.alt / count
}
},
// 地图组件回调地图加载完成后 执行
onMapReady () {
this.mapReady = true // 标记地图加载完成
this.makePlanes(this.planeList)
},
/**
* @description: 创建飞机图标
*/
makePlanes (planes) {
this.$refs.mapbox.removePlanes()// 先清除画布上现有的飞机
planes.forEach((plane, index) => { // 创建所有飞机
let planeDefaultLonLat
if (localStorage.getItem(plane.name) !== null) { // 从本地缓存 拿飞机得初始位置
planeDefaultLonLat = JSON.parse(localStorage.getItem(plane.name))
plane.lon = planeDefaultLonLat.lon
plane.lat = planeDefaultLonLat.lat
} else {
// 缺省 给经纬度 小数点后随机第五位 即有个10米左右的随机距离
plane.lon = 100 + Number((Math.random() * 0.01).toFixed(5))
plane.lat = 40 + Number((Math.random() * 0.01).toFixed(5))
}
this.$refs.mapbox.makePlane(plane, index)
})
}
},
watch: {
/**
* @description: 飞机列表更新时候 更新地图
*/
planeList: {
async handler () {
try {
// 等待地图画布准备好
await waitForMapCanvasReady(this.$refs.mapbox.map)
// 画布准备好后执行你自己的逻辑
this.onMapReady()
} catch (err) {
console.debug('等待地图画布准备超时', err)
}
},
immediate: true
},
// 实时更新所有状态 到对应飞机弹出框
planeStatus: {
handler (val) {
val.forEach((stateObj, index) => {
stateObj.name = this.planeList[index].name // 保留飞机名称
this.$refs.mapbox.updatePopupContent(stateObj, index)
})
},
deep: true
},
/**
* @description: 侧边栏显隐
*/
isCollapse () {
return this.$store.state.app.isCollapse
}
}
}
</script>
<style lang="scss" scoped></style>