GuLiMmo
2024-03-14 46442b030db95ae97b754711c6dc786376c3a6e1
update: kmz修改保持方式修改
9 files modified
751 ■■■■■ changed files
env/.env.dev 2 ●●● patch | view | raw | blame | history
env/.env.production 2 ●●● patch | view | raw | blame | history
package.json 4 ●●●● patch | view | raw | blame | history
src/pages/page-web/projects/components/route-edit/components/setting.vue 272 ●●●●● patch | view | raw | blame | history
src/pages/page-web/projects/components/route-edit/index.vue 76 ●●●●● patch | view | raw | blame | history
src/utils/cesium/kmz.ts 148 ●●●●● patch | view | raw | blame | history
src/utils/cesium/use-kmz-tsa.ts 185 ●●●● patch | view | raw | blame | history
src/utils/cesium/use-map-draw.ts 12 ●●●● patch | view | raw | blame | history
vite.config.ts 50 ●●●● patch | view | raw | blame | history
env/.env.dev
@@ -1,5 +1,5 @@
VITE_API_URL = 'http://139.196.74.78:6789'
VITE_MEDIAPANEL_API_URL = 'https://dev.jxpskj.com:8026/cloud-bucket',
VITE_MEDIAPANEL_API_URL = 'https://dev.jxpskj.com:9000/cloud-bucket'
VITE_WS_API_URL = 'ws://139.196.74.78: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_MEDIAPANEL_API_URL = 'https://dev.jxpskj.com:9000/cloud-bucket'
VITE_WS_API_URL = 'wss://dev.jxpskj.com:36789/api/v1/ws'
VITE_BASE_API = '/drone-api'
package.json
@@ -14,7 +14,6 @@
  "dependencies": {
    "@amap/amap-jsapi-loader": "^1.0.1",
    "@ant-design/icons-vue": "^6.0.1",
    "@mapbox/togeojson": "^0.16.2",
    "@turf/turf": "^6.5.0",
    "@vitejs/plugin-legacy": "^1.6.2",
    "agora-rtc-sdk-ng": "^4.12.1",
@@ -42,7 +41,8 @@
    "vue-i18n": "^9.1.6",
    "vue-router": "4",
    "vuex": "^4.0.2",
    "vuex-persistedstate": "^4.1.0"
    "vuex-persistedstate": "^4.1.0",
    "xml-js": "^1.6.11"
  },
  "devDependencies": {
    "@types/crypto-js": "^4.1.1",
src/pages/page-web/projects/components/route-edit/components/setting.vue
@@ -5,19 +5,20 @@
        <div class="setting-content">
          <div class="start-point common">
            <div class="title">起飞点</div>
            <div class="value">{{ missionConfigSetting.takeOffRefPoint ? '已设置' : '未设置' }}</div>
            <div class="value">
              {{ xmlTemplate.missionConfig?.takeOffRefPoint['#text'] !== '' ? '已设置' : '未设置' }}
            </div>
          </div>
          <div class="photo-setting common">
            <div class="title">拍照设置</div>
            <ul class="select-box">
              <li
                class="select-item"
                :style="{
                  '--settingColor': folderSetting.payloadParam.imageFormat.includes(item.value) ? '#409eff' : '#3c3c3c',
                }"
                v-for="item in photoSetting"
                :key="item.value"
                @click="selectPhotoSetting(item.value)">
              <li class="select-item" v-for="item in photoSetting" :key="item.value" :style="{
      '--settingColor': xmlTemplate.Folder.payloadParam?.imageFormat['#text']
        .split(',')
        .includes(item.value)
        ? '#409eff'
        : '#3c3c3c',
    }" @click="selectPhotoSetting(item.value)">
                {{ item.title }}照片
              </li>
            </ul>
@@ -37,7 +38,7 @@
          <div class="climb-mode common">
            <a-tooltip placement="right" :title="tipDescriptionEnums.climbModeTip">
              <div class="mode-box">
                <a-radio-group v-model:value="missionConfigSetting.flyToWaylineMode" button-style="solid">
                <a-radio-group v-model:value="xmlTemplate.missionConfig.flyToWaylineMode['#text']" button-style="solid">
                  <a-radio-button value="safely">垂直爬升</a-radio-button>
                  <a-radio-button value="pointToPoint">倾斜爬升</a-radio-button>
                </a-radio-group>
@@ -49,9 +50,9 @@
                <ul class="parameter-btn">
                  <li @click="numberControls(true, 100, 'takeOffSecurityHeight')">+100</li>
                  <li @click="numberControls(true, 10, 'takeOffSecurityHeight')">+10</li>
                  <li>
                    <span>{{ missionConfigSetting.takeOffSecurityHeight }}</span
                    >m
                  <li class="parameter-input">
                    <a-input-number :min="2" :max="1500"
                      v-model:value="xmlTemplate.missionConfig.takeOffSecurityHeight['#text']"></a-input-number>m
                  </li>
                  <li @click="numberControls(false, 100, 'takeOffSecurityHeight')">-100</li>
                  <li @click="numberControls(false, 10, 'takeOffSecurityHeight')">-10</li>
