GuLiMmo
2023-11-30 bdfc99ddd7390ed67533ed68af79ecb80104b115
航线事件编辑修改
5 files modified
398 ■■■■ changed files
env/.env.dev 1 ●●●● patch | view | raw | blame | history
env/.env.production 4 ●●●● patch | view | raw | blame | history
src/components/MediaPanel.vue 2 ●●● patch | view | raw | blame | history
src/components/waylinetool/index.vue 375 ●●●● patch | view | raw | blame | history
src/pages/page-web/projects/wayline.vue 16 ●●●●● patch | view | raw | blame | history
env/.env.dev
@@ -1,4 +1,5 @@
VITE_API_URL = 'http://192.168.1.133:6789'
VITE_MEDIAPANEL_API_URL = 'https://dev.jxpskj.com:8026/cloud-bucket',
VITE_WS_API_URL = 'ws://192.168.1.133:6789/api/v1/ws'
VITE_APP_ENVIRONMENT=DEV
VITE_BASE_API = '/drone-api'
env/.env.production
@@ -1,6 +1,6 @@
VITE_APP_ENVIRONMENT=PROD
VITE_APP_APIGATEWAY_BACKEND_HOST=''
VITE_API_URL = 'https://dev.jxpskj.com:36789'
VITE_MEDIAPANEL_API_URL = 'https://dev.jxpskj.com:8026/cloud-bucket',
VITE_WS_API_URL = 'wss://dev.jxpskj.com:36789/api/v1/ws'
VITE_BASE_API = '/drone-api'
VITE_BASE_API = '/drone-api'
src/components/MediaPanel.vue
@@ -126,7 +126,7 @@
const videoPlayerId = ref('videoPlayerId')
// 文件前缀
// const prefix = 'https://dev.jxpskj.com:8026/cloud-bucket'
const prefix = mediaPanelPrefix || 'https://dev.jxpskj.com:8026/cloud-bucket'
const prefix = mediaPanelPrefix || import.meta.env.VITE_MEDIAPANEL_API_URL
// 搜索栏配置项
const searchPanelOptions = reactive({
  size: 'large',
src/components/waylinetool/index.vue
@@ -1,16 +1,38 @@
<template>
    <ul class="btn-list" v-if="waylineAbout.isShow">
        <li class="s-btn" v-for="(item, index) in btnList" :key="index">
          <div class="title">{{ item.title }}</div>
          <div class="btn" @click="item.event">
            <img :src="item.icon" alt="icon">
          </div>
        </li>
    </ul>
  <ul class="btn-list" v-if="waylineAbout.isShow">
    <li class="s-btn" v-for="(item, index) in btnList" :key="index">
      <div class="title">{{ item.title }}</div>
      <div class="btn" @click="item.event(item)" :style="{ backgroundColor: item.selected ? '#2D8CF0' : '#323131' }">
        <img :src="item.icon" alt="icon">
      </div>
    </li>
    <li class="s-btn">
      <div class="title">取消编辑</div>
      <div class="btn cancel-edit" @click="cencalEdit">
        <CloseOutlined />
      </div>
    </li>
    <li class="s-btn">
      <div class="title">确认编辑</div>
      <div class="btn confirm-edit" @click="confirmEdit">
        <CheckOutlined />
      </div>
    </li>
  </ul>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { useMyStore } from '/@/store'
import axios from 'axios'
import JSZIP from 'jszip'
import { saveAs } from 'file-saver'
import { message } from 'ant-design-vue'
import {
  CloseOutlined,
  CheckOutlined
} from '@ant-design/icons-vue'
// 初始化jszip
const JsZip = new JSZIP()
const store = useMyStore()
@@ -20,78 +42,328 @@
// 点位按钮list
interface sBtn {
  key: string,
  icon: string,
  title: string,
  event: Function
  event: Function,
  selected?: boolean
}
// 保存当前选择的kmz文件
const kmzFileZip = ref()
// 当前选中的位置
const currentPosition = ref()
const curKmzName = ref<string>('')
// 当前kmzpath
const curKmzPath = ref<string>('')
const btnList = ref<sBtn[]>([
  {
    key: 'startRecord',
    icon: getResource('waylinetool/camera-on.png'),
    title: '开始录像',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('startRecord', {
        payloadPositionIndex: 0,
        fileSuffix: `_${new Date().toLocaleString()}`,
        payloadLensIndex: 'zoom',
        useGlobalPayloadLensIndex: 0
      })
    }
  },
  {
    key: 'stopRecord',
    icon: getResource('waylinetool/camera-off.png'),
    title: '停止录像',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('stopRecord', {
        payloadPositionIndex: 0,
        payloadLensIndex: 'zoom'
      })
    }
  },
  // {
  //   key: '',
  //   icon: getResource('waylinetool/shoot1.png'),
  //   title: '开始等时间隔拍照',
  //   event: (obj: sBtn) => {
  //     obj.selected = !obj.selected
  //     editKmlFile()
  //   }
  // },
  // {
  //   key: 'shootType',
  //   icon: getResource('waylinetool/shoot2.png'),
  //   title: '开始等距间隔拍照',
  //   event: (obj: sBtn) => {
  //     obj.selected = !obj.selected
  //     editKmlFile()
  //   }
  // },
  // {
  //   key: '',
  //   icon: getResource('waylinetool/shoot3.png'),
  //   title: '结束间隔拍照',
  //   event: (obj: sBtn) => {
  //     obj.selected = !obj.selected
  //     editKmlFile()
  //   }
  // },
  {
    icon: '',
    title: '开始等时间隔拍照',
    event: () => {}
  },
  {
    icon: '',
    title: '开始等距间隔拍照',
    event: () => {}
  },
  {
    icon: '',
    title: '结束间隔拍照',
    event: () => {}
  },
  {
    key: 'hover',
    icon: getResource('waylinetool/xt.png'),
    title: '悬停',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('hover', {
        hoverTime: 10
      })
    }
  },
  {
    icon: '',
    key: 'rotateYaw',
    icon: getResource('waylinetool/droneyaw.png'),
    title: '飞行器偏航角',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('rotateYaw', {
        aircraftHeading: 0,
        aircraftPathMode: 'clockwise'
      })
    }
  },
  {
    icon: '',
    key: 'gimbalRotate',
    icon: getResource('waylinetool/holderyaw.png'),
    title: '云台偏航角',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('gimbalRotate', {
        payloadPositionIndex: 0,
        gimbalHeadingYawBase: 'north',
        gimbalRotateMode: 'relativeAngle',
        gimbalPitchRotateEnable: 0,
        gimbalPitchRotateAngle: 0.4,
        gimbalRollRotateEnable: 0,
        gimbalRollRotateAngle: 0.4,
        gimbalYawRotateEnable: 0,
        gimbalYawRotateAngle: 0.4,
        gimbalRotateTimeEnable: 0,
        gimbalRotateTime: 5
      })
    }
  },
  // {
  //   key: '',
  //   icon: getResource('waylinetool/holdertilt.png'),
  //   title: '云台俯仰角',
  //   event: (obj: sBtn) => {
  //     obj.selected = !obj.selected
  //     editKmlFile('gimbalRotate', {
  //       payloadPositionIndex: 0,
  //       gimbalHeadingYawBase: 'north',
  //       gimbalRotateMode: 'relativeAngle',
  //       gimbalPitchRotateEnable: 0,
  //       gimbalPitchRotateAngle: 0.4,
  //       gimbalRollRotateEnable: 0,
  //       gimbalRollRotateAngle: 0.4,
  //       gimbalYawRotateEnable: 0,
  //       gimbalYawRotateAngle: 0.4,
  //       gimbalRotateTimeEnable: 0,
  //       gimbalRotateTime: 5
  //     })
  //   }
  // },
  {
    icon: '',
    title: '云台俯仰角',
    event: () => {}
  },
  {
    key: 'takePhoto',
    icon: getResource('waylinetool/camera.png'),
    title: '拍照',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('takePhoto', {
        payloadPositionIndex: 0,
        fileSuffix: `_${new Date().toLocaleString()}`,
        payloadLensIndex: 'zoom',
        useGlobalPayloadLensIndex: 0
      })
    }
  },
  {
    key: 'zoom',
    icon: getResource('waylinetool/fd.png'),
    title: '相机变焦',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('zoom', {
        payloadPositionIndex: 0,
        focalLength: 2
      })
    }
  },
  {
    key: 'customDirName',
    icon: getResource('waylinetool/create-file.png'),
    title: '创建文件夹',
    event: () => {}
    event: (obj: sBtn) => {
      obj.selected = !obj.selected
      editKmlFile('customDirName', {
        payloadPositionIndex: 0,
        directoryName: '新建文件夹'
      })
    }
  }
])
const waylineAbout = computed(() => {
  console.log(store.state.waylineTool)
  return store.state.waylineTool
})
const readKmzFile = (kmzPath: string) => {
  // return axios.get(kmzPath, { responseType: 'arraybuffer' })
  return axios({
    method: 'GET',
    url: kmzPath,
    headers: {
      'X-Auth-Token': window.localStorage.getItem('x-auth-token')
    },
    responseType: 'arraybuffer'
  })
    .then(fileRes => fileRes.data)
    .then(kmzData => JsZip.loadAsync(kmzData)) // 解压kmz文件
    .then(kmzZip => kmzZip)
    .catch(() => message.error('请求kmz文件失败!!'))
}
watch(() => store.state.waylineTool.kmzPath, (n: string) => {
  curKmzPath.value = n
  const arr = n.split('/')
  curKmzName.value = decodeURIComponent(arr[arr.length - 1])
  readKmzFile(n).then(kmzFile => {
    kmzFileZip.value = kmzFile
  })
}, {
  deep: true
})
watch(() => store.state.waylineTool.position, (position: number) => {
  currentPosition.value = position
  eventLight()
}, {
  deep: true
})
// 事件高亮
const eventLight = () => {
  // 已经添加的事件高亮
  btnList.value.forEach(item => { item.selected = false })
  kmzFileZip.value.file(/\.kml$/i)[0].async('text').then((content: string) => {
    const regx = /<Placemark>([\s\S]*?)<\/Placemark>/g
    const points = content.match(regx) || []
    const funcRegx = /<wpml:actionActuatorFunc>([\s\S]*?)<\/wpml:actionActuatorFunc>/g
    const funcs = (points[currentPosition.value] || '').match(funcRegx)
    const funcNameRegx = /<wpml:actionActuatorFunc>(.*?)<\/wpml:actionActuatorFunc>/
    btnList.value.forEach(sBtn => {
      funcs?.forEach((func: string) => {
        const funcName = func.match(funcNameRegx) || []
        if (funcName[1] === sBtn.key) {
          sBtn.selected = true
        }
      })
    })
  })
}
// 创建kml模版
const createActionKML = (actionId: string, actionType: string, param: any) => {
  let paramKML = ''
  Object.keys(param).forEach((key: string) => {
    paramKML += `
      <wpml:${key}>${param[key]}</wpml:${key}>
    `
  })
  const template = `
    <wpml:action>
      <wpml:actionId>${actionId}</wpml:actionId>
      <wpml:actionActuatorFunc>${actionType}</wpml:actionActuatorFunc>
      <wpml:actionActuatorFuncParam>
        ${paramKML}
      </wpml:actionActuatorFuncParam>
    </wpml:action>
  `
  return template
}
// 修改template.kml文件
const editKmlFile = (eventType: string, eventParam: any) => {
  // kmzZip文件处理
  const kmlFile = kmzFileZip.value.file(/\.kml$/i)[0]
  kmlFile.async('text').then((kmlText: string) => {
    const regx = /<Placemark>([\s\S]*?)<\/Placemark>/g
    const points = kmlText.match(regx) || []
    // 当前要修改的字段
    // const curSelected: string | any = points?.find(item => item.includes(currentPosition.value.join(',')))
    const curSelected: string | any = points[currentPosition.value]
    // 查找actionGroup
    const actionGroupReg = /<wpml:actionGroup>([\s\S]*?)<\/wpml:actionGroup>/g
    const actionGroup = curSelected.match(actionGroupReg) ? curSelected.match(actionGroupReg)[0] : ''
    // 查找action
    const actionReg = /<wpml:action>([\s\S]*?)<\/wpml:action>/g
    const actions = actionGroup.match(actionReg) || []
    // 判断kml文件中是否存在当前事件
    const actionIndex = actions.findIndex((action: string) => action.includes(eventType))
    if (actionIndex === -1) {
      // 创建事件
      const actionKML = createActionKML(actions.length, eventType, eventParam)
      actions.push(actionKML)
    } else {
      // 移除当前存在的事件
      actions.splice(actionIndex, 1)
    }
    let replacedActionsKML = ''
    // 创建actionGroup
    const actionGroupKML = `
      <wpml:actionGroup>
        ${actions.join('')}
      </wpml:actionGroup>
    `
    // 判断是否存在事件组
    if (!actionGroup) {
      const index = curSelected.indexOf('</Placemark>')
      replacedActionsKML = curSelected.slice(0, index) + actionGroupKML + curSelected.slice(index)
    } else {
      // 替换原有的action
      // replacedActionsKML = curSelected.replace(actionReg, actions.join(''))
      replacedActionsKML = curSelected.replace(actionGroupReg, actionGroupKML)
    }
    // Placemark代替
    const replacedTemplateKML = kmlText.replace(curSelected, replacedActionsKML)
    // 转成unit8格式
    // const textEncoder = new TextEncoder()
    // const uint8Array = textEncoder.encode(replacedTemplateKML)
    JsZip.file('wpmz/template.kml', replacedTemplateKML)
    kmzFileZip.value.file('wpmz/template.kml', replacedTemplateKML)
    // JsZip.generateAsync({ type: 'blob' }).then(content => {
    //   saveAs(content, curKmzName.value)
    // })
  })
}
const cencalEdit = () => {
  readKmzFile(curKmzPath.value).then(kmzFile => {
    kmzFileZip.value = kmzFile
    eventLight()
  })
}
const confirmEdit = () => {
  JsZip.generateAsync({ type: 'blob' }).then(content => {
    saveAs(content, curKmzName.value)
  })
}
</script>
<style lang="scss" scoped>
@@ -100,13 +372,16 @@
  right: 75px;
  top: 50%;
  transform: translateY(-50%);
  .s-btn {
    margin-bottom: 15px;
    display: flex;
    align-items: center;
    &:last-child {
      margin: 0;
    }
    .title {
      width: 130px;
      text-align: right;
@@ -115,6 +390,7 @@
      text-shadow: 2px 2px 2px #000;
      font-weight: bold;
    }
    .btn {
      width: 34px;
      height: 34px;
@@ -123,10 +399,29 @@
      display: flex;
      justify-content: center;
      align-items: center;
      img {
        width: 20px;
      }
    }
  }
  .cancel-edit {
    font-size: 20px;
    color: #fff;
    &:hover {
      background-color: #FF4D4F;
    }
  }
  .confirm-edit {
    font-size: 20px;
    color: #fff;
    &:hover {
      background-color: #1777FF;
    }
  }
}
</style>
src/pages/page-web/projects/wayline.vue
@@ -110,7 +110,12 @@
          </div>
          <div class="graph-right">
            <div v-for="(event, index) in item.eventList" :key="index" class="s-event-icon">
              <img :src="event.icon" alt="icon" />
              <a-tooltip placement="top">
                <template #title>
                  {{ event.name }}
                </template>
                <img :src="event.icon" alt="icon" />
              </a-tooltip>
            </div>
          </div>
        </li>
