Browse Source

新建机场任务-绘制航线

李富豪 1 year ago
parent
commit
88b8fd2fc1

+ 9 - 0
Web/src/api/custom/index.ts

@@ -140,6 +140,7 @@ export type RenewalJobApi = (job_id: string) => Promise<any>;
 export type FetchJobDetailApi = (job_id: string) => Promise<any>;
 export type FetchAllDockDeviceListApi = () => Promise<any>;
 export type FetchAllWaylineListApi = () => Promise<any>;
+export type FetchWaylinePointApi = (wayline_id: string) => Promise<any>;
 export type FetchWaylineListApi = (params: FetchWaylineListApiParams) => Promise<any>;
 export type FetchDeviceLogListApi = (sn: string, params: { domain_list: string }) => Promise<any>;
 export type FetchDeviceFeedbackRecordListApi = (sn: string, params: FetchDeviceFeedbackRecordListApiParams) => Promise<any>;
@@ -221,6 +222,13 @@ const fetchAllWaylineListApi: FetchAllWaylineListApi = async () => {
     return res.data;
 };
 
+// 获取航线点
+const fetchWaylinePointApi: FetchWaylinePointApi = async (wayline_id) => {
+    const url = `/wayline/api/v1/workspaces/${getWorkspaceId()}/getWaylinePoint/${wayline_id}`
+    const res = await request.get(url);
+    return res.data;
+};
+
 // 获取航线列表
 const fetchWaylineListApi: FetchWaylineListApi = async (params) => {
     const url = `/wayline/api/v1/workspaces/${getWorkspaceId()}/waylines`
@@ -405,6 +413,7 @@ export const apis = {
     fetchJobDetail: fetchJobDetailApi,
     fetchAllDockDeviceList: fetchAllDockDeviceListApi,
     fetchAllWaylineList: fetchAllWaylineListApi,
+    fetchWaylinePoint: fetchWaylinePointApi,
     fetchWaylineList: fetchWaylineListApi,
     fetchDeviceLogList: fetchDeviceLogListApi,
     fetchDeviceFeedbackRecordList: fetchDeviceFeedbackRecordListApi,

+ 12 - 0
Web/src/assets/icons/sPoint.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="256px" height="190px" viewBox="0 0 256 190" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>初始点</title>
+    <g id="初始点" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="编组">
+            <path d="M126.190464,12.1226919 C128.577213,11.6595913 131.14063,12.1070048 133.314992,13.5746994 C134.324118,14.255859 135.192903,15.1246443 135.874062,16.1337697 L135.874062,16.1337697 L241.296148,172.314638 C242.763843,174.489 243.211256,177.052416 242.748156,179.439166 C242.285055,181.825915 240.91144,184.035997 238.737078,185.503692 C237.167123,186.563412 235.316225,187.12963 233.422086,187.12963 L233.422086,187.12963 L22.5779142,187.12963 C19.9545617,187.12963 17.5795617,186.066306 15.8603998,184.347144 C14.1412379,182.627982 13.0779142,180.252982 13.0779142,177.62963 C13.0779142,175.73549 13.6441323,173.884592 14.7038518,172.314638 L14.7038518,172.314638 L120.125938,16.1337697 C121.593632,13.9594074 123.803715,12.5857925 126.190464,12.1226919 Z" id="三角形" stroke="#FFFFFF" stroke-width="5" fill="#3feb78" transform="translate(128.000000, 94.814815) scale(1, -1) translate(-128.000000, -94.814815) "></path>
+            <g id="S" transform="translate(96.869136, 23.229630)" fill="#FFFFFF" fill-rule="nonzero">
+                <path d="M31.9841975,95.8261728 C51.0735802,95.8261728 62.957037,84.4483951 62.957037,69.9101235 C62.957037,56.0039506 54.6133333,49.8093827 43.8676543,45.1318519 L30.4671605,39.442963 C23.3876543,36.4088889 15.0439506,32.8691358 15.0439506,23.3876543 C15.0439506,14.6646914 22.1234568,9.22864198 32.9955556,9.22864198 C41.7185185,9.22864198 48.6716049,12.6419753 54.2340741,18.0780247 C59.7965432,23.5140741 66.2439506,17.9516049 59.922963,11.3777778 C53.6019753,4.80395062 44.1204938,0 32.9955556,0 C16.5609877,0 4.42469136,10.1135802 4.42469136,24.1461728 C4.42469136,37.6730864 14.6646914,43.9940741 23.2612346,47.6602469 L36.6617284,53.6019753 C45.5111111,57.5209877 52.4641975,60.6814815 52.4641975,70.7950617 C52.4641975,80.1501235 44.7525926,86.5975309 32.1106173,86.5975309 C22.2498765,86.5975309 12.8948148,82.0464198 6.1945679,74.8404938 L0,82.0464198 C7.83802469,90.5165432 18.8365432,95.8261728 31.9841975,95.8261728 Z" id="路径"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 8 - 3
Web/src/hooks/use-g-map-trajectory.ts

@@ -12,7 +12,10 @@ export function useGMapTrajectory() {
 
   // 绘制轨迹
   const drawTrajectory = (list: any[]) => {
-    const paths = list.map(item => item.paths)
+    const paths = list.map(item => item.paths);
+    if (!paths.length) {
+      return;
+    }
     // 绘制起点图标
     const startPosition = paths[0];
     const startIcon = new AMap.Icon({
@@ -48,11 +51,10 @@ export function useGMapTrajectory() {
       })
     })
     // 序号下方圆形标记
-    const circles = serialList.map((item, index) => {
+    const circles = serialList.map((item) => {
       return new AMap.Circle({
         center: new AMap.LngLat(item.paths[0], item.paths[1]),
         radius: 0.5, // 半径
-        // strokeColor: 'white',
         fillColor: 'white',
         fillOpacity: 1,
         strokeWeight: 2,
@@ -102,6 +104,9 @@ export function useGMapTrajectory() {
       return data;
     });
     const paths = list.map(item => item.paths);
+    if (!paths.length) {
+      return;
+    }
     // 绘制轨迹折线
     const polyline = new AMap.Polyline({
       path: paths,

+ 97 - 2
Web/src/pages/page-web/projects/task/taskList/components/CreateTaskModal.vue

@@ -3,7 +3,8 @@
         wrapClassName="createTask-modal" :visible="visible">
         <div class="content">
             <div class="content-panel">
-                <TaskPanel ref="taskPanelRef" :onClickConfirm="onClickConfirm" :onClickCancel="onClickCancel" />
+                <TaskPanel ref="taskPanelRef" :drawTrajectory="drawTrajectory" :onClickConfirm="onClickConfirm"
+                    :onClickCancel="onClickCancel" />
             </div>
             <div class="content-map">
                 <div id="taskMap" :style="{ width: '100%', height: '100%' }"></div>
@@ -15,6 +16,7 @@
 <script lang="ts" setup>
 import { ref, reactive, onMounted } from 'vue';
 import TaskPanel from './TaskPanel.vue';
+import sPointSrc from '/@/assets/icons/sPoint.svg';
 import { useGMapManage } from '/@/hooks/use-g-map';
 
 interface Props {
@@ -32,7 +34,9 @@ const taskPanelRef = ref();
 
 const state = reactive({
     loading: false,
-    map: null,// 高德地图实例
+    AMap: null, // Map类
+    map: null, // 地图对象
+    mapCoverList: [] as any[],
 })
 
 // 高德地图Hook
@@ -40,6 +44,7 @@ const AmapHook = useGMapManage();
 
 const init = async () => {
     const AMap = await AmapHook.asyncInitMap();
+    state.AMap = AMap;
     const map = new AMap.Map('taskMap', {
         center: [121.48, 31.22],
         zoom: 12
@@ -53,6 +58,96 @@ const init = async () => {
 onMounted(() => {
     init();
 })
+
+// 绘制轨迹
+const drawTrajectory = (list: {
+    paths: number[],
+    isSPoint: boolean,
+}[]) => {
+    const AMap: any = state.AMap;
+    const map: any = state.map;
+    state.mapCoverList.forEach(cover => map.remove(cover));
+    const paths = list.map(item => item.paths);
+    if (!paths.length) {
+        return;
+    }
+    // 绘制轨迹折线
+    const polyline = new AMap.Polyline({
+        path: paths,
+        strokeColor: '#3feb78',
+        showDir: true,// 显示路线白色方向箭头
+        strokeOpacity: 1,// 轮廓线透明度
+        strokeWeight: 6,//线宽
+        strokeStyle: 'solid',
+    })
+    const serialList = list.slice();
+    // 序号
+    const text = serialList.map((item, index) => {
+        return new AMap.Text({
+            position: new AMap.LngLat(item.paths[0], item.paths[1]),
+            offset: new AMap.Pixel(-8, -30),
+            text: index + 1,
+            style: {
+                backgroundColor: 'transparent',
+                borderColor: 'transparent',
+            }
+        })
+    })
+    // 序号下方圆形标记
+    const circles = serialList.map((item) => {
+        return new AMap.Circle({
+            center: new AMap.LngLat(item.paths[0], item.paths[1]),
+            radius: 0.5, // 半径
+            fillColor: '#FFFFFF',
+            fillOpacity: 1,
+            strokeWeight: 2,
+        });
+    })
+    // 计算并显示每段线的距离
+    const distances = [];
+    const serialPaths = serialList.map(item => item.paths)
+    for (let i = 0; i < serialPaths.length - 1; i++) {
+        const distance = AMap.GeometryUtil.distance(new AMap.LngLat(serialPaths[i][0], serialPaths[i][1]), new AMap.LngLat(serialPaths[i + 1][0], serialPaths[i + 1][1]));
+        // 计算两个点之间的中点坐标
+        const midLng = (serialPaths[i][0] + serialPaths[i + 1][0]) / 2;
+        const midLat = (serialPaths[i][1] + serialPaths[i + 1][1]) / 2;
+        const midPoint = new AMap.LngLat(midLng, midLat);
+        // 在中点位置放置文本以显示距离
+        const distanceText = new AMap.Text({
+            position: midPoint,
+            offset: new AMap.Pixel(-16, 10),
+            text: `${distance.toFixed(1)} m`,// 距离
+            style: {
+                fontSize: '10px',
+                color: '#FFFFFF',
+                backgroundColor: 'rgba(0, 0, 0, 0.75)',
+                borderColor: 'transparent',
+            },
+        });
+        distances.push(distanceText);
+    }
+    const other = [polyline, ...text, ...circles, ...distances];
+    const item = list.find(item => item.isSPoint);
+    if (item) {
+        // 绘制S点图标
+        const position = item.paths;
+        const icon = new AMap.Icon({
+            size: new AMap.Size(30, 30),
+            image: sPointSrc,
+            imageSize: new AMap.Size(30, 30)
+        })
+        const marker = new AMap.Marker({
+            position: new AMap.LngLat(position[0], position[position.length - 1]),
+            icon: icon,
+            offset: new AMap.Pixel(-20, -35),// 位置偏移
+        })
+        other.push(marker);
+    }
+    state.mapCoverList = other;
+    map.add(other);
+    // 自动缩放地图到合适的视野级别
+    map.setFitView(other);
+}
 </script>
 
 <style lang="scss" scoped>

+ 24 - 0
Web/src/pages/page-web/projects/task/taskList/components/TaskPanel.vue

@@ -248,9 +248,14 @@ import aircraftSrc from '/@/components/airport/icons/aircraft.svg';
 import dockSrc from '/@/components/airport/icons/dockInfo.svg';
 import moment from 'moment';
 import { apis } from '/@/api/custom';
+import { wgs84togcj02 } from '/@/vendors/coordtransform';
 import { DEVICE_NAME } from '/@/types/device'
 
 interface Props {
+  drawTrajectory: (list: {
+    paths: number[],
+    isSPoint: boolean,
+  }[]) => void,
   onClickConfirm: (data: any) => Promise<any>,
   onClickCancel: () => void,
 };
@@ -441,6 +446,24 @@ watch(() => deviceState.visible, async (visible) => {
   }
 });
 
+watch(() => state.formModel.file_id, async (wayline_id) => {
+  if (!wayline_id) {
+    return;
+  }
+  try {
+    const res = await apis.fetchWaylinePoint(wayline_id);
+    const list = res.data.map((item: any) => {
+      return {
+        paths: wgs84togcj02(item.longitude, item.latitude),
+        isSPoint: item.point_index == 0 && item.folder_id == 0,
+      }
+    })
+    props.drawTrajectory(list);
+  } catch (error) {
+    console.error(error);
+  }
+});
+
 // 检查执行时间
 const checkTaskPeriods = (rule: any, values: string) => {
   if (values?.length) {
@@ -675,6 +698,7 @@ defineExpose({ init });
   .ant-select-selector {
     background: #3c3c3c !important;
     color: rgba(255, 255, 255, .25) !important;
+    border-color: #3c3c3c !important;
   }
 }
 

+ 7 - 2
Web/src/pages/page-web/projects/task/taskList/index.vue

@@ -66,7 +66,12 @@
             取消
           </div>
           <div style="color: #E02020;" v-else-if="record.status === 5">
-            失败
+            <span>
+              失败
+            </span>
+            <a-tooltip :title="record.error_text">
+              <ExclamationCircleOutlined />
+            </a-tooltip>
           </div>
           <div style="color: #2B85E4;" v-else-if="record.status === 6">
             暂停
@@ -111,7 +116,7 @@
 <script lang="ts" setup>
 import { reactive, onMounted, watch, computed } from 'vue';
 import { Modal, message } from 'ant-design-vue';
-import { ApiOutlined, CopyOutlined, GatewayOutlined, DeleteOutlined } from '@ant-design/icons-vue';
+import { ExclamationCircleOutlined, ApiOutlined, CopyOutlined, GatewayOutlined, DeleteOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
 import Airport from '/@/components/airport/index.vue';
 import CreateTaskModal from './components/CreateTaskModal.vue';