@@ -61,32 +62,27 @@
          </div>
          <!-- 高度模式 -->
          <div class="height-mode common">
            <a-tooltip
              placement="right"
              :title="tipDescriptionEnums[folderSetting.waylineCoordinateSysParam.heightMode]">
            <a-tooltip placement="right"
              :title="tipDescriptionEnums[xmlTemplate.Folder.waylineCoordinateSysParam.heightMode['#text']]">
              <div class="title">航线高度模式</div>
              <div class="mode-box">
                <a-radio-group v-model:value="folderSetting.waylineCoordinateSysParam.heightMode" button-style="solid">
                <a-radio-group v-model:value="xmlTemplate.Folder.waylineCoordinateSysParam.heightMode['#text']"
                  button-style="solid">
                  <a-radio-button v-for="item in heightModeSetting" :key="item.value" :value="item.value">{{
                    item.title
                  }}</a-radio-button>
      item.title
    }}</a-radio-button>
                </a-radio-group>
              </div>
              <div class="parameter-tool">
                <div class="example-img">
                  <img
                    :src="
                      folderSetting.waylineCoordinateSysParam.heightMode === heightModeSetting[0].value
                        ? heightModeSetting[0].img
                        : heightModeSetting[1].img
                    "
                    alt="height-img" />
                  <img :src="true ? heightModeSetting[0].img : heightModeSetting[1].img" alt="height-img" />
                </div>
                <div class="parameter-btn">
                  <li @click="numberControls(true, 100, 'globalHeight')">+100</li>
                  <li @click="numberControls(true, 10, 'globalHeight')">+10</li>
                  <li>
                    <span>{{ Math.round(folderSetting.globalHeight) }}</span> m
                  <li class="parameter-input">
                    <a-input-number :min="0" :max="10000"
                      v-model:value="xmlTemplate.Folder.globalHeight['#text']"></a-input-number>m
                  </li>
                  <li @click="numberControls(false, 100, 'globalHeight')">-100</li>
                  <li @click="numberControls(false, 10, 'globalHeight')">-10</li>
@@ -100,11 +96,9 @@
            <div class="speed-box">
              <div class="subtract" @click="numberControls(false, 1, 'autoFlightSpeed')">-</div>
              <div class="text">
                <a-input-number
                  :min="1"
                  :max="15"
                  class="value"
                  v-model:value="folderSetting.autoFlightSpeed"></a-input-number>
                <a-input-number v-model:value="xmlTemplate.Folder.autoFlightSpeed['#text']" :min="1" :max="15"
                  class="value">
                </a-input-number>
                m / s
              </div>
              <div class="add" @click="numberControls(true, 1, 'autoFlightSpeed')">+</div>