@@ -136,7 +141,6 @@
import { getRoot } from '/@/root'
import * as Cesium from 'cesium'
import { cesiumOperation } from '/@/hooks/use-cesium-tsa'
import { gcj02towgs84, wgs84togcj02 } from '/@/vendors/coordtransform'
import axios from 'axios'
import JSZIP from 'jszip'
// 初始化jszip
@@ -178,7 +182,7 @@
const isPointListOpen = ref<boolean>(false)
const tragetPointArr = ref<{
  position: Cesium.Cartesian3,
  eventList: string[]
  eventList: any[]
}[]>([])
const selectedPoint = ref<number | null>(null)
@@ -215,7 +219,8 @@
  },
  {
    key: 'gimbalRotate',
    name: '旋转云台'
    name: '旋转云台',
    icon: getResource('waylinetool/holderyaw.png'),
  },
  {
    key: 'rotateYaw',
@@ -245,6 +250,7 @@
  }, 1000)
  console.log(workspaceId.value)
})
onUnmounted(() => {
  if (kmlDataSource) {
    global.$viewer.dataSources.remove(kmlDataSource)
@@ -400,7 +406,7 @@
  store.commit('SET_WAYLINE_INFO', {
    isShow: true,
    wayline: currentWayLine,
    position: position
    position: index
  })
  if (getEntityById('clickBox')) {
    removeById('clickBox')