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

583 lines
17 KiB
Vue

<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} from "@/assets/js/weatherRequest.ts";
let myChart: EChartsType = undefined
let weatherChart: EChartsType = undefined
let routeStore = useRouteStore()
let selectedRouteCode = ref(null)
let showPtList = ref(true)
let selectedRoute = ref<Airline>(newAirline())
let routesInstore = 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 :any[] = []
onBeforeUnmount(()=>{
clearInterval(timer)
})
onMounted(()=>{
getTime()
myChart = echarts.init(document.getElementById('echarts-profile'),'dark')
weatherChart = echarts.init(document.getElementById('weatherChart'),'dark')
routeStore.addRoute(route)
routeStore.addRoute(route2)
routeStore.addRoute(route3)
routesInstore.value = routeStore.flyRoute.map((route)=> {
return {
value: route.unicode,
label: route.name
}
})
})
function getTime(){
timer = setInterval(getTimesInterval, 1000);
}
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 = new RouteManageViewer(window.viewer,selectedRoute.value.isClose)
window.viewer.flyTo(routeViewer.addAirLine(selectedRoute.value), {duration: 2})
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
let lat = selectedRoute.value.points[routePtNumber.value-1].lat
let lon = selectedRoute.value.points[routePtNumber.value-1].lon
if(lat && lon){
queryWeather(lon,lat)
}
}
//查询下一航点的气象信息
function nextPtWeather() {
routePtNumber.value += 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)
}
// TODO: 考虑高度
}
function queryWeather(lon:number,lat:number,level?: number) {
query_surface_forecast(lon,lat)
.then(res=>{
let weatherData = res.data.data
let timeArr = weatherData.time.map(item=>{
let i = item.indexOf('T')
return item.slice(i+1,i+3) + '时'
})
setChartOptions(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))
})
}
function setChartOptions(timeSeries:string[], temp: number[], windSpeed:number[], wind360: number[], precip:number[], humidity?:number[]) {
let windDirection = wind360.map(item=>{
let arr = [22.5, 22.5+45, 22.5+45*2, 22.5+45*3, 22.5+45*4, 22.5+45*5, 22.5+45*6, 22.5+45*7]
let str = ['北风','东北风','东风','东南风','南风','西南风','西风','西北风']
if(item<=arr[0] || item>arr[7]){
return str[0]+': '+ Math.round(item)+'°'
}else if(arr[0] <=item && item<=arr[1]){
return str[1]+' : '+ Math.round(item)+'°'
}else if(arr[1] <=item && item<=arr[2]){
return str[2]+' : '+ Math.round(item)+'°'
}else if(arr[2] <=item && item<=arr[3]){
return str[3]+' : '+ Math.round(item)+'°'
}else if(arr[3] <=item && item<=arr[4]){
return str[4]+' : '+ Math.round(item)+'°'
}else if(arr[4] <=item && item<=arr[5]){
return str[5]+' : '+ Math.round(item)+'°'
}else if(arr[5] <=item && item<=arr[6]){
return str[6]+' : '+ Math.round(item)+'°'
}else if(arr[6] <=item && item<=arr[7]){
return str[7]+' : '+ Math.round(item)+'°'
}
})
let colors = ['#5470C6', '#91CC75', '#EE6666'];
weatherChart.setOption({
backgroundColor:'#101014',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
}
},
color: colors,
grid: [
{x: '7%', y: '7%', height: '40%', left: '12%',right: '15%'},
{x: '7%', y2: '7%', height: '38%', left: '12%',right: '15%', bottom: '10%'}
],
legend: {
data:['风速','降水','温度']
},
xAxis: [
{
show: false,//隐藏了x轴
type: 'category',
gridIndex: 0,//对应前面grid的索引位置第一个
axisTick: {
alignWithLabel: true
},
data: windDirection,
},
{
type: 'category',
gridIndex: 1, //对应前面grid的索引位置第二个
axisTick: {
alignWithLabel: true
},
data: timeSeries,
}
],
//y轴不管有几个x轴几个y轴或者图只要找到他对应的grid图的序号索引就可以精准匹配
yAxis: [
{
type: 'value',
gridIndex: 1,//对应前面grid的索引位置第二个
splitLine: {show: false},
position: 'right',
axisLine: {
lineStyle: {
color: colors[1]
}
},
axisLabel: {
formatter: '{value}ml'
}
},
{
type: 'value',
gridIndex: 1,
nameLocation: 'middle',
name: '温度',
nameTextStyle: {
padding: 25
},
splitLine: {show: false},
position: 'left',
axisLine: {
lineStyle: {
color: colors[2]
}
},
axisLabel: {
formatter: '{value}°C'
}
},
{
type: 'value',
gridIndex: 0,
name: '风速',
nameTextStyle: {
padding: 25
},
position: 'left',
splitLine: {show: false},
axisLine: {
lineStyle: {
color: colors[0]
}
},
axisLabel: {
formatter: '{value}',
textStyle: {
fontSize: 12//y轴坐标轴上的字体大小
}
}
}
],
series: [
{
name:'风速',
type: "line",
xAxisIndex: 0,
yAxisIndex: 2,
data: windSpeed,
},
{
name:'降水',
type:'bar',
xAxisIndex: 1,
yAxisIndex: 0,
data: precip
},
{
name:'温度',
type:'line',
xAxisIndex: 1,
yAxisIndex: 1,
data:temp
}
]
})
}
</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">
<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 routesInstore" 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 size="small" type="error" style="height: 1.5rem; padding-top: 1px">删除</n-button>
</n-space>
</n-space>
</n-scrollbar>
</n-radio-group>
<n-divider style="margin: .2rem -.5rem"></n-divider>
<n-space vertical>
<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="start">
<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 class="border">
<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: 7rem" size="tiny" :options="levelOptions" placeholder="选择高度层"></n-select>
</n-space>
<n-scrollbar x-scrollable id="weatherChart" style="height:41.5vh; width: 100%">
<!-- <n-card class="wCard" size="small" title="卡片">-->
<!-- </n-card>-->
<!-- <n-card class="wCard" size="small" title="卡片">-->
<!-- </n-card>-->
<!-- <n-card class="wCard" size="small" title="卡片">-->
<!-- </n-card>-->
<!-- <n-card class="wCard" size="small" title="卡片">-->
<!-- </n-card>-->
<!-- <n-card class="wCard" size="small" title="卡片">-->
<!-- </n-card>-->
<!-- <n-card class="wCard" size="small" title="卡片">-->
<!-- </n-card>-->
</n-scrollbar>
</n-flex>
</n-layout-content>
</n-layout>
</div>
</div>
</n-config-provider>
</template>
<style scoped>
#left{
width: 20vw;
height: 100vh;
}
#map2 {
width: 57vw;
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);
}
.wCard {
width: 4vw;
height: 39vh;
display: inline-block
}
.border{
border: #22ee22 solid 1px;
}
</style>