@@ -120,7 +114,7 @@
<script setup lang="ts">
import { reactive, defineProps } from 'vue'
import { tipDescriptionEnums } from './enums'
import useKmzTsa, { kmlStr } from '/@/utils/cesium/use-kmz-tsa'
import useKmzTsa, { kmlStr, template as xmlTemplate } from '/@/utils/cesium/use-kmz-tsa'
import useMapDraw from '/@/utils/cesium/use-map-draw'
import { getKmlParams } from '/@/utils/cesium/kmz'
import { cesiumOperation } from '/@/hooks/use-cesium-tsa'
@@ -132,15 +126,14 @@
const mapDraw = useMapDraw('', '', '', global)
const { removeAllDataSource, removeAllPoint } = cesiumOperation()
const kmzUtils = useKmzTsa()
interface setting {
  imageFormat: string[]
}
const props = defineProps({
  loadCompleted: Boolean,
})
const getResource = (name: string) => {
  return new URL(`/src/assets/icons/${name}`, import.meta.url).href
}
const getSvgResource = (name: string) => {
  return new URL(`/src/assets/svg/${name}`, import.meta.url).href
}
@@ -148,37 +141,12 @@
const climbImage = getSvgResource('climb.svg')
const imgs = []
const missionConfigSetting = reactive<missionConfig | any>({
  flyToWaylineMode: 'safely',
  // 航线结束动作
  finishAction: 'goHome',
  // 失控是否继续执行航线
  exitOnRCLost: 'goContinue',
  // 失控动作
  executeRCLostAction: 'goBack',
  // 安全起飞高度
  takeOffSecurityHeight: 20,
  // 全局航线速度
  globalTransitionalSpeed: 10,
  // 参考起飞点
  takeOffRefPoint: '',
  // 参考起飞点海拔高度
  takeOffRefPointAGLHeight: 0,
  // 全局返航高度
  globalRTHHeight: 100,
  droneInfo: {
    droneEnumValue: 67,
    droneSubEnumValue: 0,
  },
  payloadInfo: {
    payloadEnumValue: 52,
    payloadSubEnumValue: 1,
    payloadPositionIndex: 0,
  },
})
// 拍照设置
const photoSetting = reactive([
interface photoSetting {
  title: string
  value: string
}
const photoSetting = reactive<photoSetting[]>([
  { title: '广角', value: 'wide' },
  { title: '变焦', value: 'zoom' },
  { title: '红外', value: 'ir' },
@@ -198,128 +166,38 @@
  },
])
// folder设置
const folderSetting = reactive<Folder | any>({
  templateType: 'waypoint',
  templateId: 0,
  waylineCoordinateSysParam: {
    coordinateMode: 'WGS84',
    heightMode: 'EGM96',
  },
  autoFlightSpeed: 10,
  globalHeight: 100,
  payloadParam: {
    payloadPositionIndex: 0,
    imageFormat: '',
  },
  caliFlightEnable: 0,
  gimbalPitchMode: 'manual',
})
// 计算
const numberControls = (isAdd: boolean, computeVal: number, key: string) => {
  if (isAdd) {
    if (missionConfigSetting[key]) {
      missionConfigSetting[key] += computeVal
    } else {
      folderSetting[key] += computeVal
    }
  } else {
    if (missionConfigSetting[key]) {
      missionConfigSetting[key] -= computeVal
    } else {
      if (folderSetting[key] - computeVal <= 0) {
        return message.warn('飞行高度较低!!!!')
      }
      folderSetting[key] -= computeVal
    }
  let obj = { '#text': 0 }
  if (key === 'takeOffSecurityHeight') {
    obj = xmlTemplate.value.missionConfig.takeOffSecurityHeight
  }
  if (key === 'globalHeight') {
    const entities = mapDraw.kmlEntities.value
    removeAllDataSource()
    removeAllPoint()
    mapDraw.drawWayline(entities, kmlStr.value)
    obj = xmlTemplate.value.Folder.globalHeight
  }
}
// 获取机场默认值
watch(
  () => props.loadCompleted,
  (val) => {
    if (val) {
      getDefaultValue(missionConfigSetting)
      getDefaultValue(folderSetting)
    }
  },
)
const getDefaultValue = (setting: any) => {
  Object.keys(setting).forEach((key: string) => {
    const param: RegExpMatchArray | any = getKmlParams(kmlStr.value, true, {
      name: key,
      findRegx: '([\\s\\S]*?)',
    })
    if (Object.prototype.toString.call(setting[key]) === '[object Object]') {
      const value: RegExpMatchArray | any = getKmlParams(param[0], true, {
        name: key,
        findRegx: '([\\s\\S]*?)',
      })
      Object.keys(setting[key]).forEach((v) => {
        const keyValue: RegExpMatchArray | any = getKmlParams(value[1], true, {
          name: v,
          findRegx: '([\\s\\S]*?)',
        })
        if (!Number(keyValue[1])) {
          setting[key][v] = keyValue[1]
        } else {
          setting[key][v] = Number(keyValue[1])
        }
      })
    } else {
      if (!Number(param[1])) {
        setting[key] = param[1]
      } else {
        setting[key] = Number(param[1])
      }
    }
  })
  return setting
}
// 全局设置写入kmz文件
watch(
  () => missionConfigSetting,
  (settingVal) => {
    if (!props.loadCompleted) return
    kmzUtils.edit(settingVal, 'missionConfig', true)
  },
  {
    deep: true,
  },
)
// folder设置写入kmz文件
watch(
  () => folderSetting,
  (newVal) => {
    if (!props.loadCompleted) return
    kmzUtils.edit(newVal, 'Folder', true)
  },
  {
    deep: true,
  },
)
// 拍照设置
const selectPhotoSetting = (value: string) => {
  const arr: string[] = folderSetting.payloadParam.imageFormat.split(',')
  const index: number = arr.findIndex((item) => item === value)
  if (index !== -1) {
    arr.splice(index, 1)
  if (key === 'autoFlightSpeed') {
    obj = xmlTemplate.value.Folder.autoFlightSpeed
  }
  let value = Number(obj['#text'])
  if (isAdd) {
    value += computeVal
  } else {
    arr.push(value)
    value -= computeVal
  }
  folderSetting.payloadParam.imageFormat = arr.join(',')
  obj['#text'] = value
}
// 拍照选择
const selectPhotoSetting = (value: string) => {
  const imageFormat = xmlTemplate.value.Folder.payloadParam.imageFormat
  const imageFormatArr = imageFormat['#text'].split(',')
  if (imageFormatArr.includes(value)) {
    const index = imageFormatArr.findIndex((item: string) => item === value)
    imageFormatArr.splice(index, 1)
  } else {
    imageFormatArr.push(value)
  }
  xmlTemplate.value.Folder.payloadParam.imageFormat['#text'] = imageFormatArr.join(',')
}
</script>
@@ -349,6 +227,7 @@
    .start-point {
      display: flex;
      justify-content: flex-start;
      .value {
        margin-left: auto;
        color: #409eff;
@@ -472,6 +351,31 @@
              }
            }
          }
          .parameter-input {
            display: flex;
            align-items: center;
            :deep() {
              .ant-input-number {
                background-color: transparent;
                color: #409eff;
                font-size: 23px;
                border: 0;
                .ant-input-number-handler-wrap {
                  display: none;
                }
                .ant-input-number-input-wrap {
                  input {
                    font-weight: bold;
                    text-align: center;
                  }
                }
              }
            }
          }
        }
      }
    }
