From 30c586d9c0792a0638520936c227088b5970c2e0 Mon Sep 17 00:00:00 2001 From: zhaipx Date: Tue, 15 Apr 2025 10:15:20 +0800 Subject: [PATCH] =?UTF-8?q?=E8=88=AA=E7=BA=BF=E7=AE=A1=E7=90=86=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2UI=E5=92=8C=E6=93=8D=E4=BD=9C=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/js/RouteManageViewer.js | 68 ++++- src/assets/js/TypeInitial.ts | 13 + src/assets/js/measureViewer.js | 20 +- src/assets/js/request.js | 27 -- src/assets/js/testData.ts | 38 +++ src/assets/js/useBlockDialog.js | 54 ---- src/assets/js/weatherRequest.ts | 39 +++ src/components/BottomBar.vue | 2 +- src/components/RouteOptions.vue | 9 +- src/components/page/RouteManagePage.vue | 359 ++++++++++++++++++++++++ src/components/toolbar.vue | 15 +- src/router/index.js | 6 +- src/store/RouteStore.ts | 13 +- src/types/entityoptions.ts | 17 ++ src/utils/map/SpatialAnalysis.ts | 126 +++++++++ src/utils/map/geocomputation.ts | 14 + 16 files changed, 716 insertions(+), 104 deletions(-) create mode 100644 src/assets/js/TypeInitial.ts delete mode 100644 src/assets/js/request.js create mode 100644 src/assets/js/testData.ts delete mode 100644 src/assets/js/useBlockDialog.js create mode 100644 src/assets/js/weatherRequest.ts 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} - * @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} - * @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 = ()=>{ + + + 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 @@ \ 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}
航点高度:{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 }