forked from drone/command-center-dashboard

shuishen
2025-04-16 2530e63434696a1035683f8f08ee6d3df3f6eea7
feat:增加useSingleDroneMap
4 files added
347 ■■■■■ changed files
src/hooks/useSingleDroneMap/index.js 77 ●●●●● patch | view | raw | blame | history
src/hooks/useSingleDroneMap/useMapEvents.js 56 ●●●●● patch | view | raw | blame | history
src/hooks/useSingleDroneMap/useMapMarkers.js 147 ●●●●● patch | view | raw | blame | history
src/hooks/useSingleDroneMap/useMapPopup.js 67 ●●●●● patch | view | raw | blame | history
src/hooks/useSingleDroneMap/index.js
New file
@@ -0,0 +1,77 @@
/*
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-15 22:41:40
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-16 15:06:15
 * @FilePath: \command-center-dashboard\src\hooks\useSingleDroneMap\index.js
 * @Description:
 *
 * Copyright (c) 2025 by shuishen, All Rights Reserved.
 */
import { useMapMarkers } from './useMapMarkers'
import { useMapEvents } from './useMapEvents'
import { useMapPopup } from './useMapPopup'
/**
 * Cesium地图功能组合Hook
 * @param {Object} options - 配置选项
 * @param {Function} options.fetchMarkers - 获取标注的函数
 * @param {Function} options.fetchEvents - 获取事件的函数
 * @param {Component} options.popupComponent - 弹窗组件
 */
export function useSingleDroneMap (options = {
  dronePosition: {},
  eventLoadType: 'data',
  eventPositions: [],
  eventApi: null,
  eventApiParams: {}
}) {
  let viewer = null
  let currentEntity = null
  // 组合各个功能Hook
  const {
    initLayer,
    removeLayer,
  } = useMapMarkers(viewer, {})
  const {
    handlerInit,
    removeHandler
  } = useMapEvents(viewer, {
    onEventClick: (entity) => {
      currentEntity = entity // 更新当前实体
      addLabel()
    }
  })
  const {
    addLabel
  } = useMapPopup(viewer, {
    currentEntity
  })
  const init = () => {
    viewer = window.$viewer
    initLayer()
    handlerInit()
  }
  const removeAll = () => {
    removeLayer()
    removeHandler()
    removeLabel()
  }
  // 自动清理
  onUnmounted(() => {
    removeAll()
  })
  return {
    init,
    removeAll
  }
}
src/hooks/useSingleDroneMap/useMapEvents.js
New file
@@ -0,0 +1,56 @@
import * as Cesium from 'cesium'
/**
 * 地图事件 Hook
 * @param {Cesium.Viewer} viewer - Cesium 实例
 * @param {Object} options - 配置选项
 * @param {Function} options.onEventClick - 点击事件的回调
 */
export function useMapEvents (viewer, options = {}) {
  const { onEventClick } = options
  let handler = null
  // 查找特定类型的实体
  const findEntityByType = (entities, type) => {
    return entities.find(entity =>
      entity?.properties?.customData?._value?.data?.type === type
    )
  }
  // 左键单机事件
  const singleMachineEvent = async click => {
    let clickedEntities = viewer.scene.drillPick(click.position).map(item => item.id)
    if (!clickedEntities.length) return
    const currentEntity = findEntityByType(clickedEntities, 'device') ||
      findEntityByType(clickedEntities, 'event')
    currentEntity = deviceAggregationFind || deviceFind || eventFind
    if (currentEntity) {
      onEventClick?.(currentEntity)
    }
  }
  const handlerInit = () => {
    if (handler) return
    handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
    handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
  }
  const removeHandler = () => {
    handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    handler?.destroy()
    handler = null
  }
  // 自动清理
  onUnmounted(() => {
    removeHandler()
  })
  return {
    handlerInit,
    removeHandler
  }
}
src/hooks/useSingleDroneMap/useMapMarkers.js
New file
@@ -0,0 +1,147 @@
import * as Cesium from 'cesium'
import aggregationImg from '@/assets/images/home/useUavHome/aggregation.png'
/**
 * 地图标注
 * @param {*} viewer
 * @param {*} options
 * @returns
 */
