You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
GCSGUI/src/components/page/RouteManagePage.vue

517 lines
16 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script setup lang="ts">
import SceneViewer from "@/components/SceneViewer.vue";
import BottomBar from "@/components/BottomBar.vue";
import {ArrowBackOutline,ArrowForwardOutline,Save,Add, SaveOutline} from "@vicons/ionicons5";
import {nextTick, onBeforeUnmount, onMounted, ref} from "vue";
import {darkTheme, useMessage} from "naive-ui";
import {route,route2,route3} from "@/assets/js/testData.ts";
import {useRouteStore} from "@/store/RouteStore.ts";
import {Airline} from "@/types/entityoptions.ts";
import {newAirline, newAirlinePt} from "@/assets/js/TypeInitial.ts";
import * as echarts from "echarts";
import {EChartsType} from "echarts";
import {transLonlat2Car3} from "@/utils/map/geocomputation.ts";
import {drawEcharts_RouteDetection, profileAnalyse} from "@/utils/map/SpatialAnalysis.ts";
import RouteManageViewer from "@/assets/js/RouteManageViewer.js";
import {query_surface_forecast, query_upper_forecast, requireWeatherLevel} from "@/assets/js/weatherRequest.ts";
import {setChartOptions} from "@/assets/js/weatherCharts.ts";
import type { NotificationType } from 'naive-ui'
import { useNotification } from 'naive-ui'
import {useStaticStore} from "@/store/staticOptions";
import {useRouter} from "vue-router";
let myChart: EChartsType = undefined
let weatherChart: EChartsType = undefined
let routeStore = useRouteStore()
let selectedRouteCode = ref(null)
let selectedLevel = ref(null)
let showPtList = ref(true)
let selectedRoute = ref<Airline>(newAirline())
let routesListRef = ref<{ value: string|number; label: string}[]>(null)
let routeViewer = null
let routePtNumber = ref(1)
let uiMsg = useMessage()
let nowTime = ref('')
let timer = null
let PtOptions = [
{label: '删除航点', key: 'deletePt'},
{label: '插入航点', key: 'insertPt'},
]
let routeCode = [
{
label: "任务航线1",
value: 1,
},{
label: "任务航线2",
value: 2,
},{
label: "任务航线3",
value: 3,
},{
label: "任务航线4",
value: 4,
},{
label: "任务航线5",
value: 5,
}
]
let levelOptions = ref<any[]>()
let sStore = useStaticStore();
const router = useRouter()
const notification = useNotification()
const notify = (type: NotificationType, title: string, detail: string) =>{
notification[type]({
title: title,
meta: detail,
duration: 3000,
closable: true,
keepAliveOnHover: true
})
}
onBeforeUnmount(()=>{
clearInterval(timer)
})
onMounted(()=>{
routeViewer = new RouteManageViewer(window.viewer)
myChart = echarts.init(document.getElementById('echarts-profile'),'dark')
weatherChart = echarts.init(document.getElementById('weatherChart'),'dark')
timer = setInterval(getTimesInterval, 1000);
routeStore.addRoute(route)
routeStore.addRoute(route2)
routeStore.addRoute(route3)
routesListRef.value = routeStore.flyRoute.map((route)=> {
return {
value: route.unicode,
label: route.name
}
})
requireWeatherLevel().then(res=>{
console.log(res)
// 查询失败
if(!res.data.success){
notify('error', '气象信息查询失败', res.data.message)
return
}
levelOptions.value = res.data.data.levelsAvailableList[0].pressureLevels.map((level,index)=>{
return {
value: level,
label: level+'hPa/ '+ res.data.data.levelsAvailableList[0].heightLevels[index] + 'm'
}
})
levelOptions.value.reverse()
levelOptions.value.unshift({value: -1, label: '地面'})
selectedLevel.value = -1
})
})
function getTimesInterval() {
let now = new Date();
let year = now.getFullYear(); // 年
let month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月
let day = now.getDate().toString().padStart(2, '0'); // 日
let hours = now.getHours().toString().padStart(2, '0'); // 时
let minutes = now.getMinutes().toString().padStart(2, '0'); // 分
let seconds = now.getSeconds().toString().padStart(2, '0'); // 秒
// 格式化时间
nowTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 航线选择回调
function checkRoute(key:number|string){
new Promise((resolve,reject)=>{
selectedRoute.value = routeStore.flyRoute.filter(element => element.unicode === String(key))[0]
if(selectedRoute.value == null) reject('未找到航线')
routeViewer.routeParams.isClose = selectedRoute.value.isClose
window.viewer.flyTo(routeViewer.addAirLine(selectedRoute.value), {duration: 2})
queryWeather(selectedRoute.value.points[0].lon,selectedRoute.value.points[0].lat)
setTimeout(()=>resolve('success'),2500)
}).then((result)=>{
//绘制地形图表
let Cartain3 = transLonlat2Car3(selectedRoute.value.points)
// 计算地形剖面默认采样间隔为1km
let res = profileAnalyse(window.viewer, Cartain3, 1000)
// 计算航线距离数组
let orderArr = []
let hArr = []
Cartain3.forEach((_,index) => {
orderArr.push(index + 1)
hArr.push(selectedRoute.value.points[index].alt)
})
console.log(orderArr,hArr)
// 弹出地形图表
drawEcharts_RouteDetection(myChart, res.distanceArray, res.elevationArray, orderArr, hArr)
}).catch((err)=>{
uiMsg.error(err)
})
}
// 在尾部添加航点
function addPtToTail() {
let newPt = newAirlinePt()
newPt.nPt = selectedRoute.value.points.at(-1).nPt + 1
selectedRoute.value.points.push(newPt)
}
// 保存航点编辑
// 1 重新编辑航点的ch2属性
// 2 刷新航线属性
// 3 将改动更新到store中
function savePt() {
selectedRoute.value.points.forEach((pt,index) => {
pt.ch2 = 3
pt.nPt = index+1 //航点序号从1起
})
selectedRoute.value.points.at(-1).ch2 = 1
selectedRoute.value.PtNum = selectedRoute.value.points.length
routeStore.addRoute(selectedRoute.value)
}
/** 删除或在此前插入航点
*
* @param key 下拉菜单key
* @param index 航点索引
*/
function handlePtSelect(key:any, index:number) {
if(key=='deletePt'){
selectedRoute.value.points.splice(index,1)
selectedRoute.value.points.forEach((pt,i) => {
pt.nPt = i+1
})
showPtList.value = false
nextTick(()=>{
showPtList.value = true
})
}
else if(key=='insertPt'){
selectedRoute.value.points.forEach((pt,i) => {
if(i>=index) pt.nPt = i + 2
})
let newPt = newAirlinePt()
newPt.nPt = index + 1
showPtList.value = false
nextTick(()=>{
selectedRoute.value.points.splice(index,0,newPt)
showPtList.value = true
})
}
}
//查询上一航点的气象信息
function prevPtWeather() {
routePtNumber.value -= 1
window.viewer.flyTo(window.viewer.entities.getById(selectedRoute.value.unicode + "-航点" + (routePtNumber.value).toString()),
{duration: 1})
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
if(lat && lon){
queryWeather(lon,lat, selectedLevel.value)
}
}
//查询下一航点的气象信息
function nextPtWeather() {
routePtNumber.value += 1
window.viewer.flyTo(window.viewer.entities.getById(selectedRoute.value.unicode + "-航点" + (routePtNumber.value).toString()),
{duration: 1})
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
if(lat && lon){
queryWeather(lon,lat, selectedLevel.value)
}
}
function queryWeather(lon:number,lat:number,level?: number) {
let response : Promise<any>
if(level==null|| level==-1){ //地面
response = query_surface_forecast(lon,lat)
}else{
response = query_upper_forecast(lon,lat, level)
}
response.then(res=>{
// 查询失败
if(!res.data.success){
notify('error', '气象信息查询失败', res.data.message)
return
}
let weatherData = res.data.data
let timeArr = weatherData.time.map(item=>{
let i = item.indexOf('T')
return item.slice(i+1,i+3) + '时'
})
if(!Reflect.has(weatherData,'precip')){ //高空数据无precip(降水)属性需添加数组并补0
weatherData.precip = Array.from({length:timeArr.length},()=>0)
}
setChartOptions(weatherChart, timeArr.slice(0,8),weatherData.temp.slice(0,8),weatherData.windSpeed.slice(0,8),
weatherData.wind360.slice(0,8), weatherData.precip.slice(0,8),weatherData.humidity.slice(0,8))
}).catch(e=>{
console.log()
uiMsg.error('气象信息查询失败:' + e.message,{duration:3000, keepAliveOnHover: true})
})
}
function levelChanged(key: string | number) {
selectedLevel.value = key
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
if(lat && lon){
queryWeather(lon,lat, Number(key))
}
}
/**
* 将航线信息发送给QT端每次发送一条航线
* @param routeUnicode 航线唯一码
*/
function sendRouteToQT(routeUnicode: string|number){
let selectedRoute = routeStore.flyRoute.filter(element => element.unicode === String(routeUnicode))[0]
if(selectedRoute){
if(sStore.webskt.ws?.readyState == WebSocket.OPEN){
console.log(selectedRoute)
sStore.webskt.ws.send(JSON.stringify(selectedRoute))
uiMsg.info('航线' + selectedRoute.name + '已下发')
}else {
uiMsg.info('请先开启WebSocket')
}
}else
uiMsg.warning('未找到相应的航线')
}
function delRoute(routeUnicode: string|number){
}
function backToHomePage(){
router.push({name: 'Home'})
}
</script>
<template>
<n-config-provider :theme="darkTheme">
<div style="display: flex;">
<div id="left">
<n-layout style="height: 100%;">
<n-layout-header :bordered="true">
<n-popover trigger="hover">
<template #trigger>
<n-button quaternary type="success" size="large" @click="backToHomePage">
<template #icon>
<n-icon><ArrowBackOutline/></n-icon>
</template>
</n-button>
</template>
返回
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<n-button quaternary type="success" size="large">
<template #icon>
<n-icon><Save/></n-icon>
</template>
</n-button>
</template>
保存编辑
</n-popover>
</n-layout-header>
<n-layout-content>
<n-flex vertical style="margin-left: 1.2rem; margin-top: .5rem;overflow: hidden">
<n-ellipsis><h3>航线列表</h3></n-ellipsis>
<n-radio-group v-model:value="selectedRouteCode" name="radiogroup" @update:value="checkRoute">
<n-scrollbar style="height: 20vh">
<n-space vertical>
<n-space v-for="r in routesListRef" justify="space-between"
style="margin-right: 2rem; border-bottom: #383737 1px solid">
<n-radio :key="r.value" :value="r.value">
{{ r.label }}
</n-radio>
<n-button-group>
<n-button secondary size="small" type="info" style="height: 1.3rem" @click="sendRouteToQT(r.value)">下发</n-button>
<n-button secondary size="small" type="error" style="height: 1.3rem; margin-bottom: 4px" @click="delRoute(r.value)">删除</n-button>
</n-button-group>
</n-space>
</n-space>
</n-scrollbar>
</n-radio-group>
<n-divider style="margin: .2rem -.5rem"></n-divider>
<n-space vertical style="margin: .2rem 1rem">
<n-space>
<n-space>
<n-ellipsis>开闭</n-ellipsis>
<n-switch v-model:value="selectedRoute.isClose" size="small">
<template #checked>闭</template>
<template #unchecked>开</template>
</n-switch>
</n-space>
<n-space style="margin-left: .5rem">
<n-ellipsis>航点数</n-ellipsis>
<n-input-number :show-button="false" disabled size="tiny" style="width: 5rem"
v-model:value="selectedRoute.PtNum"></n-input-number>
</n-space>
</n-space>
<n-space>
<n-ellipsis style="width: 4rem">航线名称</n-ellipsis>
<n-input size="tiny" style="width:10rem" v-model:value="selectedRoute.name"></n-input>
</n-space>
<n-space>
<n-ellipsis style="width: 4rem">航线类型</n-ellipsis>
<n-select style="width:10rem" size="tiny" v-model:value="selectedRoute.code" :options="routeCode"/>
</n-space>
</n-space>
<n-divider style="margin: .2rem -.5rem"></n-divider>
<n-ellipsis><h3>地形剖面图</h3></n-ellipsis>
<n-space justify="space-evenly">
<n-ellipsis>采样间隔</n-ellipsis>
<n-input-number size="tiny" placeholder="默认1000米"
style="width:8vw;" :min="100" :step="100">
</n-input-number>
<n-button type="success" size="tiny">重新绘图</n-button>
</n-space>
<div id="echarts-profile">
<!-- Echarts地形剖面绘图区 -->
</div>
</n-flex>
</n-layout-content>
</n-layout>
</div>
<div id="map2">
<SceneViewer id="scene-viewer-route"></SceneViewer>
<BottomBar></BottomBar>
</div>
<div id="right">
<n-layout style="height: 100%">
<n-layout-header :bordered="true">
<n-space justify="center">
<n-button quaternary type="default" size="large"><h4>航点详情 {{'— '+selectedRoute.name}}</h4></n-button>
</n-space>
</n-layout-header>
<n-layout-content>
<n-flex vertical >
<n-space justify="space-around" style="margin-top:.5rem;">
<n-ellipsis style="margin-left:.2rem; width: 2rem">序号</n-ellipsis>
<n-ellipsis style="width: 5.5rem">经度</n-ellipsis>
<n-ellipsis style="width: 5.5rem">纬度</n-ellipsis>
<n-ellipsis style="width: 3rem">高度</n-ellipsis>
</n-space>
<n-scrollbar style="height: 30vh">
<div v-if="selectedRoute.points.length>0 && showPtList" >
<n-space justify="space-around" style="margin: .2rem 0" v-for="(pt,index) in selectedRoute.points" >
<n-dropdown trigger="hover" size="small" placement="top-start" :show-arrow="true"
:options="PtOptions" @select="handlePtSelect($event,index)">
<n-button secondary type="info" size="tiny" style="margin-left:.2rem; width: 1.7rem">{{ pt.nPt }}</n-button>
</n-dropdown>
<n-input-number size="tiny" v-model:value="pt.lon" :show-button="false" style="width: 5.5rem"
placeholder="经度" max="180" min="-180"></n-input-number>
<n-input-number size="tiny" v-model:value="pt.lat" :show-button="false" style="width: 5.5rem"
placeholder="纬度" max="90" min="-90"></n-input-number>
<n-input-number size="tiny" v-model:value="pt.alt" :show-button="false" style="width: 3rem"
placeholder="高度" max="12000" min="-500"></n-input-number>
</n-space>
<n-space justify="center" style="margin: .5rem 0">
<n-button type="info" size="small" @click="addPtToTail">
<template #icon>
<Add/>
</template>
添加点
</n-button>
<n-button type="info" size="small" @click="savePt">
<template #icon>
<SaveOutline/>
</template>
保存编辑
</n-button>
</n-space>
</div>
<n-space v-else justify="space-around">
<n-empty style="margin-top:3rem " description="无内容">
</n-empty>
</n-space>
</n-scrollbar>
</n-flex>
<n-divider style="margin: .2rem -.5rem"></n-divider>
<n-flex vertical>
<n-row style="margin-left: 1rem; margin-top: .5rem;"><n-ellipsis><h3>气象信息</h3></n-ellipsis></n-row>
<n-space justify="space-around" class="routePtSelector">
<n-button size="small" @click="prevPtWeather" :disabled="routePtNumber<=1">
<template #icon><ArrowBackOutline/></template>
</n-button>
<n-ellipsis>航点 {{routePtNumber}}</n-ellipsis>
<n-button size="small" @click="nextPtWeather" :disabled="selectedRoute.points.length<=routePtNumber">
<template #icon><ArrowForwardOutline/></template>
</n-button>
</n-space>
<n-space justify="center">
<n-ellipsis>{{nowTime}}</n-ellipsis>
<n-select style="width: 10rem" size="tiny" :options="levelOptions" v-model:value="selectedLevel"
@update:value="levelChanged" placeholder="选择高度层"></n-select>
</n-space>
<n-scrollbar x-scrollable id="weatherChart" style="height:41.5vh; width: 100%">
<!-- Echarts -->
</n-scrollbar>
</n-flex>
</n-layout-content>
</n-layout>
</div>
</div>
</n-config-provider>
</template>
<style scoped>
#left{
width: 22vw;
height: 100vh;
}
#map2 {
width: 55vw;
height: 100vh;
position: relative;
}
#right{
width: 23vw;
height: 100vh;
}
#echarts-profile{
margin-left: -1rem;
margin-top: -.5rem;
height: 40vh;
}
#scene-viewer-route {
width: 100%;
height: 100%;
overflow: hidden;
}
.routePtSelector {
width: 110%;
color: #63e2b7;
font-weight: bold;
margin-left: -1rem
}
.routePtSelector:hover{
background: rgb(24, 24, 28);
}
.border{
border: #22ee22 solid 1px;
}
</style>