diff --git a/src/assets/js/RouteManageViewer.js b/src/assets/js/RouteManageViewer.js index 2b6d3c0..4096064 100644 --- a/src/assets/js/RouteManageViewer.js +++ b/src/assets/js/RouteManageViewer.js @@ -1,14 +1,14 @@ -import {DataSource} from "cesium"; +import {Cartesian3, DataSource} from "cesium"; import * as Cesium from 'cesium' export default class RouteManageViewer { - constructor(viewer, isClose, height) { + constructor(viewer, isClose) { this.viewer = viewer this.scene = viewer.scene this.routeParams = { isClose: isClose, - height: height, + height: 0, } this.positions = [] this.temPositions = [] // 鼠标移动时产生的临时点 @@ -16,8 +16,66 @@ export default class RouteManageViewer { this.lineEntity = undefined // 折线元素 this.lineEntitys = [] // 折线元素数组,每完成一次航线绘制,将折线元素加入此数组 } - - // -------------------------------------------// + + /** + * 显示航线 + * @param line Airline + */ + addAirLine(line){ + let res = this.viewer.entities.getById( "航线" + line.unicode) + if(res!==undefined){ + return res + } + + let degreesArr = [] + for (let i = 0; i < line.points.length; i++) { + let coord = new Cartesian3.fromDegrees(line.points[i].lon,line.points[i].lat,line.points[i].alt) + degreesArr.push(coord) + } + if(line.isClose) + degreesArr.push(new Cartesian3.fromDegrees(line.points[0].lon,line.points[0].lat,line.points[0].alt)) + + let airlineEntity = new Cesium.Entity({ + name: line.name, + id: "航线" + line.unicode, + polyline: { + positions: degreesArr, + width: 3, + material: Cesium.Color.ORANGE, + clampToGround: false, + } + }) + this.viewer.entities.add(airlineEntity) + degreesArr.forEach((pt,index)=>{ + let vertexEntity = new Cesium.Entity({ + id: line.unicode + "-航点" + index, + position: pt, + point: { + color: Cesium.Color.WHITE, + pixelSize: 6, + outlineColor: Cesium.Color.RED, + outlineWidth: 2, + disableDepthTestDistance:99000000, + // heightReference:Cesium.HeightReference.CLAMP_TO_GROUND, + } + }); + this.viewer.entities.add(vertexEntity) + }) + return airlineEntity + } + + /** + * 删除航线 + * @param line Airline + */ + removeRoute(line){ + this.viewer.entities.removeById( "航线" + line.unicode) + line.points.forEach((_,index)=>{ + this.viewer.entities.removeById(line.unicode + "-航点" + index) + }) + } + + // -----------------绘制航线相关功能--------------------------// //开始绘制 start() { this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) diff --git a/src/assets/js/TypeInitial.ts b/src/assets/js/TypeInitial.ts new file mode 100644 index 0000000..1ed7fd6 --- /dev/null +++ b/src/assets/js/TypeInitial.ts @@ -0,0 +1,13 @@ +import {Airline, AirlinePoint} from "@/types/entityoptions.ts"; + +export function newAirlinePt() { + let pt: AirlinePoint = {alt: undefined, ch1: 0, ch2: 0, lat: undefined, lon: undefined, nPt: undefined, speed: 0} + return pt +} + +export function newAirline() { + let flyLine: Airline = { + PtNum: undefined, code: undefined, isClose: false, name: "", points: [], totalDistance: undefined + } + return flyLine +} \ No newline at end of file diff --git a/src/assets/js/measureViewer.js b/src/assets/js/measureViewer.js index a40afcd..05ae9fc 100644 --- a/src/assets/js/measureViewer.js +++ b/src/assets/js/measureViewer.js @@ -322,6 +322,11 @@ export default class MeasureViewer { * @param line Airline */ showAirLine(line){ + let res = this.viewer.entities.getById( "航线" + line.code + "-" +line.name,) + if(res!==undefined){ + return res + } + let degreesArr = [] for (let i = 0; i < line.points.length; i++) { let coord = new Cartesian3.fromDegrees(line.points[i].lon,line.points[i].lat,line.points[i].alt) @@ -329,10 +334,10 @@ export default class MeasureViewer { } if(line.isClose) degreesArr.push(new Cartesian3.fromDegrees(line.points[0].lon,line.points[0].lat,line.points[0].alt)) - + let airlineEntity = new Cesium.Entity({ name: line.name, - id: line.name + line.totalDistance, + id: "航线" + line.code + "-" +line.name, polyline: { positions: degreesArr, width: 3, @@ -341,21 +346,24 @@ export default class MeasureViewer { } }) this.viewer.entities.add(airlineEntity) - for (let pt in degreesArr) { + degreesArr.forEach((pt,index)=>{ let vertexEntity = new Cesium.Entity({ - // id: "航点" + , + id: line.code + "-航点" + index, position: pt, point: { color: Cesium.Color.FUCHSIA, pixelSize: 6, + outlineColor: Cesium.Color.RED, + outlineWidth: 2, disableDepthTestDistance:99000000, // heightReference:Cesium.HeightReference.CLAMP_TO_GROUND, } }); this.viewer.entities.add(vertexEntity) - } - + }) + return airlineEntity } + addAirplaneEntity(modelPath,StrUavTypeID){ this.viewer.scene.globe.depthTestAgainstTerrain = true; diff --git a/src/assets/js/request.js b/src/assets/js/request.js deleted file mode 100644 index 8a945b4..0000000 --- a/src/assets/js/request.js +++ /dev/null @@ -1,27 +0,0 @@ -import axios from "axios"; - -function login(username, password) -{ - return axios({ - method: "POST", - url: "/onlinetest/htfp/cac/logIn", - headers: {"Content-Type": "application/json"}, - data: { - username: username, - password: password, - forceLogOutOtherDeviceAccount: false - } - }) -} - -function requestAirline(uavID){ - return axios({ - method: "POST", - url: "/onlinetest/htfp/cac/queryUavBundledRouteList", - headers: [{"Content-Type": "application/json"},{"X-Authorization-With": sessionStorage.getItem("token")}], - data: { - uavId: uavID, - } - }) -} -export {login, requestAirline} diff --git a/src/assets/js/testData.ts b/src/assets/js/testData.ts new file mode 100644 index 0000000..453e155 --- /dev/null +++ b/src/assets/js/testData.ts @@ -0,0 +1,38 @@ +import {Airline, getUnicode} from "@/types/entityoptions.ts"; + +// let route: AirlinePoint = {alt: 0, ch1: 0, ch2: 0, lat: 0, lon: 0, nPt: 0, speed: 0} +const route: Airline = { + PtNum: 5, code: 1, isClose: false, name: "测试航线1", totalDistance: 12323, + unicode:getUnicode(), + points: [ + { lon: 120.23234234, lat: 30.232323,alt: 550, ch1: 0, ch2: 3, nPt: 1, speed: 0}, + { lon: 120.34233234, lat: 30.232312,alt: 550, ch1: 0, ch2: 3, nPt: 2, speed: 0}, + { lon: 120.23234324, lat: 30.21223,alt: 550, ch1: 0, ch2: 3, nPt: 3, speed: 0}, + { lon: 120.23289964, lat: 30.256323,alt: 550, ch1: 0, ch2: 3, nPt: 4, speed: 0}, + { lon: 120.53564234, lat: 30.132323,alt: 550, ch1: 0, ch2: 1, nPt: 5, speed: 0}, + ], +} +const route2: Airline = { + PtNum: 4, code: 2, isClose: true, name: "测试航线2", totalDistance: 2341234, + unicode:getUnicode(), + points: [ + { lon: 120.23234234, lat: 30.232323,alt: 550, ch1: 0, ch2: 3, nPt: 0, speed: 0}, + { lon: 120.34233234, lat: 30.232312,alt: 550, ch1: 0, ch2: 3, nPt: 1, speed: 0}, + { lon: 120.23234324, lat: 30.21223,alt: 550, ch1: 0, ch2: 3, nPt: 2, speed: 0}, + { lon: 120.53564234, lat: 30.132323,alt: 550, ch1: 2, ch2: 1, nPt: 4, speed: 0}, + ], +} +const route3: Airline = { + PtNum: 7, code: 3, isClose: false, name: "测试航线3", totalDistance: 3234134, + unicode:getUnicode(), + points: [ + { lon: 123.23234234, lat: 30.232323,alt: 550, ch1: 0, ch2: 3, nPt: 0, speed: 0}, + { lon: 123.34233234, lat: 30.232312,alt: 550, ch1: 0, ch2: 3, nPt: 1, speed: 0}, + { lon: 123.23234324, lat: 30.21223,alt: 550, ch1: 0, ch2: 3, nPt: 2, speed: 0}, + { lon: 123.23289964, lat: 30.256323,alt: 550, ch1: 0, ch2: 3, nPt: 3, speed: 0}, + { lon: 123.53564234, lat: 30.132323,alt: 550, ch1: 0, ch2: 1, nPt: 4, speed: 0}, + { lon: 123.53564234, lat: 30.132323,alt: 550, ch1: 0, ch2: 1, nPt: 4, speed: 0}, + { lon: 123.53564234, lat: 30.132323,alt: 550, ch1: 0, ch2: 1, nPt: 4, speed: 0}, + ], +} +export {route,route2,route3} \ No newline at end of file diff --git a/src/assets/js/useBlockDialog.js b/src/assets/js/useBlockDialog.js deleted file mode 100644 index e365bc2..0000000 --- a/src/assets/js/useBlockDialog.js +++ /dev/null @@ -1,54 +0,0 @@ -import { defineAsyncComponent, render, createVNode } from "vue"; -export class routeDialog { - constructor() { - this.component = defineAsyncComponent( - () => import("@/components/RouteOptions.vue")); - this.vnode = null; - this.node = null; - this.props = { - width: "40%", - height: "auto", - }; - } - installRouteDialog() { - if (!this.vnode) { - const dialog = createVNode(this.component, this.props); - const container = document.createElement('div'); - render(dialog, container); - this.vnode = dialog; - this.node = container.childNodes[0]; - document.body.appendChild(this.node); - } - } - /** - * - * @returns {Promise<string>} - * @param points 经纬度坐标点数组 - */ - show(points) { - // 发送信号,显示窗口 - const event = new CustomEvent('route-dialog-show', { detail: - { show: true, pts: points } - }); - document.dispatchEvent(event); - return new Promise((resolve,reject) => { - document.addEventListener('route-dialog-confirm', event => { - if(event.detail==='cancel'){ - reject('cancel') - }else{ - resolve(event.detail); - } - }); - }) - } - } - const aDialog = new routeDialog(); - - /** - * 展示一个阻塞式对话框 - * @returns {Promise<string>} - * @param point 经纬度坐标点数组 - */ - export async function showRouteDialog(point) { - return aDialog.show(point); - } \ No newline at end of file diff --git a/src/assets/js/weatherRequest.ts b/src/assets/js/weatherRequest.ts new file mode 100644 index 0000000..9e818bc --- /dev/null +++ b/src/assets/js/weatherRequest.ts @@ -0,0 +1,39 @@ +import axios from "axios"; + +type WeatherResponse = { + success: boolean, + code: number, + message: number, + data: any //响应数据 +} + +//查询未来24小时,指定位置的地面气象变量信息 +function query_surface_forecast(lat:number, lon: number) +{ + return axios({ + method: "POST", + url: "/onlinetest/htfp/weather/v1surface/querySurfaceForecast", + headers: {"Content-Type": "application/json"}, + data: { + latitude: lat, + longitude: lon, + } + }) +} + +//查询未来24小时,指定位置和高度的高空气象变量信息 +function query_upper_forecast(lat:number, lon: number, level:number){ + return axios({ + method: "POST", + url: "/onlinetest/htfp/weather/v1upper/queryUpperForecast", + headers: {"Content-Type": "application/json"}, + data: { + latitude: lat, + longitude:lon, + level: level + } + }) +} + + +export {query_upper_forecast, query_surface_forecast} diff --git a/src/components/BottomBar.vue b/src/components/BottomBar.vue index ac04461..1dc8068 100644 --- a/src/components/BottomBar.vue +++ b/src/components/BottomBar.vue @@ -72,7 +72,7 @@ function lonlatClick() { position: absolute; bottom: 1px; left: 0; - width: 100vw; + width: 100%; height: 1.7rem; background-color: rgba(47, 53, 60, 0.8); color: #fff; diff --git a/src/components/RouteOptions.vue b/src/components/RouteOptions.vue index e4e06a5..33ba264 100644 --- a/src/components/RouteOptions.vue +++ b/src/components/RouteOptions.vue @@ -8,7 +8,8 @@ import {defineEmits, ref} from "vue"; let emit = defineEmits(['routeDraw','cancelDraw']) let routeParams = ref({ - code: 0, + name: '', + code: 0, isClose: false, height: 0, routePts: [] @@ -26,6 +27,9 @@ let routeCode = [ },{ label: "任务航线4", value: 4, + },{ + label: "任务航线5", + value: 5, } ] const done = ()=>{ @@ -59,6 +63,9 @@ const cancel = ()=>{ </n-switch> </n-form-item> <n-form-item label="航线名称"> + <n-input v-model:value="routeParams.name" placeholder="输入名称"></n-input> + </n-form-item> + <n-form-item label="航线类型"> <n-select v-model:value="routeParams.code" :options="routeCode" /> </n-form-item> <n-form-item label="航线高度/m"> diff --git a/src/components/page/RouteManagePage.vue b/src/components/page/RouteManagePage.vue index 96c0baf..f8ddbbc 100644 --- a/src/components/page/RouteManagePage.vue +++ b/src/components/page/RouteManagePage.vue @@ -1,11 +1,370 @@ <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, 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"; +let myChart: 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 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, + } +] + +onMounted(()=>{ + myChart = echarts.init(document.getElementById('echarts-profile'),'dark') + + routeStore.addRoute(route) + routeStore.addRoute(route2) + routeStore.addRoute(route3) + + routesInstore.value = routeStore.flyRoute.map((route)=> { + return { + value: route.unicode, + label: route.name + } + }) + +}) + +// 航线选择回调 +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 + //TODO: 查询气象接口,使用返回值promise +} + +//查询下一航点的气象信息 +function nextPtWeather() { + routePtNumber.value += 1 + console.log(selectedRoute.value.points.length) + //TODO: 查询气象接口,使用返回值promise +} </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 style="height: 45vh; margin-left: 1rem; margin-top: .5rem;" > + <n-row><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-flex> + </n-layout-content> + </n-layout> + </div> + </div> + </n-config-provider> + </template> <style scoped> +#left{ + width: 20vw; + height: 100vh; +} +#map2 { + width: 60vw; + height: 100vh; + position: relative; +} +#right{ + width: 20vw; + 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); +} </style> \ No newline at end of file diff --git a/src/components/toolbar.vue b/src/components/toolbar.vue index db5c017..6d2ebc4 100644 --- a/src/components/toolbar.vue +++ b/src/components/toolbar.vue @@ -23,6 +23,7 @@ import RouteOptions from "@/components/RouteOptions.vue"; import {useRouteStore} from "@/store/RouteStore"; import MarkerDialog from "@/components/MarkerDialog.vue"; import { useRouter, useRoute } from 'vue-router' +import {getUnicode} from "@/types/entityoptions.ts"; const router = useRouter() const route = useRoute() @@ -77,7 +78,7 @@ function handleEditSelect(key) { else{ // 转到航线管理页面 router.push({ - name: 'Test', + name: 'routeManage', }) } } @@ -283,11 +284,12 @@ function manageLayer(){ /** * 绘制航线,目前只绘制任务航线 - * @param routeParams 航线属性 + * @param routeParams 航线属性,type:{name: string,code: number,isOpen: bool,height: number,routePts: []} */ -function startDrawRoute(routeParams /*{code: number,isOpen: bool,height: number,routePts: []}*/) { +function startDrawRoute(routeParams) { showRouteModal.value = false - let drawLine = new RouteManageViewer(window.viewer, routeParams.isClose, routeParams.height) + let drawLine = new RouteManageViewer(window.viewer, routeParams.isClose) + drawLine.routeParams.height = routeParams.height drawLine.start().then(result => { console.log(result) // result是经纬度坐标数组,需转换为 AirlinePoint数组 @@ -299,9 +301,10 @@ function startDrawRoute(routeParams /*{code: number,isOpen: bool,height: number, AirlinePoints.at(-1).ch2 = 0x01; // 航线加入store let aRoute = { - code: routeParams.code, PtNum: result.length, isClose: routeParams.isClose, name: "", points: AirlinePoints, totalDistance: 0 + code: routeParams.code, PtNum: result.length, isClose: routeParams.isClose, unicode: getUnicode(), + name: routeParams.name, points: AirlinePoints, totalDistance: 0 } - useRouteStore().route.push(aRoute) + useRouteStore().flyRoute.push(aRoute) // fixme: 航线加入图层管理 }).catch(reject => { diff --git a/src/router/index.js b/src/router/index.js index 65db1c8..8ce9b7f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -14,9 +14,9 @@ const router = createRouter({ component: ()=>import('@/components/RouteOptions.vue') }, { - path: '/test2', - name: 'Test2', - component: ()=>import('@/components/MarkerDialog.vue') + path: '/routeManage', + name: 'routeManage', + component: ()=>import('@/components/page/RouteManagePage.vue') } ] }); diff --git a/src/store/RouteStore.ts b/src/store/RouteStore.ts index 646a8ee..b2b9a06 100644 --- a/src/store/RouteStore.ts +++ b/src/store/RouteStore.ts @@ -5,9 +5,20 @@ import {Airline} from "@/types/entityoptions.ts"; export const useRouteStore = defineStore('RouteStore', { state: ()=>{ return { - route: [] as Airline[] //地图中绘制的航线,用于发送给后端(QT) + flyRoute: [] as Airline[] //地图中绘制的航线,用于发送给后端(QT) } }, actions: { + /** + * 向store中添加航线,code为唯一码,如冲突,则会替换原有值 + * @param route + */ + addRoute(route:Airline){ + let index = this.flyRoute.findIndex((item: Airline)=>item.code===route.code) + if(index > -1){ + this.flyRoute.splice(index,1); + } + this.flyRoute.push(route) + } } }) \ No newline at end of file diff --git a/src/types/entityoptions.ts b/src/types/entityoptions.ts index 26ce27e..38d0b01 100644 --- a/src/types/entityoptions.ts +++ b/src/types/entityoptions.ts @@ -49,6 +49,7 @@ export type AirlinePoint = { export type Airline = { name: string, code: number, //航线编号 + unicode: string, //航线唯一码,前端有效(用于图层管理、store管理和地图Entity管理) PtNum: number, isClose: boolean, totalDistance: number, @@ -60,3 +61,19 @@ export type routeTerrain = { distanceArray: number[], elevationArray: number[], } + +/** + * 生成唯一码(随机字符串+时间戳) + * @param len 随机字符串长度,默认12 + */ +export function getUnicode(len:number = 12): string{ + let timestamp = new Date().getTime(); + /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/ + let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; + let maxPos = $chars.length; + let randomStr = ''; + for (let i = 0; i < len; i++) { + randomStr += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return randomStr + timestamp; +} \ No newline at end of file diff --git a/src/utils/map/SpatialAnalysis.ts b/src/utils/map/SpatialAnalysis.ts index d198ab2..647fd48 100644 --- a/src/utils/map/SpatialAnalysis.ts +++ b/src/utils/map/SpatialAnalysis.ts @@ -683,3 +683,129 @@ export const drawEchartsVisibility = (xData:number[], yData:number[],startHeight ], }); } + +/** + * 航线管理页面图表 + * @param myChart + * @param xData 地形距离数组,地面距离 + * @param yData 地形高度数组,以面积线绘制 + * @param routeArr 航点序号数组 + * @param routeHeight 航线高度数组,以折线绘制 + */ +export const drawEcharts_RouteDetection = (myChart: EChartsType,xData:number[], yData:number[], + routeArr: number[], routeHeight:number[]) => { + + // 绘制图表 + myChart.setOption({ + backgroundColor:'#101014', + grid: { + top: "17%", + right: "6%", + left:" 15%", + bottom: "20%", + }, + legend: { + show: true, + type: 'plain', + top: '7%', + right: '5%', + data: [ + { //地形剖面图例 + name: '地形剖面', + itemStyle: {fontSize: 14}, + lineStyle: 'inherit', + }, + { //视线图例 + name: '航线高度', + lineStyle: 'inherit', + itemStyle: {fontSize: 14} + } + ], + selectedMode: true, //图例选择模式 + inactiveColor: '#626161', + }, + tooltip: { + show: true, + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + formatter:'地表高度: {c0}<br> 航点高度:{c1}' + }, + xAxis: [ + { + boundaryGap : false, + min: 0, + data: xData, + nameTextStyle: { + fontWeight:'bolder', + fontSize: 10 + }, + axisLine:{ + onZero: false, + show: true, // 是否显示坐标轴轴线 + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + }, + axisLabel: { + formatter: '{value}', + margin: 7, + }, + }, + { + position: 'bottom', + boundaryGap: false, + data: routeArr, + offset: 30, + axisTick:{ show: true, inside: true }, // 是否显示坐标轴刻度 + axisLine:{ + onZero: false, + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + }, + axisLabel: { + formatter: '航点{value}', + margin: 7, + }, + } + ], + yAxis: { + type: 'value', + name: '高度/ m', + nameTextStyle: { + fontSize: 14 + }, + nameLocation: 'end', + position: 'left', + axisLabel: { + formatter: '{value}' + }, + axisLine: { + show: true, + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + } + }, + series: [ + { + name:'地形剖面', + xAxisIndex: 0, + type: 'line', + data: yData, + areaStyle: { + color: '#37a5fb', + opacity: 0.5 + } + }, + { + name:'航线高度', + xAxisIndex: 1, + type: 'line', + data: routeHeight, + lineStyle: { + color: '#8ae73d', + } + }, + ], + }); +} diff --git a/src/utils/map/geocomputation.ts b/src/utils/map/geocomputation.ts index d84e5f5..dd2431e 100644 --- a/src/utils/map/geocomputation.ts +++ b/src/utils/map/geocomputation.ts @@ -15,6 +15,7 @@ import { Viewer } from 'cesium' import {Angle} from "@/utils/map/angle.ts"; +import {AirlinePoint} from "@/types/entityoptions.ts"; /** * 计算空间中一点到一条直线的最短距离的交点(在不考虑地球曲率的情况下) @@ -184,4 +185,17 @@ export function ByDirectionAndLen(position: Cartesian3, angle:number, distance:n return Matrix4.multiplyByPoint(matrix, new Cartesian3(0, distance, 0), new Cartesian3()); } + +/** + * 把航点数组转为笛卡尔3坐标数组 + * @param coords + */ +export function transLonlat2Car3(coords: AirlinePoint[]):Cartesian3[] { + let coordCar3: Cartesian3[] = [] + coords.forEach(coord => { + let car3 = Cartesian3.fromDegrees(coord.lon, coord.lat, coord.alt) + coordCar3.push(car3) + }) + return coordCar3 +} export { getClosestPoint, isOnLineSegment, getDistance, getAzimuth, getPolygonArea }