export function useMapMarkers (viewer, options = {
  dronePosition: {},
  eventLoadType: 'data',
  eventPositions: [],
  eventApi: null,
  eventApiParams: {}
}) {
  const { dronePosition, eventLoadType, eventPositions, eventApi, eventApiParams } = options
  const initDroneEntity = () => {
    const featuresList = item.gJson.features.map(item1 => {
      // const {lng,lat} = getCenterPoint(item1.geometry.coordinates[0][0])
      return {
        name: item1.properties.name,
        position: item1.properties.centroid,
        data: item1.data,
        id: item1.region_code,
      }
    })
    // 遍历特征并添加实体
    featuresList.forEach((feature, index) => {
      if (!feature.position) return
      const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1], 0)
      viewer.entities.add({
        position: position,
        id: `aggregation-name-${index}`,
        label: {
          text: feature.name,
          font: '14pt Source Han Sans CN',
          fillColor: Cesium.Color.WHITE,
          outlineColor: Cesium.Color.BLACK,
          outlineWidth: 2,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          pixelOffset: new Cesium.Cartesian2(0, -9),
        },
      })
      viewer.entities.add({
        id: `aggregation-count-${index}`,
        position: position,
        label: {
          text: (feature.data.total_device_count || 0).toString(),
          font: '12pt Source Han Sans CN',
          fillColor: Cesium.Color.BLACK,
          outlineColor: Cesium.Color.BLACK,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          eyeOffset: new Cesium.Cartesian3(0, 0, -10), // 让label "浮" 在广告牌前面
        },
        billboard: {
          image: new Cesium.ConstantProperty(aggregationImg),
          width: 35,
          height: 35,
        },
        properties: {
          id: feature.id,
          customData: {
            data: feature.data,
          },
        },
      })
    })
  }
  const initDroneEventEntity = () => {
    // 遍历特征并添加实体
    eventPositions.forEach((feature, index) => {
      if (!feature.position) return
      const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1], 0)
      viewer.entities.add({
        position: position,
        id: `aggregation-name-${index}`,
        label: {
          text: feature.name,
          font: '14pt Source Han Sans CN',
          fillColor: Cesium.Color.WHITE,
          outlineColor: Cesium.Color.BLACK,
          outlineWidth: 2,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          pixelOffset: new Cesium.Cartesian2(0, -9),
        },
      })
      viewer.entities.add({
        id: `aggregation-count-${index}`,
        position: position,
        label: {
          text: (feature.data.total_device_count || 0).toString(),
          font: '12pt Source Han Sans CN',
          fillColor: Cesium.Color.BLACK,
          outlineColor: Cesium.Color.BLACK,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          eyeOffset: new Cesium.Cartesian3(0, 0, -10), // 让label "浮" 在广告牌前面
        },
        billboard: {
          image: new Cesium.ConstantProperty(aggregationImg),
          width: 35,
          height: 35,
        },
        properties: {
          id: feature.id,
          customData: {
            data: feature.data,
          },
        },
      })
    })
  }
  const initEventApiEntity = () => {
    eventApi(eventApiParams).then(res => {
      console.log(res)
    })
  }
  // 移除 点 和 gjson 实体
  const removeEntities = () => {
    // entities移除
    const entitiesIDs = viewer.entities.values.map(i => i.id)
    entitiesIDs.forEach(item => {
      item.includes('aggregation-') && viewer.entities.removeById(item)
    })
  }
  const initLayer = () => {
    initDroneEntity()
    eventLoadType === 'data' ? initDroneEventEntity() : initEventApiEntity()
  }
  const removeLayer = () => {
    removeEntities()
  }
  return {
    initLayer,
    removeLayer
  }
}
src/hooks/useSingleDroneMap/useMapPopup.js
New file
@@ -0,0 +1,67 @@
import * as Cesium from 'cesium'
import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue'
/**
 * 地图弹窗 Hook
 * @param {Cesium.Viewer} viewer - Cesium 实例
 * @param {Object} options - 配置选项
 * @param {Component} options.popupComponent - 弹窗组件
 */
export function useMapPopup (viewer, options = {}) {
  const { currentEntity } = options
  // 获取弹框box
  const getLabelDom = data => {
    const vNode = h(EventPopUpBox, { data, removeLabel })
    const tooltipContainer = document.createElement('div')
    tooltipContainer.id = 'mapPopUpBox'
    tooltipContainer.style.position = 'absolute'
    tooltipContainer.style.transform = styleTransform
    tooltipContainer.style.pointerEvents = 'none'
    document.querySelector('.page-index').append(tooltipContainer)
    render(vNode, tooltipContainer)
    return tooltipContainer
  }
  // 弹框位置刷新
  const labelBoxRender = () => {
    if (!currentEntity) return
    let dom = document.querySelector('#mapPopUpBox')
    if (!dom) {
      dom = getLabelDom(currentEntity.properties.customData._value.data)
    }
    const screenPosition = viewer.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value)
    if (screenPosition) {
      dom.style.left = `${screenPosition.x}px`
      dom.style.top = `${screenPosition.y}px`
      dom.style.display = 'block'
    }
  }
  const removeDom = () => {
    const dom = document.querySelector('#mapPopUpBox')
    if (dom && dom.parentNode) {
      dom.parentNode.removeChild(dom)
    }
  }
  const addLabel = () => {
    removeLabel()
    viewer?.scene.postRender.addEventListener(labelBoxRender)
  }
  // 移除弹框标签
  const removeLabel = () => {
    viewer?.scene.postRender.removeEventListener(labelBoxRender)
    removeDom()
  }
  // 自动清理
  onUnmounted(() => {
    removeLabel()
  })
  return {
    addLabel
  }
}