@@ -526,8 +430,10 @@
      }
    }
  }
  :deep() {
    .ant-popover-content {
      .ant-popover-arrow,
      .ant-popover-inner {
        background-color: #101010;
src/pages/page-web/projects/components/route-edit/index.vue
@@ -73,16 +73,16 @@
</template>
<script setup lang="ts">
import _ from 'lodash'
import _, { template } from 'lodash'
import * as Cesium from 'cesium'
import setting from './components/setting.vue'
import useKmzTsa, { kmlStr } from '/@/utils/cesium/use-kmz-tsa'
import useKmzTsa, { kmlStr, template as xmlTemplate } from '/@/utils/cesium/use-kmz-tsa'
import { ref, reactive, defineEmits, defineProps, watch, onMounted } from 'vue'
import { ArrowLeftOutlined, CaretDownOutlined, SaveOutlined } from '@ant-design/icons-vue'
import { cesiumOperation } from '/@/hooks/use-cesium-tsa'
import { useMyStore } from '/@/store'
import ImageTrailMaterial from '/@/utils/cesium/ImageTrailMaterial'
import useMapDraw, { kmlEntities as globalEntities } from '/@/utils/cesium/use-map-draw'
import useMapDraw, { kmlEntities as globalEntities, selectPointIndex as pointIdx } from '/@/utils/cesium/use-map-draw'
import { getKmlParams } from '/@/utils/cesium/kmz'
import { getHaeHeight } from '/@/utils/cesium/mapUtils'
const store = useMyStore()
@@ -207,8 +207,10 @@
    global.$viewer.entities.remove(nextPointEntity)
    kmlEntities.value.forEach((entity: Cesium.Entity | any, i: number) => {
      entity.billboard.image = createBillboard(i + 1, '#61d396')
      entity.label.show = false
    })
    selectPointIndex.value = null
    pointIdx.value = null
    return
  }
  if (prevPointEntity) {
@@ -242,11 +244,14 @@
  kmlEntities.value.forEach((entity: Cesium.Entity | any, i: number) => {
    if (i === index) {
      entity.billboard.image = createBillboard(index + 1, '#f3be4f')
      entity.label.show = true
    } else {
      entity.billboard.image = createBillboard(i + 1, '#61d396')
      entity.label.show = false
    }
  })
  selectPointIndex.value = index
  pointIdx.value = index
}
// 添加右键事件,弹出窗口
@@ -331,59 +336,82 @@
    const longitude = Cesium.Math.toDegrees(c2Postion.longitude)
    const latitude = Cesium.Math.toDegrees(c2Postion.latitude)
    // 获取全局高度
    const globalHeight: any = getKmlParams(kmlStr.value, true, {
      name: 'globalHeight',
      findRegx: '([\\s\\S]*?)',
    })
    const height = Number(globalHeight[1])
    const globalHeight: string = xmlTemplate.value.Folder.globalHeight['#text']
    const height = Number(globalHeight)
    // 右键点击的位置
    const createPointPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
    const entity: Cesium.Entity = global.$viewer.entities.add({
      position: createPointPosition,
    })
    const posterHeightRegStr = getKmlParams(kmlStr.value, true, {
      name: 'takeOffRefPointAGLHeight',
      findRegx: '([\\s\\S]*?)',
    }) || ['', 0]
    const posterHeight = Number(posterHeightRegStr[1])
    // 加入kml文件中
    // 起飞点海拔
    const takeOffRefPointAGLHeight = xmlTemplate.value.missionConfig.takeOffRefPointAGLHeight
    const posterHeight = Number(takeOffRefPointAGLHeight['#text'])
    // 当前点位个数
    const points = xmlTemplate.value.Folder.Placemark
    // 加入到解析的JSON对象中
    const setting = {
      Point: {
        coordinates: `${longitude},${latitude}`,
        coordinates: { '#text': `${longitude.toFixed(6)},${latitude.toFixed(6)}` },
      },
      index: tragetPointArr.value.length,
      height,
      ellipsoidHeight: height - posterHeight,
      index: { '#text': points.length },
      height: { '#text': height },
      ellipsoidHeight: { '#text': height - posterHeight },
      isRisky: { '#text': 0 },
      useGlobalHeadingParam: { '#text': 1 },
      useGlobalHeight: { '#text': 1 },
      useGlobalSpeed: { '#text': 1 },
      useGlobalTurnParam: { '#text': 1 },
      useStraightLine: { '#text': 1 },
      waypointHeadingParam: {
        waypointHeadingAngle: { '#text': 0 },
        waypointHeadingMode: { '#text': 'followWayline' },
        waypointHeadingPathMode: { '#text': 'followBadArc' },
        waypointHeadingPoiIndex: { '#text': 0 },
        waypointPoiPoint: { '#text': '0.000000,0.000000,0.000000' },
      },
      waypointSpeed: { '#text': 10 },
      waypointTurnParam: {
        waypointTurnDampingDist: { '#text': 0.2 },
        waypointTurnMode: { '#text': 'toPointAndStopWithDiscontinuityCurvature' },
      },
    }
    removeAllDataSource()
    removeAllPoint()
    if (className === 'finally-point') {
      kmzUtils.writePoint(setting, 'add')
      globalEntities.value.push(entity)
      xmlTemplate.value.Folder.Placemark.push(setting)
      tragetPointArr.value.push({
        position: createPointPosition,
        eventList: [],
        isUseGlobalHeight: Boolean(setting.useGlobalHeight['#text']),
      })
    }
    if (className === 'add-prev-point') {
      kmzUtils.writePoint(setting, 'prev', selectPointIndex.value)
      globalEntities.value.splice(selectPointIndex.value, 0, entity)
      xmlTemplate.value.Folder.Placemark.splice(selectPointIndex.value, 0, setting)
      tragetPointArr.value.splice(selectPointIndex.value, 0, {
        position: createPointPosition,
        eventList: [],
        isUseGlobalHeight: Boolean(setting.useGlobalHeight['#text']),
      })
    }
    if (className === 'add-next-point') {
      kmzUtils.writePoint(setting, 'next', selectPointIndex.value)
      const index = _.cloneDeep(selectPointIndex.value + 1)
      globalEntities.value.splice(index, 0, entity)
      xmlTemplate.value.Folder.Placemark.splice(index, 0, setting)
      tragetPointArr.value.splice(index, 0, {
        position: createPointPosition,
        eventList: [],
        isUseGlobalHeight: Boolean(setting.useGlobalHeight['#text']),
      })
    }
    mapDraw.drawWayline(globalEntities.value, kmlStr.value)
    // clearCesiumMap()
    // createMapMarker(undefined, [...kmlEntities.value, entity])
    // 更新点位序号
    xmlTemplate.value.Folder.Placemark.forEach((placemark: { index: { [x: string]: number } }, index: number) => {
      placemark.index['#text'] = index
    })
    const xmlStr = kmzUtils.generateXML(xmlTemplate.value)
    mapDraw.drawWayline(globalEntities.value, xmlStr)
  })
}
src/utils/cesium/kmz.ts
@@ -1,10 +1,7 @@
import axios from 'axios'
import JsZip from 'jszip'
import toGeoJson from '@mapbox/togeojson'
// import Xml2Json from 'xml2json'
// const xmlJson = new Xml2Json()
const noWmpl: string[] = ['Folder', 'Placemark', 'Point', 'coordinates']
const noWmpl: string[] = ['Document', 'Folder', 'Placemark', 'Point', 'coordinates']
/**
 * @description: 读取kmz文件解析出kml文件
@@ -73,16 +70,6 @@
  return paramGroup
}
export const insertChar = (str: string, index: number, char: string) => {
  if (index > str.length) {
    return str + char
  } else if (index < 0) {
    return char + str
  } else {
    return str.slice(0, index) + char + str.slice(index)
  }
}
export const getTagNameFromXml = (xmlString: string): string | null => {
  const parser = new DOMParser()
  const xmlDoc = parser.parseFromString(xmlString, 'text/xml')
@@ -94,61 +81,110 @@
}
// 将xml转为json
const deepParse = (xmlDoc: Document, tagName: string) => {
  const xmlObj: { [key: string]: any } = {}
  const rootDom = xmlDoc.getElementsByTagName(tagName)[0]
  for (let i = 0; i < rootDom.children.length; i++) {
    const child = rootDom.children[i]
    const childName = child.nodeName.replace('wpml:', '')
    if (child.children.length > 0) {
      xmlObj[childName] = deepParse(xmlDoc, child.nodeName)
    } else {
      if (xmlObj[childName] !== undefined) {
        if (!Array.isArray(xmlObj[childName])) {
          xmlObj[childName] = [xmlObj[childName]]
const deepParse = (
  xml:
    | {
        nodeType: number
        childNodes: string | any[]
        nodeValue: string
        attributes: { length: number; item: (arg0: number) => any }
      }
    | any,
) => {
  let obj: any = {}
  // 如果是文档节点,遍历其子节点
  if (xml.nodeType === 1) {
    // element node
    // 处理子节点
    if (xml.childNodes.length > 0) {
      for (let i = 0; i < xml.childNodes.length; i++) {
        const item = xml.childNodes[i]
        const nodeName = item.nodeName.replace('wpml:', '')
        // 检查是否是文本节点
        if (item.nodeType === 3) {
          // text node
          // 处理文本节点的内容
          if (item.nodeValue.trim() !== '') {
            obj[nodeName] = item.nodeValue.trim()
          }
        } else if (item.nodeType === 1) {
          // element node
          // 递归调用处理子节点
          if (obj[nodeName] === undefined) {
            obj[nodeName] = deepParse(item)
          } else {
            if (!obj[nodeName].push) {
              const old = obj[nodeName]
              obj[nodeName] = []
              obj[nodeName].push(old)
            }
            obj[nodeName].push(deepParse(item))
          }
        }
        xmlObj[childName].push(child.textContent)
      } else {
        const value = child.textContent?.split('\n').join('').split(' ').join('')
        xmlObj[childName.replace('wpml:', '')] = value
      }
    }
  } else if (xml.nodeType === 3) {
    // text node
    // 处理文本节点的内容
    obj = xml.nodeValue.trim()
  }
  return xmlObj
  return obj
}
export const XMLToJSON = (xmlStr: string, tagName: string) => {
/**
 * @description: XML转JSON
 * @param {string} xmlStr xml字符串
 * @return {*} string
 */
export const XMLToJSON = (xmlStr: string) => {
  const parser = new DOMParser()
  const xmlDoc = parser.parseFromString(xmlStr, 'text/xml')
  const xmlObj = deepParse(xmlDoc, tagName)
  delete xmlObj.parsererror
  const xmlObj = deepParse(xmlDoc.documentElement)
  return xmlObj
}
// json转xml
export const JSONToXML = (JSONData = {}) => {
  if (!JSONData) return ''
  let res = ''
  const JSONParse = (obj: { [x: string]: any }) => {
    for (const key in obj) {
      if (Array.isArray(obj[key])) {
        obj[key].forEach((item: any, index: any) => {
          let xmlTag = ''
          if (noWmpl.includes(key)) {
            xmlTag = `<${key} rowNum="${index}">${item}</${key}>`
/**
 * @description: JSON转XML
 * @param {any} obj 要转换的对象
 * @param {string} rootName xml根节点名称
 * @return {*} string
 */
export const JSONToXML = (obj: any, rootName?: string | undefined) => {
  let xml = ''
  const buildXml = (obj: string | any[], rootName?: string | undefined) => {
    let xml = ''
    if (Array.isArray(obj)) {
      obj.forEach((item) => {
        xml += `<${rootName}>${buildXml(item)}</${rootName}>`
      })
    } else if (typeof obj === 'object') {
      Object.keys(obj).forEach((key) => {
        if (key === '#text') {
          xml += obj[key]
        } else {
          if (key === 'Placemark') {
            xml += `${buildXml(obj[key], key)}`
          } else {
            xmlTag = `<wpml:${key} rowNum="${index}">${item}</wpml:${key}>`
            const str = !noWmpl.includes(key) ? 'wpml:' : ''
            xml += `<${str}${key}>${buildXml(obj[key], key)}</${str}${key}>`
          }
          res += xmlTag
        })
      } else if (typeof obj[key] === 'object') {
        res += (noWmpl.includes(key) ? `<${key}>` : `<wpml:${key}>`)
        JSONParse(obj[key])
        res += (noWmpl.includes(key) ? `</${key}>` : `</wpml:${key}>`)
      } else {
        res += (noWmpl.includes(key) ? `<${key}>${obj[key] || ''}</${key}>` : `<wpml:${key}>${obj[key] || ''}</wpml:${key}>`)
      }
        }
      })
    } else {
      xml += obj
    }
    return xml
  }
  JSONParse(JSONData)
  return res
  xml += `
  <?xml version="1.0" encoding="UTF-8"?>
  <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.5">
    ${buildXml(obj, rootName)}
  </kml>
  `
  return xml
}
src/utils/cesium/use-kmz-tsa.ts
@@ -1,8 +1,6 @@
import { ref, getCurrentInstance } from 'vue'
import { ref } from 'vue'
import {
  analyzeKmzFile,
  getKmlParams,
  generateKmlFormat,
  XMLToJSON,
  JSONToXML
} from '/@/utils/cesium/kmz'
@@ -18,7 +16,7 @@
const kmlStr = ref('')
const kmzFile = ref<any>(null)
const template = ref({
const template = ref<any>({
  author: '',
  createTime: '',
  updateTime: '',
@@ -91,7 +89,7 @@
    ],
  },
})
const waylines = ref({
const waylines = ref<any>({
  missionConfig: {
    flyToWaylineMode: 'safely',
    finishAction: 'goHome',
@@ -138,34 +136,6 @@
  },
})
// 点位参数
const placemark: any = {
  Point: {
    coordinates: '',
  },
  index: 0,
  executeHeight: 80,
  height: 80,
  waypointSpeed: 10,
  waypointHeadingParam: {
    waypointHeadingMode: 'followWayline',
    waypointHeadingAngle: 0,
    waypointPoiPoint: '0.000000,0.000000,0.000000',
    waypointHeadingPathMode: 'followBadArc',
    waypointHeadingPoiIndex: 0,
  },
  waypointTurnParam: {
    waypointTurnMode: 'toPointAndStopWithDiscontinuityCurvature',
    waypointTurnDampingDist: 0.2,
  },
  useGlobalHeight: 1,
  useGlobalSpeed: 1,
  useGlobalHeadingParam: 1,
  useGlobalTurnParam: 1,
  useStraightLine: 1,
  actionGroup: {},
}
const useKmzTsa = () => {
  const init = async (fileUrl: string, fileContent?: string) => {
    const fileRes = await analyzeKmzFile(fileUrl)
@@ -176,151 +146,36 @@
      return false
    }
    kmlStr.value = kmzRes
    const tplXmlJson = XMLToJSON(kmzRes)
    template.value = tplXmlJson.Document
    return kmzRes
  }
  const create = () => {
    const nowTime = new Date().getTime()
    const str = `
    <?xml version="1.0" encoding="UTF-8"?>
    <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.5">
      <Document>
        <wpml:author>17304076412</wpml:author>
        <wpml:createTime>${nowTime}</wpml:createTime>
        <wpml:updateTime>${nowTime}</wpml:updateTime>
        <wpml:missionConfig>
        </wpml:missionConfig>
        <Folder>
        </Folder>
      </Document>
    </kml>
    `
    kmlStr.value = str
  }
  const writePoint = (settingParmas: any = {}, place: string, pointNumber: number = 0) => {
    Object.keys(settingParmas).forEach((key: string) => {
      placemark[key] = settingParmas[key]
    })
    const Placemark = `<Placemark>${generateKmlFormat(placemark)}</Placemark>`
    const points: RegExpMatchArray = getKmlParams(kmlStr.value, false, {
      name: 'Placemark',
      findRegx: '([\\s\\S]*?)',
      mode: 'g',
    }) || ['']
    if (place === 'add') {
      const lastPoint = points[points.length - 1]
      const index = kmlStr.value.indexOf(lastPoint)
      const beforeStr = kmlStr.value.slice(0, index)
      const afterStr = kmlStr.value.slice(index + lastPoint.length)
      kmlStr.value = beforeStr + lastPoint + Placemark + afterStr
    }
    if (place === 'prev') {
      const currentPoint: string = points[pointNumber]
      const index = kmlStr.value.indexOf(currentPoint)
      const beforeStr = kmlStr.value.slice(0, index)
      const afterStr = kmlStr.value.slice(index + currentPoint.length)
      kmlStr.value = beforeStr + Placemark + currentPoint + afterStr
      // 更新点位序号
      const pointIndexList = getKmlParams(kmlStr.value, true, {
        name: 'index',
        findRegx: '([\\s\\S]*?)',
        mode: 'g',
      })
      pointIndexList?.forEach((pintIndex: string, index: number) => {
        kmlStr.value.replace(pintIndex, `<wpml:index>${index}</wpml:index>`)
      })
    }
    if (place === 'next') {
      const currentPoint: string = points[pointNumber + 1]
      const index = kmlStr.value.indexOf(currentPoint)
      const beforeStr = kmlStr.value.slice(0, index)
      const currentPointLength = currentPoint?.length || 0
      const afterStr = kmlStr.value.slice(index + currentPointLength)
      kmlStr.value = beforeStr + Placemark + currentPoint + afterStr
      // 更新点位序号
      const pointIndexList = getKmlParams(kmlStr.value, true, {
        name: 'index',
        findRegx: '([\\s\\S]*?)',
        mode: 'g',
      })
      pointIndexList?.forEach((pintIndex: string, index: number) => {
        kmlStr.value.replace(pintIndex, `<wpml:index>${index}</wpml:index>`)
      })
    }
  }
  const edit = (settingParmas: any = {}, keyName?: string, isKeyWpml: boolean = false) => {
    Object.keys(settingParmas).forEach((key: string) => {
      const regxVal: any = getKmlParams(kmlStr.value, true, {
        name: key,
        findRegx: '([\\s\\S]*?)',
      })
      const str = regxVal[1]
      if (str) {
        if (key === 'globalHeight') {
          const regxVal: any = getKmlParams(kmlStr.value, true, {
            name: 'height',
            findRegx: '([\\s\\S]*?)',
            mode: 'g',
          })
          const ellipsoidHeightRegx = getKmlParams(kmlStr.value, true, {
            name: 'ellipsoidHeight',
            findRegx: '([\\s\\S]*?)',
            mode: 'g',
          }) || ['']
          // 文件中的海拔高度
          const takeOffRefPointAGLHeightRegxStr = getKmlParams(kmlStr.value, true, {
            name: 'takeOffRefPointAGLHeight',
            findRegx: '([\\s\\S]*?)',
          }) || ['', '0']
          const posterHeight = Number(takeOffRefPointAGLHeightRegxStr[1])
          // 获取每个点是否使用全局高度
          const useGlobalHeightRegStr = getKmlParams(kmlStr.value, true, {
            name: 'useGlobalHeight',
            findRegx: '([\\s\\S]*?)',
            mode: 'g',
          }) || ['', '0']
          regxVal.forEach((heightStr: string, index: number) => {
            // 使用正则取值
            const getUseGlobalHeightValStr = getKmlParams(useGlobalHeightRegStr[index], true, {
              name: 'useGlobalHeight',
              findRegx: '([\\s\\S]*?)',
            }) || ['', '0']
            const isGlobalHeight = Boolean(Number(getUseGlobalHeightValStr[1]))
            if (isGlobalHeight) {
              // 替换height
              const rstr = generateKmlFormat({ height: settingParmas[key] })
              const rEllipsoidHeightStr = generateKmlFormat({ ellipsoidHeight: settingParmas[key] - posterHeight })
              kmlStr.value = kmlStr.value.replace(heightStr, rstr)
              kmlStr.value = kmlStr.value.replace(ellipsoidHeightRegx[index], rEllipsoidHeightStr)
              // 替换
            } else {
              const heightRegxVal: any = getKmlParams(heightStr, true, {
                name: 'height',
                findRegx: '([\\s\\S]*?)',
              })
            }
          })
        }
        const replaceStr = generateKmlFormat({ [key]: settingParmas[key] })
        kmlStr.value = kmlStr.value.replace(regxVal[0], replaceStr)
      } else {
        console.warn('没有找到对应的属性')
      }
    })
  }
  }
  const save = () => {
    console.log('存在问题')
  }
  const generateXML = <T>(xmlJson: T) => {
    return JSONToXML(xmlJson)
  }
  // 将wpmz/template.kml解析传来json转换回去,重新复制以便于后续绘制
  watch(() => template.value, (newobj) => {
    const xmlString = JSONToXML(newobj)
    kmlStr.value = xmlString
  }, {
    deep: true
  })
  return {
    init,
    create,
    // write,
    writePoint,
    edit,
    save,
    generateXML
  }
}
export { kmlStr, fileAuthor }
export { kmlStr, fileAuthor, template, waylines }
export default useKmzTsa
src/utils/cesium/use-map-draw.ts
@@ -4,6 +4,7 @@
import ImageTrailMaterial from '/@/utils/cesium/ImageTrailMaterial'
import { ref } from 'vue'
import { cesiumOperation } from '/@/hooks/use-cesium-tsa'
import { template as xmlTemplate } from '/@/utils/cesium/use-kmz-tsa'
const { addPolyline, getEntityById } = cesiumOperation()
const getResource = (name: string) => {
@@ -22,7 +23,7 @@
}
interface tragetPoint {
  position: Cesium.Cartesian3
  isUseGlobalHeight: boolean,
  isUseGlobalHeight: boolean
  eventList: eventParmas[]
}
@@ -110,6 +111,8 @@
// kml中的全部实体
const kmlEntities = ref<Cesium.Entity[]>([])
const selectPointIndex = ref<number | null>(null)
const useMapDraw = (
  dataSource: Cesium.DataSource | any,
  entitiesList: Cesium.Entity[] | any,
@@ -160,7 +163,7 @@
    btmStartPoint = Cesium.Cartesian3.fromDegrees(
      Number(startPointPosition[1]),
      Number(startPointPosition[0]),
      Number(startPointPosition[2])
      Number(startPointPosition[2]),
    )
    // 参考起飞点“海拔高度,与“参考起飞点”中的椭球高度对应
    // const posterHeightRegStr = getKmlParams(kmlRes, true, {
@@ -223,7 +226,10 @@
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        disableDepthTestDistance: Number.POSITIVE_INFINITY,
        pixelOffset: new Cesium.Cartesian2(0, -40),
        show: false
      })
      // 广告牌隐藏
      // entity.label._show = false
      // 修改点的信息
      entity.point = new Cesium.PointGraphics({
        pixelSize: 20,
@@ -412,5 +418,5 @@
  }
}
export { waylinePointsEvent, waylineDetails, kmlEntities }
export { waylinePointsEvent, waylineDetails, kmlEntities, selectPointIndex }
export default useMapDraw
vite.config.ts
@@ -23,13 +23,13 @@
      vue(),
      cesium(),
      eslintPlugin({
        fix: true
        fix: true,
      }),
      ViteComponents({
        customComponentResolvers: [AntDesignVueResolver()],
      }),
      viteSvgIcons({
      // 指定需要缓存的图标文件夹
        // 指定需要缓存的图标文件夹
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        // 指定symbolId格式
        symbolId: 'icon-[dir]-[name]',
@@ -38,25 +38,26 @@
        entry: path.resolve(__dirname, './src/main.ts'), // 入口文件
        localEnabled: command === 'serve', // serve开发环境下
        // enabled: command !== 'serve' || mode === 'test', // 打包环境下/发布测试包,
        config: { // vconsole 配置项
        config: {
          // vconsole 配置项
          maxLogNumber: 1000,
          theme: 'light'
        }
          theme: 'light',
        },
      }),
      AutoImport({
        dts: 'types/auto-imports.d.ts',
        imports: ['vue', 'vue-router'],
        // 解决eslint报错问题
        eslintrc: {
        // 这里先设置成true然后npm run dev 运行之后会生成 .eslintrc-auto-import.json 文件之后,在改为false
          // 这里先设置成true然后npm run dev 运行之后会生成 .eslintrc-auto-import.json 文件之后,在改为false
          enabled: false,
          filepath: './.eslintrc-auto-import.json', // 生成的文件路径
          globalsPropValue: true,
        },
      }),
      PkgConfig(),
      OptimizationPersist()
    // [svgBuilder('./src/assets/icons/')] // All svg under src/icons/svg/ have been imported here, no need to import separately
      OptimizationPersist(),
      // [svgBuilder('./src/assets/icons/')] // All svg under src/icons/svg/ have been imported here, no need to import separately
    ],
    server: {
      open: true,
@@ -66,35 +67,36 @@
        [env.VITE_BASE_API]: {
          // 代理请求之后的请求地址(你的真实接口地址)
          target: env.VITE_API_URL,
          rewrite: path => path.replace(new RegExp(`^${env.VITE_BASE_API}`), ''),
          rewrite: (path) => path.replace(new RegExp(`^${env.VITE_BASE_API}`), ''),
          // 跨域
          changeOrigin: true
        }
      }
          changeOrigin: true,
        },
      },
    },
    envDir: './env',
    resolve: {
      alias: [{
      // https://github.com/vitejs/vite/issues/279#issuecomment-635646269
        find: '/@',
        replacement: path.resolve(__dirname, './src'),
      }
      ]
      alias: [
        {
          // https://github.com/vitejs/vite/issues/279#issuecomment-635646269
          find: '/@',
          replacement: path.resolve(__dirname, './src'),
        },
      ],
    },
    css: {
      preprocessorOptions: {
        scss: {
        // example : additionalData: `@import "./src/design/styles/variables";`
        // dont need include file extend .scss
          additionalData: '@import "./src/styles/variables";'
          // example : additionalData: `@import "./src/design/styles/variables";`
          // dont need include file extend .scss
          additionalData: '@import "./src/styles/variables";',
        },
      }
      },
    },
    base: './',
    build: {
      target: ['es2015'], // 最低支持 es2015
      sourcemap: true
    }
      sourcemap: true,
    },
  }
})
// export default ({ command, mode }: ConfigEnv): UserConfigExport => defineConfig({