无人机管理后台前端(已迁走)
chenyao
2025-09-13 744be26032980317d34d22643bf7bdd59a8dbedb
feat:更新网格管理
4 files modified
3 files added
3034 ■■■■■ changed files
src/api/airspace/airspace.js 34 ●●●●● patch | view | raw | blame | history
src/utils/cesium/publicCesium.js 1 ●●●● patch | view | raw | blame | history
src/views/airspace/airspaceType.vue 15 ●●●● patch | view | raw | blame | history
src/views/gridManagement/GridSettings/OccupancyGrid.js 1293 ●●●●● patch | view | raw | blame | history
src/views/gridManagement/GridSettings/PathPlanning.js 1058 ●●●●● patch | view | raw | blame | history
src/views/gridManagement/gridManagement.vue 623 ●●●●● patch | view | raw | blame | history
vite.config.mjs 10 ●●●● patch | view | raw | blame | history
src/api/airspace/airspace.js
@@ -62,4 +62,36 @@
    params
  })
}
// ----------空域录入相关接口调用----------
// ----------空域录入相关接口调用----------
// ----------网格管理相关接口调用----------
export const airGridPage = params => {
  return request({
    url: '/drone-device-core/airgrid/page',
    method: 'get',
    params
  })
}
export const airGridUpdate = data => {
  return request({
    url: '/drone-device-core/airgrid/update',
    method: 'put',
    data: data,
  })
}
export const airGridAdd= data => {
  return request({
    url: '/drone-device-core/airgrid/add',
    method: 'post',
    data: data,
  })
}
export const airGridDelete= data => {
  return request({
    url: '/drone-device-core/airgrid/delete/' + data,
    method: 'delete',
  })
}
src/utils/cesium/publicCesium.js
@@ -224,6 +224,7 @@
    // 飞行 flyto
    flyTo (pointOption, time = 4, height = 3000, orientation = {}, complete = () => { }) {
        console.log(pointOption, )
        if (!pointOption.longitude && !pointOption.latitude) return
        const destination = Cesium.Cartesian3.fromDegrees(pointOption.longitude, pointOption.latitude, height)
        const duration = time
src/views/airspace/airspaceType.vue
@@ -126,10 +126,19 @@
  rowView.value = row
}
function handleDelete (row) {
  airSpaceTypeDelete(row.id).then(res => {
    ElMessage.success('删除成功')
    getList()
  // 确定要删除么?
  ElMessage({
    message: '确定要删除么?',
    type: 'warning',
    showClose: true,
    onClose: () => {
      airSpaceTypeDelete(row.id).then(res => {
        ElMessage.success('删除成功')
        getList()
      })
    }
  })
}
function handleAdd() {
  titleTxt.value = '新增'
src/views/gridManagement/GridSettings/OccupancyGrid.js
New file
@@ -0,0 +1,1293 @@
// ================================
// 3D占用网格系统模块
// ================================
import * as Cesium from 'cesium'
class OccupancyGrid {
  constructor(viewer, config = {}, gridParams) {
    this.viewer = viewer
    this.gridEntities = []
    this.tilesBoundingBoxes = [] // 存储瓦片边界框
    this.gridParams = gridParams
    // 网格配置参数 - 可自定义
    this.config = {
      // 网格单元尺寸配置
      gridSize: config.gridSize || 50, // 网格单元大小(米)
      gridWidth: config.gridWidth || 50, // 网格宽度(米)
      gridHeight: config.gridHeight || 50, // 网格高度(米)
      gridDepth: config.gridDepth || 50, // 网格深度(米)
      // 区域扩展配置
      heightExtension: config.heightExtension || 120, // 向上向下各扩展120米,确保4层网格(总高度240米,4×50=200米,留有余量)
      widthExtension: config.widthExtension || 100, // 水平方向扩展100米
      // 颜色配置
      occupiedColor: config.occupiedColor || Cesium.Color.RED.withAlpha(0.7), // 占用网格颜色(红色)
      freeColor: config.freeColor || Cesium.Color.GREEN.withAlpha(0.3), // 空闲网格颜色(绿色)
      occupiedOutlineColor: config.occupiedOutlineColor || Cesium.Color.DARKRED, // 占用网格边框颜色
      freeOutlineColor: config.freeOutlineColor || Cesium.Color.DARKGREEN, // 空闲网格边框颜色
      outlineWidth: config.outlineWidth || 1, // 边框宽度
      // 透明度配置
      occupiedAlpha: config.occupiedAlpha || 0.7, // 占用网格透明度
      freeAlpha: config.freeAlpha || 0.3, // 空闲网格透明度
      // 性能优化配置
      maxGridCount: config.maxGridCount || 10000, // 最大网格数量限制
      enableBatching: config.enableBatching || true, // 是否启用批处理优化
      // 调试配置
      showOccupiedOnly: config.showOccupiedOnly || false, // 是否只显示占用的网格
      showFreeOnly: config.showFreeOnly || false, // 是否只显示空闲的网格
      enableLogging: config.enableLogging || true // 是否启用详细日志
    }
    // 向后兼容旧参数
    this.gridSize = this.config.gridSize
    this.heightExtension = this.config.heightExtension
  }
  // ================================
  // 更新配置
  // ================================
  updateConfig (newConfig) {
    this.config = { ...this.config, ...newConfig }
    // 更新向后兼容参数
    this.gridSize = this.config.gridSize
    this.heightExtension = this.config.heightExtension
    if (this.config.enableLogging) {
      console.log('网格配置已更新:', this.config)
    }
  }
  // ================================
  // 获取当前配置
  // ================================
  getConfig () {
    return { ...this.config }
  }
  // ================================
  // 生成占用网格 - 使用配置参数
  // ================================
  async generateOccupancyGrid (waypoints) {
    if (waypoints.length < 2) {
      console.warn('需要至少2个航点来生成占用网格')
      return
    }
    // 清除之前的网格
    this.clearGrid()
    // 获取起点和终点
    const startPoint = waypoints[0]
    const endPoint = waypoints[waypoints.length - 1]
    // 计算边界框
    const bounds = this.calculateBounds(startPoint, endPoint)
    // 收集3D瓦片的边界框信息
    await this.collectTilesBoundingBoxes(bounds)
    // 生成网格
    await this.createGrid(bounds)
    if (this.config.enableLogging) {
      console.log('3D占用网格生成完成')
      console.log(`网格尺寸: ${this.config.gridWidth}m x ${this.config.gridHeight}m x ${this.config.gridDepth}m`)
      console.log(`总网格数量: ${this.gridEntities.length}`)
      console.log(`瓦片边界框数量: ${this.tilesBoundingBoxes.length}`)
    }
  }
  // ================================
  // 使用航线瓦片数据生成占用网格 - 使用配置参数
  // ================================
  async generateOccupancyGridWithTiles (waypoints, flightPathTiles) {
    if (waypoints.length < 2) {
      console.warn('需要至少2个航点来生成占用网格')
      return
    }
    if (this.config.enableLogging) {
      console.log(`开始生成占用网格,将分析网格范围内的所有叶子瓦片节点`)
    }
    // 清除之前的网格
    this.clearGrid()
    // 获取起点和终点
    const startPoint = waypoints[0]
    const endPoint = waypoints[waypoints.length - 1]
    // 计算边界框
    const bounds = this.calculateBounds(startPoint, endPoint)
    // 获取瓦片集,用于遍历叶子节点
    this.tileset = this.getTileset()
    if (!this.tileset) {
      console.warn('未找到瓦片集,无法生成占用网格')
      return
    }
    // 生成网格
    await this.createGrid(bounds)
    if (this.config.enableLogging) {
      console.log('使用叶子瓦片节点的3D占用网格生成完成')
      console.log(`网格尺寸: ${this.config.gridWidth}m x ${this.config.gridHeight}m x ${this.config.gridDepth}m`)
      console.log(`总网格数量: ${this.gridEntities.length}`)
    }
  }
  // ================================
  // 获取瓦片集
  // ================================
  getTileset () {
    const primitives = this.viewer.scene.primitives
    for (let i = 0; i < primitives.length; i++) {
      const primitive = primitives.get(i)
      if (primitive instanceof Cesium.Cesium3DTileset) {
        return primitive
      }
    }
    return null
  }
  // ================================
  // 计算边界框 - 使用配置参数
  // ================================
  calculateBounds (startPoint, endPoint) {
    // 转换为地理坐标
    const startCartographic = Cesium.Cartographic.fromCartesian(startPoint.position)
    const endCartographic = Cesium.Cartographic.fromCartesian(endPoint.position)
    // 计算基础边界
    let minLon = Math.min(startCartographic.longitude, endCartographic.longitude)
    let maxLon = Math.max(startCartographic.longitude, endCartographic.longitude)
    let minLat = Math.min(startCartographic.latitude, endCartographic.latitude)
    let maxLat = Math.max(startCartographic.latitude, endCartographic.latitude)
    // 应用水平扩展
    const earthRadius = 6371000
    const widthExtensionLon = this.config.widthExtension / (earthRadius * Math.cos((minLat + maxLat) / 2))
    const widthExtensionLat = this.config.widthExtension / earthRadius
    minLon -= widthExtensionLon
    maxLon += widthExtensionLon
    minLat -= widthExtensionLat
    maxLat += widthExtensionLat
    // 计算平均高度和垂直扩展
    const avgHeight = (startPoint.height + endPoint.height) / 2
    const heightOffset = 20 // 向上平移50米,避免网格被tileset遮盖
    const minHeight = avgHeight - this.config.heightExtension + heightOffset
    const maxHeight = avgHeight + this.config.heightExtension + heightOffset
    const bounds = {
      minLon: minLon,
      maxLon: maxLon,
      minLat: minLat,
      maxLat: maxLat,
      minHeight: minHeight,
      maxHeight: maxHeight,
      avgHeight: avgHeight
    }
    if (this.config.enableLogging) {
      console.log('计算网格边界范围:', {
        经度范围: `${Cesium.Math.toDegrees(minLon).toFixed(6)} 到 ${Cesium.Math.toDegrees(maxLon).toFixed(6)}`,
        纬度范围: `${Cesium.Math.toDegrees(minLat).toFixed(6)} 到 ${Cesium.Math.toDegrees(maxLat).toFixed(6)}`,
        高度范围: `${minHeight.toFixed(1)}m 到 ${maxHeight.toFixed(1)}m (向上平移50m)`,
        水平扩展: `${this.config.widthExtension}m`,
        垂直扩展: `${this.config.heightExtension}m`
      })
    }
    return bounds
  }
  // ================================
  // 收集3D瓦片的边界框信息
  // ================================
  async collectTilesBoundingBoxes (bounds) {
    this.tilesBoundingBoxes = []
    // 获取所有的图元
    const primitives = this.viewer.scene.primitives
    let tileset = null
    // 查找3D Tiles图层
    for (let i = 0; i < primitives.length; i++) {
      const primitive = primitives.get(i)
      if (primitive instanceof Cesium.Cesium3DTileset) {
        tileset = primitive
        break
      }
    }
    if (!tileset || !tileset.root) {
      console.warn('未找到3D Tiles图层')
      return
    }
    // 等待瓦片集加载完成
    if (!tileset.ready) {
      await tileset.readyPromise
    }
    // 遍历瓦片树并收集边界框
    this.traverseTiles(tileset.root, bounds)
    console.log(`收集到${this.tilesBoundingBoxes.length}个瓦片边界框`)
  }
  // ================================
  // 收集航线瓦片的边界框信息
  // ================================
  collectFlightPathTilesBoundingBoxes (flightPathTiles) {
    console.log('开始收集航线瓦片边界框信息')
    this.tilesBoundingBoxes = []
    flightPathTiles.forEach((tile, index) => {
      try {
        // 获取边界体信息
        let boundingVolume = null
        if (tile.boundingVolume) {
          boundingVolume = tile.boundingVolume
        } else if (tile._boundingVolume) {
          boundingVolume = tile._boundingVolume
        } else if (tile.contentBoundingVolume) {
          boundingVolume = tile.contentBoundingVolume
        }
        if (!boundingVolume) {
          console.warn(`瓦片 ${index} 没有边界体信息`)
          return
        }
        let boundingBox = null
        // 处理不同类型的边界体
        if (boundingVolume instanceof Cesium.OrientedBoundingBox) {
          boundingBox = this.orientedBoundingBoxToAxisAligned(boundingVolume)
        }
        else if (boundingVolume instanceof Cesium.BoundingSphere) {
          boundingBox = this.sphereToAxisAligned(boundingVolume)
        }
        else if (boundingVolume.orientedBoundingBox) {
          boundingBox = this.orientedBoundingBoxToAxisAligned(boundingVolume.orientedBoundingBox)
        }
        else if (boundingVolume.boundingSphere) {
          boundingBox = this.sphereToAxisAligned(boundingVolume.boundingSphere)
        }
        else if (boundingVolume._orientedBoundingBox) {
          boundingBox = this.orientedBoundingBoxToAxisAligned(boundingVolume._orientedBoundingBox)
        }
        else if (boundingVolume._boundingSphere) {
          boundingBox = this.sphereToAxisAligned(boundingVolume._boundingSphere)
        }
        if (boundingBox) {
          this.tilesBoundingBoxes.push(boundingBox)
          console.log(`添加瓦片 ${index} 的边界框`)
        } else {
          console.warn(`无法处理瓦片 ${index} 的边界体类型`)
        }
      } catch (error) {
        console.warn(`处理瓦片 ${index} 边界体时出错:`, error)
      }
    })
    console.log(`成功收集 ${this.tilesBoundingBoxes.length} 个航线瓦片边界框`)
  }
  // ================================
  // 遍历瓦片树
  // ================================
  traverseTiles (tile, bounds) {
    if (!tile) return
    // 获取瓦片的边界框
    const boundingVolume = tile.boundingVolume
    if (boundingVolume) {
      let boundingBox = null
      try {
        // 检查是否有中心点和半径(球体)
        if (boundingVolume.center && boundingVolume.radius !== undefined) {
          boundingBox = this.sphereToBox(boundingVolume)
        }
        // 检查是否有中心点和半轴(盒子)
        else if (boundingVolume.center && boundingVolume.halfAxes) {
          const halfAxes = boundingVolume.halfAxes
          const halfExtents = new Cesium.Cartesian3(
            Math.max(10, Cesium.Cartesian3.magnitude(new Cesium.Cartesian3(halfAxes[0], halfAxes[1], halfAxes[2]))),
            Math.max(10, Cesium.Cartesian3.magnitude(new Cesium.Cartesian3(halfAxes[3], halfAxes[4], halfAxes[5]))),
            Math.max(10, Cesium.Cartesian3.magnitude(new Cesium.Cartesian3(halfAxes[6], halfAxes[7], halfAxes[8])))
          )
          boundingBox = {
            center: boundingVolume.center,
            halfExtents: halfExtents
          }
        }
        // 检查是否有区域信息
        else if (boundingVolume.west !== undefined && boundingVolume.east !== undefined) {
          boundingBox = this.regionToBox(boundingVolume)
        }
        // 默认情况:创建一个小的边界框
        else if (boundingVolume.center) {
          boundingBox = {
            center: boundingVolume.center,
            halfExtents: new Cesium.Cartesian3(50, 50, 50) // 默认50米
          }
        }
      } catch (error) {
        console.warn('处理边界体积时出错:', error)
        return
      }
      if (boundingBox && this.isBoundingBoxInRegion(boundingBox, bounds)) {
        this.tilesBoundingBoxes.push(boundingBox)
      }
    }
    // 递归遍历子瓦片
    if (tile.children && tile.children.length > 0) {
      for (const child of tile.children) {
        this.traverseTiles(child, bounds)
      }
    }
  }
  // ================================
  // 将球体转换为包围盒
  // ================================
  sphereToBox (sphere) {
    const center = sphere.center
    const radius = sphere.radius
    // 创建一个立方体包围盒
    const halfExtents = new Cesium.Cartesian3(radius, radius, radius)
    return {
      center: center,
      halfExtents: halfExtents
    }
  }
  // ================================
  // 将区域转换为包围盒
  // ================================
  regionToBox (region) {
    // 计算区域的中心点
    const centerLon = (region.west + region.east) / 2
    const centerLat = (region.south + region.north) / 2
    const centerHeight = (region.minimumHeight + region.maximumHeight) / 2
    const center = Cesium.Cartesian3.fromRadians(centerLon, centerLat, centerHeight)
    // 计算区域的半尺寸
    const lonExtent = (region.east - region.west) / 2
    const latExtent = (region.north - region.south) / 2
    const heightExtent = (region.maximumHeight - region.minimumHeight) / 2
    // 转换为笛卡尔坐标的半扩展
    const earthRadius = 6371000
    const halfExtents = new Cesium.Cartesian3(
      lonExtent * earthRadius * Math.cos(centerLat),
      latExtent * earthRadius,
      heightExtent
    )
    return {
      center: center,
      halfExtents: halfExtents
    }
  }
  // ================================
  // 检查边界框是否在感兴趣区域内
  // ================================
  isBoundingBoxInRegion (boundingBox, bounds) {
    // 将边界框中心转换为地理坐标
    const centerCartographic = Cesium.Cartographic.fromCartesian(boundingBox.center)
    // 检查是否在地理范围内
    return (centerCartographic.longitude >= bounds.minLon &&
      centerCartographic.longitude <= bounds.maxLon &&
      centerCartographic.latitude >= bounds.minLat &&
      centerCartographic.latitude <= bounds.maxLat &&
      centerCartographic.height >= bounds.minHeight &&
      centerCartographic.height <= bounds.maxHeight)
  }
  // ================================
  // 创建网格 - 使用配置参数
  // ================================
  async createGrid (bounds) {
    // 将地理坐标转换为距离(米)
    const earthRadius = 6371000 // 地球半径(米)
    // 计算经纬度范围对应的实际距离
    const latDistance = (bounds.maxLat - bounds.minLat) * earthRadius
    const lonDistance = (bounds.maxLon - bounds.minLon) * earthRadius * Math.cos((bounds.minLat + bounds.maxLat) / 2)
    const heightDistance = bounds.maxHeight - bounds.minHeight
    // 计算网格数量 - 使用配置的网格尺寸
    const gridCountX = Math.ceil(lonDistance / this.config.gridWidth)
    const gridCountY = Math.ceil(latDistance / this.config.gridHeight)
    const gridCountZ = Math.ceil(heightDistance / this.config.gridDepth)
    const totalGridCount = gridCountX * gridCountY * gridCountZ
    if (this.config.enableLogging) {
      console.log(`计划生成网格数量: ${gridCountX} x ${gridCountY} x ${gridCountZ} = ${totalGridCount}`)
      console.log(`网格单元尺寸: 宽${this.config.gridWidth}m x 高${this.config.gridHeight}m x 深${this.config.gridDepth}m`)
      console.log(`实际距离: 经度${lonDistance.toFixed(1)}m x 纬度${latDistance.toFixed(1)}m x 高度${heightDistance.toFixed(1)}m`)
      console.log(`垂直方向: 高度范围${heightDistance.toFixed(1)}m ÷ 网格深度${this.config.gridDepth}m = ${gridCountZ}层网格`)
    }
    // 性能保护:检查网格数量是否超出限制
    if (totalGridCount > this.config.maxGridCount) {
      console.warn(`网格数量 ${totalGridCount} 超过最大限制 ${this.config.maxGridCount},将进行优化处理`)
      // 自动调整网格尺寸
      const scaleFactor = Math.cbrt(totalGridCount / this.config.maxGridCount)
      const adjustedGridSize = this.config.gridSize * scaleFactor
      console.log(`自动调整网格尺寸从 ${this.config.gridSize}m 到 ${adjustedGridSize.toFixed(1)}m`)
      // 重新计算网格数量
      const newGridCountX = Math.ceil(lonDistance / adjustedGridSize)
      const newGridCountY = Math.ceil(latDistance / adjustedGridSize)
      const newGridCountZ = Math.ceil(heightDistance / adjustedGridSize)
      if (this.config.enableLogging) {
        console.log(`调整后网格数量: ${newGridCountX} x ${newGridCountY} x ${newGridCountZ} = ${newGridCountX * newGridCountY * newGridCountZ}`)
      }
      // 使用调整后的参数
      return this.createGridWithCustomSize(bounds, newGridCountX, newGridCountY, newGridCountZ, adjustedGridSize)
    }
    // 正常情况下生成网格
    return this.createGridWithCustomSize(bounds, gridCountX, gridCountY, gridCountZ, this.config.gridSize)
  }
  // ================================
  // 使用自定义尺寸创建网格
  // ================================
  async createGridWithCustomSize (bounds, gridCountX, gridCountY, gridCountZ, gridSize) {
    let occupiedCount = 0
    let freeCount = 0
    // 批处理参数
    const batchSize = this.config.enableBatching ? 100 : 1
    let batchCount = 0
    // 生成网格立方体
    for (let i = 0; i < gridCountX; i++) {
      for (let j = 0; j < gridCountY; j++) {
        for (let k = 0; k < gridCountZ; k++) {
          const gridCenter = this.calculateGridCenter(bounds, i, j, k, gridCountX, gridCountY, gridCountZ)
          const isOccupied = await this.checkOccupancy(gridCenter)
          // 计算网格索引号 (i, j, k)
          const gridIndex = { x: i, y: j, z: k }
          // 根据配置决定是否显示
          let shouldCreate = true
          if (this.config.showOccupiedOnly && !isOccupied) {
            shouldCreate = false
          }
          if (this.config.showFreeOnly && isOccupied) {
            shouldCreate = false
          }
          if (shouldCreate) {
            this.createGridCube(gridCenter, isOccupied, gridSize, gridIndex)
          }
          // 统计
          if (isOccupied) {
            occupiedCount++
          } else {
            freeCount++
          }
          // 批处理优化:每处理一定数量后让出控制权
          batchCount++
          if (this.config.enableBatching && batchCount >= batchSize) {
            batchCount = 0
            await new Promise(resolve => setTimeout(resolve, 1)) // 让出1ms给浏览器
          }
        }
      }
    }
    if (this.config.enableLogging) {
      this.gridParams.hasGrid = true
      console.log(`网格生成完成 - 占用: ${occupiedCount}, 空闲: ${freeCount}, 总计: ${this.gridEntities.length}`)
    }
  }
  // ================================
  // 计算网格中心点
  // ================================
  calculateGridCenter (bounds, i, j, k, gridCountX, gridCountY, gridCountZ) {
    // 计算网格中心的地理坐标
    const lonStep = (bounds.maxLon - bounds.minLon) / gridCountX
    const latStep = (bounds.maxLat - bounds.minLat) / gridCountY
    const heightStep = (bounds.maxHeight - bounds.minHeight) / gridCountZ
    const lon = bounds.minLon + (i + 0.5) * lonStep
    const lat = bounds.minLat + (j + 0.5) * latStep
    const height = bounds.minHeight + (k + 0.5) * heightStep
    return {
      longitude: lon,
      latitude: lat,
      height: height
    }
  }
  // ================================
  // 检查占用状态 - 基于网格范围内的叶子瓦片节点及其外包盒求交
  // ================================
  async checkOccupancy (gridCenter) {
    if (!this.tileset) {
      return false // 没有瓦片集,默认未占用
    }
    // 计算网格单元的边界范围
    const gridBounds = this.calculateGridBounds(gridCenter)
    // 收集网格范围内的所有叶子瓦片节点
    const leafTilesInGrid = []
    this.findLeafTilesInGridBounds(this.tileset.root, gridBounds, leafTilesInGrid)
    console.log(this.tileset.root, gridBounds, 'this.tileset.root')
    if (leafTilesInGrid.length === 0) {
      return false // 没有叶子瓦片,标记为未占用
    }
    // 使用叶子节点的外包盒与网格单元进行精确求交计算
    const hasIntersection = this.checkGridTileIntersections(gridBounds, leafTilesInGrid)
    if (hasIntersection) {
      console.log(`网格单元 [${Cesium.Math.toDegrees(gridCenter.longitude).toFixed(6)}, ${Cesium.Math.toDegrees(gridCenter.latitude).toFixed(6)}, ${gridCenter.height.toFixed(1)}] 与 ${leafTilesInGrid.length} 个叶子瓦片相交`)
    }
    return hasIntersection
  }
  // ================================
  // 递归查找网格范围内的叶子瓦片节点
  // ================================
  findLeafTilesInGridBounds (tile, gridBounds, leafTiles) {
    if (!tile) {
      return
    }
    // 检查是否为叶子节点
    const isLeafNode = !tile.children || tile.children.length === 0
    // 首先检查当前瓦片是否与网格边界相交
    const intersects = this.isTileIntersectingGridBounds(tile, gridBounds)
    if (!intersects) {
      return // 如果当前瓦片不与网格相交,则跳过其所有子节点
    }
    if (isLeafNode) {
      // 只有叶子节点才添加到结果中
      leafTiles.push(tile)
    } else {
      // 中间节点与网格相交,继续遍历其子节点
      for (let i = 0; i < tile.children.length; i++) {
        this.findLeafTilesInGridBounds(tile.children[i], gridBounds, leafTiles)
      }
    }
  }
  // ================================
  // 检查瓦片是否与网格边界相交(简化版本,用于预筛选)
  // ================================
  isTileIntersectingGridBounds (tile, gridBounds) {
    // 获取瓦片的边界体信息
    let boundingVolume = null
    if (tile.boundingVolume) {
      boundingVolume = tile.boundingVolume
    } else if (tile._boundingVolume) {
      boundingVolume = tile._boundingVolume
    } else if (tile.contentBoundingVolume) {
      boundingVolume = tile.contentBoundingVolume
    }
    if (!boundingVolume) {
      return false // 没有边界体信息
    }
    try {
      // 简化的边界检测,用于快速预筛选
      let tileBounds = null
      if (boundingVolume instanceof Cesium.OrientedBoundingBox) {
        tileBounds = this.getSimpleBoundsFromOBB(boundingVolume)
      }
      else if (boundingVolume instanceof Cesium.BoundingSphere) {
        tileBounds = this.getSimpleBoundsFromSphere(boundingVolume)
      }
      else if (boundingVolume.orientedBoundingBox) {
        tileBounds = this.getSimpleBoundsFromOBB(boundingVolume.orientedBoundingBox)
      }
      else if (boundingVolume.boundingSphere) {
        tileBounds = this.getSimpleBoundsFromSphere(boundingVolume.boundingSphere)
      }
      else if (boundingVolume._orientedBoundingBox) {
        tileBounds = this.getSimpleBoundsFromOBB(boundingVolume._orientedBoundingBox)
      }
      else if (boundingVolume._boundingSphere) {
        tileBounds = this.getSimpleBoundsFromSphere(boundingVolume._boundingSphere)
      }
      if (!tileBounds) {
        return false
      }
      // 简单的3D AABB相交检测
      const tolerance = 0.0001
      const intersects = !(
        gridBounds.maxLon < tileBounds.minLon - tolerance ||
        gridBounds.minLon > tileBounds.maxLon + tolerance ||
        gridBounds.maxLat < tileBounds.minLat - tolerance ||
        gridBounds.minLat > tileBounds.maxLat + tolerance ||
        gridBounds.maxHeight < tileBounds.minHeight - tolerance ||
        gridBounds.minHeight > tileBounds.maxHeight + tolerance
      )
      return intersects
    } catch (error) {
      console.warn('预筛选瓦片边界体时出错:', error)
      return true // 出错时保守地返回true,让后续精确计算处理
    }
  }
  // ================================
  // 从OrientedBoundingBox获取简化边界(用于预筛选)
  // ================================
  getSimpleBoundsFromOBB (obb) {
    const center = obb.center
    const halfAxes = obb.halfAxes
    // 计算边界盒的大致范围
    const xAxis = new Cesium.Cartesian3()
    const yAxis = new Cesium.Cartesian3()
    const zAxis = new Cesium.Cartesian3()
    Cesium.Matrix3.getColumn(halfAxes, 0, xAxis)
    Cesium.Matrix3.getColumn(halfAxes, 1, yAxis)
    Cesium.Matrix3.getColumn(halfAxes, 2, zAxis)
    // 估算边界盒的最大扩展
    const maxExtent = Math.max(
      Cesium.Cartesian3.magnitude(xAxis),
      Cesium.Cartesian3.magnitude(yAxis),
      Cesium.Cartesian3.magnitude(zAxis)
    )
    // 将中心转换为地理坐标
    const centerCartographic = Cesium.Cartographic.fromCartesian(center)
    const centerLon = Cesium.Math.toDegrees(centerCartographic.longitude)
    const centerLat = Cesium.Math.toDegrees(centerCartographic.latitude)
    const centerHeight = centerCartographic.height
    // 估算经纬度范围
    const deltaLon = maxExtent / (111320 * Math.cos(centerCartographic.latitude))
    const deltaLat = maxExtent / 110540
    return {
      minLon: centerLon - deltaLon,
      maxLon: centerLon + deltaLon,
      minLat: centerLat - deltaLat,
      maxLat: centerLat + deltaLat,
      minHeight: centerHeight - maxExtent,
      maxHeight: centerHeight + maxExtent
    }
  }
  // ================================
  // 从BoundingSphere获取简化边界(用于预筛选)
  // ================================
  getSimpleBoundsFromSphere (sphere) {
    const center = sphere.center
    const radius = sphere.radius
    // 将球心转换为地理坐标
    const centerCartographic = Cesium.Cartographic.fromCartesian(center)
    const centerLon = Cesium.Math.toDegrees(centerCartographic.longitude)
    const centerLat = Cesium.Math.toDegrees(centerCartographic.latitude)
    const centerHeight = centerCartographic.height
    // 估算经纬度范围
    const deltaLon = radius / (111320 * Math.cos(centerCartographic.latitude))
    const deltaLat = radius / 110540
    return {
      minLon: centerLon - deltaLon,
      maxLon: centerLon + deltaLon,
      minLat: centerLat - deltaLat,
      maxLat: centerLat + deltaLat,
      minHeight: centerHeight - radius,
      maxHeight: centerHeight + radius
    }
  }
  // ================================
  // 检查网格与叶子瓦片的精确求交
  // ================================
  checkGridTileIntersections (gridBounds, leafTiles) {
    for (const tile of leafTiles) {
      if (this.isGridIntersectingTileBoundingBox(gridBounds, tile)) {
        return true // 找到一个相交的叶子瓦片即可确定占用状态
      }
    }
    return false
  }
  // ================================
  // 检查网格是否与瓦片外包盒相交(精确计算)
  // ================================
  isGridIntersectingTileBoundingBox (gridBounds, tile) {
    // 获取瓦片的边界体信息
    let boundingVolume = null
    if (tile.boundingVolume) {
      boundingVolume = tile.boundingVolume
    } else if (tile._boundingVolume) {
      boundingVolume = tile._boundingVolume
    } else if (tile.contentBoundingVolume) {
      boundingVolume = tile.contentBoundingVolume
    }
    if (!boundingVolume) {
      return false // 没有边界体信息
    }
    try {
      // 获取瓦片边界框的8个顶点(轴对齐)
      const tileVertices = this.getTileBoundingBoxVertices(boundingVolume)
      if (!tileVertices) {
        return false
      }
      // 将网格边界转换为轴对齐边界框的8个顶点
      const gridVertices = this.getGridBoundingBoxVertices(gridBounds)
      // 执行3D AABB求交检测
      return this.checkAABBIntersection(gridVertices, tileVertices)
    } catch (error) {
      console.warn('处理瓦片边界体时出错:', error)
      return false
    }
  }
  // ================================
  // 获取瓦片边界框的8个顶点(参考FlightPathTiles方法)
  // ================================
  getTileBoundingBoxVertices (boundingVolume) {
    let center = null
    let halfAxes = null
    // 处理不同类型的边界体
    if (boundingVolume instanceof Cesium.OrientedBoundingBox) {
      center = boundingVolume.center
      halfAxes = boundingVolume.halfAxes
    } else if (boundingVolume.orientedBoundingBox) {
      center = boundingVolume.orientedBoundingBox.center
      halfAxes = boundingVolume.orientedBoundingBox.halfAxes
    } else if (boundingVolume._orientedBoundingBox) {
      center = boundingVolume._orientedBoundingBox.center
      halfAxes = boundingVolume._orientedBoundingBox.halfAxes
    } else if (boundingVolume instanceof Cesium.BoundingSphere) {
      return this.getBoundingSphereVertices(boundingVolume)
    } else if (boundingVolume.boundingSphere) {
      return this.getBoundingSphereVertices(boundingVolume.boundingSphere)
    } else if (boundingVolume._boundingSphere) {
      return this.getBoundingSphereVertices(boundingVolume._boundingSphere)
    }
    if (!center || !halfAxes) {
      return null
    }
    return this.calculateBoundingBoxVertices(center, halfAxes)
  }
  // ================================
  // 计算边界框的8个顶点(轴对齐,参考FlightPathTiles方法)
  // ================================
  calculateBoundingBoxVertices (center, halfAxes) {
    // 计算所有8个顶点,然后找到轴对齐的边界框
    const tempVertices = []
    const directions = [
      [-1, -1, -1], [-1, -1, 1], [-1, 1, -1], [-1, 1, 1],
      [1, -1, -1], [1, -1, 1], [1, 1, -1], [1, 1, 1]
    ]
    // 提取三个轴向的向量
    const xAxis = new Cesium.Cartesian3()
    const yAxis = new Cesium.Cartesian3()
    const zAxis = new Cesium.Cartesian3()
    Cesium.Matrix3.getColumn(halfAxes, 0, xAxis)
    Cesium.Matrix3.getColumn(halfAxes, 1, yAxis)
    Cesium.Matrix3.getColumn(halfAxes, 2, zAxis)
    // 计算原始的8个顶点
    directions.forEach(dir => {
      const vertex = Cesium.Cartesian3.clone(center)
      Cesium.Cartesian3.add(vertex, Cesium.Cartesian3.multiplyByScalar(xAxis, dir[0], new Cesium.Cartesian3()), vertex)
      Cesium.Cartesian3.add(vertex, Cesium.Cartesian3.multiplyByScalar(yAxis, dir[1], new Cesium.Cartesian3()), vertex)
      Cesium.Cartesian3.add(vertex, Cesium.Cartesian3.multiplyByScalar(zAxis, dir[2], new Cesium.Cartesian3()), vertex)
      tempVertices.push(vertex)
    })
    // 转换为地理坐标,找到边界范围
    let minLon = Infinity, maxLon = -Infinity
    let minLat = Infinity, maxLat = -Infinity
    let minHeight = Infinity, maxHeight = -Infinity
    tempVertices.forEach(vertex => {
      const cartographic = Cesium.Cartographic.fromCartesian(vertex)
      const lon = Cesium.Math.toDegrees(cartographic.longitude)
      const lat = Cesium.Math.toDegrees(cartographic.latitude)
      const height = cartographic.height
      minLon = Math.min(minLon, lon)
      maxLon = Math.max(maxLon, lon)
      minLat = Math.min(minLat, lat)
      maxLat = Math.max(maxLat, lat)
      minHeight = Math.min(minHeight, height)
      maxHeight = Math.max(maxHeight, height)
    })
    // 创建轴对齐的边界框顶点(平行于地面)
    const vertices = [
      // 底面4个顶点
      Cesium.Cartesian3.fromDegrees(minLon, minLat, minHeight), // 0: 左下后
      Cesium.Cartesian3.fromDegrees(maxLon, minLat, minHeight), // 1: 右下后
      Cesium.Cartesian3.fromDegrees(minLon, maxLat, minHeight), // 2: 左上后
      Cesium.Cartesian3.fromDegrees(maxLon, maxLat, minHeight), // 3: 右上后
      // 顶面4个顶点
      Cesium.Cartesian3.fromDegrees(minLon, minLat, maxHeight), // 4: 左下前
      Cesium.Cartesian3.fromDegrees(maxLon, minLat, maxHeight), // 5: 右下前
      Cesium.Cartesian3.fromDegrees(minLon, maxLat, maxHeight), // 6: 左上前
      Cesium.Cartesian3.fromDegrees(maxLon, maxLat, maxHeight)  // 7: 右上前
    ]
    return vertices
  }
  // ================================
  // 处理球体边界的情况
  // ================================
  getBoundingSphereVertices (sphere) {
    const center = sphere.center
    const radius = sphere.radius
    // 将球心转换为地理坐标
    const centerCartographic = Cesium.Cartographic.fromCartesian(center)
    const centerLon = Cesium.Math.toDegrees(centerCartographic.longitude)
    const centerLat = Cesium.Math.toDegrees(centerCartographic.latitude)
    const centerHeight = centerCartographic.height
    // 估算经纬度范围(简化处理)
    const deltaLon = radius / (111320 * Math.cos(centerCartographic.latitude)) // 经度差
    const deltaLat = radius / 110540 // 纬度差
    // 创建轴对齐的边界框顶点
    const vertices = [
      // 底面4个顶点
      Cesium.Cartesian3.fromDegrees(centerLon - deltaLon, centerLat - deltaLat, centerHeight - radius),
      Cesium.Cartesian3.fromDegrees(centerLon + deltaLon, centerLat - deltaLat, centerHeight - radius),
      Cesium.Cartesian3.fromDegrees(centerLon - deltaLon, centerLat + deltaLat, centerHeight - radius),
      Cesium.Cartesian3.fromDegrees(centerLon + deltaLon, centerLat + deltaLat, centerHeight - radius),
      // 顶面4个顶点
      Cesium.Cartesian3.fromDegrees(centerLon - deltaLon, centerLat - deltaLat, centerHeight + radius),
      Cesium.Cartesian3.fromDegrees(centerLon + deltaLon, centerLat - deltaLat, centerHeight + radius),
      Cesium.Cartesian3.fromDegrees(centerLon - deltaLon, centerLat + deltaLat, centerHeight + radius),
      Cesium.Cartesian3.fromDegrees(centerLon + deltaLon, centerLat + deltaLat, centerHeight + radius)
    ]
    return vertices
  }
  // ================================
  // 获取网格边界框的8个顶点
  // ================================
  getGridBoundingBoxVertices (gridBounds) {
    const vertices = [
      // 底面4个顶点
      Cesium.Cartesian3.fromDegrees(gridBounds.minLon, gridBounds.minLat, gridBounds.minHeight),
      Cesium.Cartesian3.fromDegrees(gridBounds.maxLon, gridBounds.minLat, gridBounds.minHeight),
      Cesium.Cartesian3.fromDegrees(gridBounds.minLon, gridBounds.maxLat, gridBounds.minHeight),
      Cesium.Cartesian3.fromDegrees(gridBounds.maxLon, gridBounds.maxLat, gridBounds.minHeight),
      // 顶面4个顶点
      Cesium.Cartesian3.fromDegrees(gridBounds.minLon, gridBounds.minLat, gridBounds.maxHeight),
      Cesium.Cartesian3.fromDegrees(gridBounds.maxLon, gridBounds.minLat, gridBounds.maxHeight),
      Cesium.Cartesian3.fromDegrees(gridBounds.minLon, gridBounds.maxLat, gridBounds.maxHeight),
      Cesium.Cartesian3.fromDegrees(gridBounds.maxLon, gridBounds.maxLat, gridBounds.maxHeight)
    ]
    return vertices
  }
  // ================================
  // 检查两个轴对齐边界框(AABB)是否相交
  // ================================
  checkAABBIntersection (vertices1, vertices2) {
    // 将顶点转换为地理坐标进行比较
    const bounds1 = this.getVerticesBounds(vertices1)
    const bounds2 = this.getVerticesBounds(vertices2)
    // 3D AABB相交检测
    const tolerance = 0.0001
    const intersects = !(
      bounds1.maxLon < bounds2.minLon - tolerance ||
      bounds1.minLon > bounds2.maxLon + tolerance ||
      bounds1.maxLat < bounds2.minLat - tolerance ||
      bounds1.minLat > bounds2.maxLat + tolerance ||
      bounds1.maxHeight < bounds2.minHeight - tolerance ||
      bounds1.minHeight > bounds2.maxHeight + tolerance
    )
    return intersects
  }
  // ================================
  // 从顶点计算边界范围
  // ================================
  getVerticesBounds (vertices) {
    let minLon = Infinity, maxLon = -Infinity
    let minLat = Infinity, maxLat = -Infinity
    let minHeight = Infinity, maxHeight = -Infinity
    vertices.forEach(vertex => {
      const cartographic = Cesium.Cartographic.fromCartesian(vertex)
      const lon = Cesium.Math.toDegrees(cartographic.longitude)
      const lat = Cesium.Math.toDegrees(cartographic.latitude)
      const height = cartographic.height
      minLon = Math.min(minLon, lon)
      maxLon = Math.max(maxLon, lon)
      minLat = Math.min(minLat, lat)
      maxLat = Math.max(maxLat, lat)
      minHeight = Math.min(minHeight, height)
      maxHeight = Math.max(maxHeight, height)
    })
    return {
      minLon: minLon,
      maxLon: maxLon,
      minLat: minLat,
      maxLat: maxLat,
      minHeight: minHeight,
      maxHeight: maxHeight
    }
  }
  // ================================
  // 计算网格单元的边界范围
  // ================================
  calculateGridBounds (gridCenter) {
    const gridLon = Cesium.Math.toDegrees(gridCenter.longitude)
    const gridLat = Cesium.Math.toDegrees(gridCenter.latitude)
    const gridHeight = gridCenter.height
    // 计算网格单元的边界范围
    const earthRadius = 6371000
    const halfSize = this.gridSize / 2
    // 计算经纬度的半范围
    const deltaLon = halfSize / (earthRadius * Math.cos(gridCenter.latitude))
    const deltaLat = halfSize / earthRadius
    return {
      minLon: gridLon - Cesium.Math.toDegrees(deltaLon),
      maxLon: gridLon + Cesium.Math.toDegrees(deltaLon),
      minLat: gridLat - Cesium.Math.toDegrees(deltaLat),
      maxLat: gridLat + Cesium.Math.toDegrees(deltaLat),
      minHeight: gridHeight - halfSize,
      maxHeight: gridHeight + halfSize
    }
  }
  // ================================
  // 创建网格单元的边界框
  // ================================
  createGridBoundingBox (gridCenter) {
    // 将地理坐标转换为笛卡尔坐标
    const centerCartesian = Cesium.Cartesian3.fromRadians(
      gridCenter.longitude,
      gridCenter.latitude,
      gridCenter.height
    )
    // 创建半扩展向量
    const halfSize = this.gridSize / 2
    const halfExtents = new Cesium.Cartesian3(halfSize, halfSize, halfSize)
    return {
      center: centerCartesian,
      halfExtents: halfExtents
    }
  }
  // ================================
  // 检查两个边界框是否相交
  // ================================
  checkBoundingBoxIntersection (box1, box2) {
    // 计算两个边界框的最小和最大点
    const box1Min = Cesium.Cartesian3.subtract(box1.center, box1.halfExtents, new Cesium.Cartesian3())
    const box1Max = Cesium.Cartesian3.add(box1.center, box1.halfExtents, new Cesium.Cartesian3())
    const box2Min = Cesium.Cartesian3.subtract(box2.center, box2.halfExtents, new Cesium.Cartesian3())
    const box2Max = Cesium.Cartesian3.add(box2.center, box2.halfExtents, new Cesium.Cartesian3())
    // 检查在三个轴上是否都有重叠
    return (box1Min.x <= box2Max.x && box1Max.x >= box2Min.x &&
      box1Min.y <= box2Max.y && box1Max.y >= box2Min.y &&
      box1Min.z <= box2Max.z && box1Max.z >= box2Min.z)
  }
  // ================================
  // 创建网格立方体 - 使用配置的颜色和尺寸
  // ================================
  createGridCube (gridCenter, isOccupied, customGridSize = null, gridIndex = null) {
    // 将地理坐标转换为笛卡尔坐标
    const position = Cesium.Cartesian3.fromRadians(
      gridCenter.longitude,
      gridCenter.latitude,
      gridCenter.height
    )
    // 使用配置的颜色和透明度
    const color = isOccupied ?
      (this.config.occupiedColor || Cesium.Color.RED.withAlpha(this.config.occupiedAlpha)) :
      (this.config.freeColor || Cesium.Color.GREEN.withAlpha(this.config.freeAlpha))
    const outlineColor = isOccupied ?
      this.config.occupiedOutlineColor :
      this.config.freeOutlineColor
    // 使用自定义尺寸或配置的网格尺寸
    const gridSize = customGridSize || this.config.gridSize
    const dimensions = new Cesium.Cartesian3(
      this.config.gridWidth || gridSize,
      this.config.gridHeight || gridSize,
      this.config.gridDepth || gridSize
    )
    // 创建立方体实体
    const entity = this.viewer.entities.add({
      position: position,
      show: isOccupied ? this.gridParams.showOccupancy : this.gridParams.showIdle,
      box: {
        dimensions: dimensions,
        material: color,
        outline: true,
        outlineColor: outlineColor,
        outlineWidth: this.config.outlineWidth
      },
      // 添加占用状态标记,用于统计
      properties: {
        isOccupied: isOccupied,
        gridType: isOccupied ? 'occupied' : 'free',
        createdAt: new Date().toISOString(),
        gridIndex: gridIndex // 存储网格索引号
      }
    })
    this.gridEntities.push(entity)
    if (isOccupied && this.config.enableLogging) {
      const indexStr = gridIndex ? `索引[${gridIndex.x},${gridIndex.y},${gridIndex.z}] ` : ''
      console.log(`创建占用网格 ${indexStr}在位置: [${Cesium.Math.toDegrees(gridCenter.longitude).toFixed(6)}, ${Cesium.Math.toDegrees(gridCenter.latitude).toFixed(6)}, ${gridCenter.height.toFixed(1)}]`)
    }
  }
  // ================================
  // 清除网格
  // ================================
  clearGrid () {
    // 移除所有网格实体
    this.gridEntities.forEach(entity => {
      this.viewer.entities.remove(entity)
    })
    this.gridEntities = []
    // 清除瓦片边界框数据
    this.tilesBoundingBoxes = []
    console.log('已清除3D占用网格')
  }
  // ================================
  // 切换网格显示
  // ================================
  toggleGridVisibility (visible) {
    this.gridEntities.forEach(entity => {
      entity.show = visible
    })
    console.log(`3D占用网格${visible ? '显示' : '隐藏'}`)
  }
  // ================================
  // 获取网格统计信息 - 使用配置参数
  // ================================
  getGridStatistics () {
    const totalGrids = this.gridEntities.length
    const occupiedGrids = this.gridEntities.filter(entity =>
      entity.properties && entity.properties.isOccupied
    ).length
    const freeGrids = totalGrids - occupiedGrids
    const occupancyRate = totalGrids > 0 ? Math.round((occupiedGrids / totalGrids) * 100) : 0
    if (this.config.enableLogging) {
      console.log(`网格统计: 总计${totalGrids}, 占用${occupiedGrids}, 空闲${freeGrids}, 占用率${occupancyRate}%`)
      console.log(`网格配置: 尺寸${this.config.gridWidth}x${this.config.gridHeight}x${this.config.gridDepth}m, 扩展${this.config.heightExtension}m(垂直)/${this.config.widthExtension}m(水平)`)
    }
    return {
      total: totalGrids,
      occupied: occupiedGrids,
      free: freeGrids,
      occupancyRate: occupancyRate,
      config: this.getConfig()
    }
  }
  // ================================
  // 获取占用网格数量
  // ================================
  getOccupiedGridCount () {
    return this.gridEntities.filter(entity =>
      entity.properties && entity.properties.isOccupied
    ).length
  }
  // 只显示占用
  sheGridDisplay () {
    this.gridEntities.forEach(entity => {
      const isOccupied = entity.properties.isOccupied?._value
      entity.show = isOccupied ? this.gridParams.showOccupancy : this.gridParams.showIdle
    })
  }
  // ================================
  // 将OrientedBoundingBox转换为轴对齐边界框
  // ================================
  orientedBoundingBoxToAxisAligned (obb) {
    const center = obb.center
    const halfAxes = obb.halfAxes
    // 计算8个顶点
    const vertices = []
    const directions = [
      [-1, -1, -1], [-1, -1, 1], [-1, 1, -1], [-1, 1, 1],
      [1, -1, -1], [1, -1, 1], [1, 1, -1], [1, 1, 1]
    ]
    // 提取三个轴向的向量
    const xAxis = new Cesium.Cartesian3()
    const yAxis = new Cesium.Cartesian3()
    const zAxis = new Cesium.Cartesian3()
    Cesium.Matrix3.getColumn(halfAxes, 0, xAxis)
    Cesium.Matrix3.getColumn(halfAxes, 1, yAxis)
    Cesium.Matrix3.getColumn(halfAxes, 2, zAxis)
    directions.forEach(dir => {
      const vertex = Cesium.Cartesian3.clone(center)
      Cesium.Cartesian3.add(vertex, Cesium.Cartesian3.multiplyByScalar(xAxis, dir[0], new Cesium.Cartesian3()), vertex)
      Cesium.Cartesian3.add(vertex, Cesium.Cartesian3.multiplyByScalar(yAxis, dir[1], new Cesium.Cartesian3()), vertex)
      Cesium.Cartesian3.add(vertex, Cesium.Cartesian3.multiplyByScalar(zAxis, dir[2], new Cesium.Cartesian3()), vertex)
      vertices.push(vertex)
    })
    // 转换为地理坐标,找到边界范围
    let minLon = Infinity, maxLon = -Infinity
    let minLat = Infinity, maxLat = -Infinity
    let minHeight = Infinity, maxHeight = -Infinity
    vertices.forEach(vertex => {
      const cartographic = Cesium.Cartographic.fromCartesian(vertex)
      const lon = Cesium.Math.toDegrees(cartographic.longitude)
      const lat = Cesium.Math.toDegrees(cartographic.latitude)
      const height = cartographic.height
      minLon = Math.min(minLon, lon)
      maxLon = Math.max(maxLon, lon)
      minLat = Math.min(minLat, lat)
      maxLat = Math.max(maxLat, lat)
      minHeight = Math.min(minHeight, height)
      maxHeight = Math.max(maxHeight, height)
    })
    return {
      center: center,
      minLon: minLon,
      maxLon: maxLon,
      minLat: minLat,
      maxLat: maxLat,
      minHeight: minHeight,
      maxHeight: maxHeight
    }
  }
  // ================================
  // 将BoundingSphere转换为轴对齐边界框
  // ================================
  sphereToAxisAligned (sphere) {
    const center = sphere.center
    const radius = sphere.radius
    // 将球心转换为地理坐标
    const centerCartographic = Cesium.Cartographic.fromCartesian(center)
    const centerLon = Cesium.Math.toDegrees(centerCartographic.longitude)
    const centerLat = Cesium.Math.toDegrees(centerCartographic.latitude)
    const centerHeight = centerCartographic.height
    // 估算经纬度范围(简化处理)
    const deltaLon = radius / (111320 * Math.cos(centerCartographic.latitude)) // 经度差
    const deltaLat = radius / 110540 // 纬度差
    return {
      center: center,
      minLon: centerLon - deltaLon,
      maxLon: centerLon + deltaLon,
      minLat: centerLat - deltaLat,
      maxLat: centerLat + deltaLat,
      minHeight: centerHeight - radius,
      maxHeight: centerHeight + radius
    }
  }
}
export default OccupancyGrid
src/views/gridManagement/GridSettings/PathPlanning.js
New file
@@ -0,0 +1,1058 @@
// ================================
// A*航线规划模块 - 基于3D栅格索引
// ================================
import * as Cesium from 'cesium'
class PathPlanning {
  constructor(viewer, occupancyGrid) {
    this.viewer = viewer;
    this.occupancyGrid = occupancyGrid;
    this.pathEntities = [];
    this.isEnabled = false;
    // 路径配置
    this.config = {
      pathColor: Cesium.Color.YELLOW.withAlpha(0.8), // 路径颜色(黄色)
      pathOutlineColor: Cesium.Color.ORANGE, // 路径边框颜色
      startPointColor: Cesium.Color.BLUE.withAlpha(0.9), // 起点颜色(蓝色)
      endPointColor: Cesium.Color.BLUE.withAlpha(0.9), // 终点颜色(蓝色)
      startEndOutlineColor: Cesium.Color.DARKBLUE, // 起点终点边框颜色
      outlineWidth: 2, // 边框宽度
      enableDiagonal: true, // 是否允许对角线移动(3D中为26连通性)
      verticalWeight: 1.2 // 垂直移动权重(略高于水平移动)
    };
    // 显示控制
    this.showOnlyPath = false; // 是否只显示路径单元
    this.hasActivePath = false; // 是否有活跃的路径
    // 3D栅格数据结构
    this.grid3D = null; // 3维数组存储占用状态
    this.gridDimensions = { width: 0, height: 0, depth: 0 }; // 栅格尺寸
    this.gridEntityMap = new Map(); // 栅格索引(字符串)到实体的映射
    this.indexToEntityMap = new Map(); // 栅格索引对象到实体的映射
    // A*算法相关
    this.openList = []; // 开放列表
    this.closedList = []; // 关闭列表
  }
  // ================================
  // 启用/禁用路径规划功能
  // ================================
  setEnabled(enabled) {
    this.isEnabled = enabled;
    if (!enabled) {
      this.clearPath();
    }
  }
  // ================================
  // A*路径规划主函数 - 完全基于3D栅格索引
  // ================================
  async planPath() {
    if (!this.isEnabled || !this.occupancyGrid.gridEntities.length) {
      console.warn('路径规划未启用或网格未生成');
      return;
    }
    try {
      console.log('开始基于3D栅格索引的A*路径规划...');
      // 1. 构建3D栅格索引结构和占用状态数组
      this.build3DGridStructure();
      // 2. 通过最近邻查找获取航线起点和终点对应的3D栅格索引
      const { startIndex, endIndex } = await this.findWaypointGridIndices();
      if (!startIndex || !endIndex) {
        console.warn('无法找到航线起点或终点对应的栅格索引');
        alert('无法找到航线起点或终点对应的栅格索引!');
        return;
      }
      console.log('起点栅格索引:', startIndex);
      console.log('终点栅格索引:', endIndex);
      console.log('3D栅格维度:', this.gridDimensions);
      // 3. 首先显示起点和终点(蓝色)
      console.log('首先显示起点和终点...');
      this.visualizeStartEndPoints(startIndex, endIndex);
      // 等待一小段时间让用户看到起点和终点
      await new Promise(resolve => setTimeout(resolve, 1000));
      // 4. 在三维数组上执行A*路径规划
      console.log('开始执行A*路径规划算法...');
      const pathIndices = this.aStarOnGrid3D(startIndex, endIndex);
      if (pathIndices && pathIndices.length > 0) {
        // 5. 通过索引找到对应的3D占用网格单元,复制并修改颜色(包含起点终点的蓝色)
        this.visualizePathByGridCopy(pathIndices, startIndex, endIndex);
        console.log(`路径规划成功,路径长度:${pathIndices.length}个栅格索引`);
        // 更新UI
        this.updatePathInfo(pathIndices);
      } else {
        console.warn('未找到可行路径');
        alert('未找到从起点到终点的可行路径!');
        // 即使没找到路径,也保持起点终点的显示
      }
    } catch (error) {
      console.error('路径规划错误:', error);
      alert('路径规划过程中发生错误,请查看控制台日志。');
    }
  }
  // ================================
  // 构建3D栅格索引结构和占用状态数组 - 直接使用网格实体properties数据
  // ================================
  build3DGridStructure() {
    console.log('开始构建3D栅格索引结构,直接使用网格实体properties数据...');
    if (!this.occupancyGrid.gridEntities.length) {
      throw new Error('没有可用的占用网格实体');
    }
    // 第一步:分析所有网格实体,从properties中提取gridIndex,确定栅格维度
    let minX = Infinity, minY = Infinity, minZ = Infinity;
    let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
    const validEntities = [];
    // 遍历所有网格实体,提取gridIndex信息
    for (const entity of this.occupancyGrid.gridEntities) {
      // 直接从properties中获取gridIndex
      if (entity.properties && entity.properties.gridIndex) {
        const gridIndex = entity.properties.gridIndex.getValue();
        if (gridIndex && typeof gridIndex.x === 'number' &&
            typeof gridIndex.y === 'number' && typeof gridIndex.z === 'number') {
          // 更新维度范围
          minX = Math.min(minX, gridIndex.x);
          maxX = Math.max(maxX, gridIndex.x);
          minY = Math.min(minY, gridIndex.y);
          maxY = Math.max(maxY, gridIndex.y);
          minZ = Math.min(minZ, gridIndex.z);
          maxZ = Math.max(maxZ, gridIndex.z);
          validEntities.push(entity);
        } else {
          console.warn('网格实体properties中的gridIndex格式不正确:', gridIndex);
        }
      } else {
        console.warn('网格实体缺少gridIndex属性');
      }
    }
    if (validEntities.length === 0) {
      throw new Error('没有找到包含有效gridIndex的网格实体');
    }
    // 计算栅格维度(基于实际的索引范围)
    this.gridDimensions = {
      width: maxX - minX + 1,
      height: maxY - minY + 1,
      depth: maxZ - minZ + 1
    };
    console.log(`从properties提取的3D栅格维度: ${this.gridDimensions.width} x ${this.gridDimensions.height} x ${this.gridDimensions.depth}`);
    console.log(`索引范围: X[${minX}, ${maxX}], Y[${minY}, ${maxY}], Z[${minZ}, ${maxZ}]`);
    // 第二步:初始化3D占用状态数组
    this.grid3D = new Array(this.gridDimensions.width);
    for (let x = 0; x < this.gridDimensions.width; x++) {
      this.grid3D[x] = new Array(this.gridDimensions.height);
      for (let y = 0; y < this.gridDimensions.height; y++) {
        this.grid3D[x][y] = new Array(this.gridDimensions.depth);
        for (let z = 0; z < this.gridDimensions.depth; z++) {
          this.grid3D[x][y][z] = {
            occupied: false,
            entity: null,
            exists: false
          };
        }
      }
    }
    // 第三步:直接使用实体properties数据填充3D数组
    this.gridEntityMap.clear();
    this.indexToEntityMap.clear();
    let mappedCount = 0;
    let occupiedCount = 0;
    let freeCount = 0;
    // 记录索引偏移量,将原始索引映射到数组索引
    this.indexOffset = { x: minX, y: minY, z: minZ };
    for (const entity of validEntities) {
      const gridIndex = entity.properties.gridIndex.getValue();
      const isOccupied = entity.properties.isOccupied ? entity.properties.isOccupied.getValue() : false;
      // 转换为数组索引(相对于最小值的偏移)
      const arrayX = gridIndex.x - minX;
      const arrayY = gridIndex.y - minY;
      const arrayZ = gridIndex.z - minZ;
      // 确保索引在有效范围内
      if (arrayX >= 0 && arrayX < this.gridDimensions.width &&
          arrayY >= 0 && arrayY < this.gridDimensions.height &&
          arrayZ >= 0 && arrayZ < this.gridDimensions.depth) {
        // 存储到3D数组
        this.grid3D[arrayX][arrayY][arrayZ] = {
          occupied: isOccupied,
          entity: entity,
          exists: true,
          originalIndex: { x: gridIndex.x, y: gridIndex.y, z: gridIndex.z } // 保存原始索引
        };
        // 建立映射关系
        const gridKey = `${arrayX},${arrayY},${arrayZ}`;
        this.gridEntityMap.set(gridKey, entity);
        this.indexToEntityMap.set(`${arrayX}_${arrayY}_${arrayZ}`, entity);
        mappedCount++;
        if (isOccupied) {
          occupiedCount++;
        } else {
          freeCount++;
        }
      } else {
        console.warn(`网格索引超出范围: 原始(${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}) -> 数组(${arrayX}, ${arrayY}, ${arrayZ})`);
      }
    }
    console.log(`3D栅格结构构建完成:`);
    console.log(`- 栅格维度: ${this.gridDimensions.width} x ${this.gridDimensions.height} x ${this.gridDimensions.depth}`);
    console.log(`- 索引偏移: (${this.indexOffset.x}, ${this.indexOffset.y}, ${this.indexOffset.z})`);
    console.log(`- 映射实体数: ${mappedCount}`);
    console.log(`- 占用栅格: ${occupiedCount}`);
    console.log(`- 空闲栅格: ${freeCount}`);
    console.log(`- 占用率: ${mappedCount > 0 ? ((occupiedCount / mappedCount) * 100).toFixed(1) : 0}%`);
  }
  // ================================
  // 通过最近邻查找获取航线起点和终点对应的3D栅格索引
  // ================================
  async findWaypointGridIndices() {
    console.log('开始通过最近邻查找航线起点和终点对应的栅格索引...');
    // 获取航线航点
    const waypoints = this.getFlightWaypoints();
    if (!waypoints || waypoints.length < 2) {
      console.warn('航线航点不足,无法确定起点和终点');
      return { startIndex: null, endIndex: null };
    }
    const startWaypoint = waypoints[0];
    const endWaypoint = waypoints[waypoints.length - 1];
    console.log('航线起点位置:', startWaypoint.position);
    console.log('航线终点位置:', endWaypoint.position);
    // 使用最近邻搜索找到对应的栅格索引
    const startIndex = this.findNearestGridIndex(startWaypoint.position);
    const endIndex = this.findNearestGridIndex(endWaypoint.position);
    if (!startIndex) {
      console.error('无法找到航线起点对应的栅格索引');
      return { startIndex: null, endIndex: null };
    }
    if (!endIndex) {
      console.error('无法找到航线终点对应的栅格索引');
      return { startIndex: null, endIndex: null };
    }
    // 验证找到的栅格索引是否可通行
    if (this.grid3D[startIndex.x][startIndex.y][startIndex.z].occupied) {
      console.warn('起点栅格被占用,尝试寻找附近的空闲栅格...');
      const freeStartIndex = this.findNearestFreeGrid(startIndex);
      if (freeStartIndex) {
        console.log(`找到起点附近的空闲栅格: (${freeStartIndex.x}, ${freeStartIndex.y}, ${freeStartIndex.z})`);
        startIndex.x = freeStartIndex.x;
        startIndex.y = freeStartIndex.y;
        startIndex.z = freeStartIndex.z;
      } else {
        console.error('起点附近没有可通行的栅格');
        return { startIndex: null, endIndex: null };
      }
    }
    if (this.grid3D[endIndex.x][endIndex.y][endIndex.z].occupied) {
      console.warn('终点栅格被占用,尝试寻找附近的空闲栅格...');
      const freeEndIndex = this.findNearestFreeGrid(endIndex);
      if (freeEndIndex) {
        console.log(`找到终点附近的空闲栅格: (${freeEndIndex.x}, ${freeEndIndex.y}, ${freeEndIndex.z})`);
        endIndex.x = freeEndIndex.x;
        endIndex.y = freeEndIndex.y;
        endIndex.z = freeEndIndex.z;
      } else {
        console.error('终点附近没有可通行的栅格');
        return { startIndex: null, endIndex: null };
      }
    }
    console.log(`成功找到起点栅格索引: (${startIndex.x}, ${startIndex.y}, ${startIndex.z})`);
    console.log(`成功找到终点栅格索引: (${endIndex.x}, ${endIndex.y}, ${endIndex.z})`);
    return { startIndex, endIndex };
  }
  // ================================
  // 最近邻搜索:找到距离指定世界坐标最近的栅格索引 - 直接使用properties数据
  // ================================
  findNearestGridIndex(targetPosition) {
    let nearestIndex = null;
    let minDistance = Infinity;
    let nearestEntity = null;
    // 直接遍历所有网格实体,使用properties中的gridIndex
    for (const entity of this.occupancyGrid.gridEntities) {
      if (entity.properties && entity.properties.gridIndex && entity.position) {
        try {
          // 获取栅格实体的位置
          const entityPosition = entity.position.getValue(this.viewer.clock.currentTime);
          // 计算距离
          const distance = Cesium.Cartesian3.distance(targetPosition, entityPosition);
          if (distance < minDistance) {
            minDistance = distance;
            nearestEntity = entity;
            // 直接从properties获取gridIndex,并转换为数组索引
            const originalIndex = entity.properties.gridIndex.getValue();
            nearestIndex = {
              x: originalIndex.x - this.indexOffset.x,
              y: originalIndex.y - this.indexOffset.y,
              z: originalIndex.z - this.indexOffset.z,
              original: originalIndex // 保存原始索引以便调试
            };
          }
        } catch (error) {
          console.warn('处理网格实体时出错:', error);
        }
      }
    }
    if (nearestIndex) {
      console.log(`找到最近栅格索引: 数组索引(${nearestIndex.x}, ${nearestIndex.y}, ${nearestIndex.z}), 原始索引(${nearestIndex.original.x}, ${nearestIndex.original.y}, ${nearestIndex.original.z}), 距离: ${minDistance.toFixed(2)}m`);
      // 验证找到的索引是否有效
      if (!this.isValidGridIndex(nearestIndex)) {
        console.error('找到的栅格索引无效');
        return null;
      }
      // 移除original属性,只返回数组索引
      return { x: nearestIndex.x, y: nearestIndex.y, z: nearestIndex.z };
    } else {
      console.warn('未找到最近的栅格索引');
      return null;
    }
  }
  // ================================
  // 寻找指定栅格索引附近的空闲(可通行)栅格
  // ================================
  findNearestFreeGrid(centerIndex) {
    const searchRadius = 3; // 搜索半径
    for (let radius = 1; radius <= searchRadius; radius++) {
      for (let dx = -radius; dx <= radius; dx++) {
        for (let dy = -radius; dy <= radius; dy++) {
          for (let dz = -radius; dz <= radius; dz++) {
            const x = centerIndex.x + dx;
            const y = centerIndex.y + dy;
            const z = centerIndex.z + dz;
            if (this.isValidGridIndex({ x, y, z })) {
              const cell = this.grid3D[x][y][z];
              if (cell.exists && !cell.occupied) {
                return { x, y, z };
              }
            }
          }
        }
      }
    }
    return null; // 未找到空闲栅格
  }
  // ================================
  // 获取航线航点
  // ================================
  getFlightWaypoints() {
    // 尝试从UAVApp获取航线航点
    if (window.uavApp && window.uavApp.waypoints && window.uavApp.waypoints.length > 0) {
      return window.uavApp.waypoints;
    }
    console.warn('未找到航线航点,将无法进行基于航线的路径规划');
    return [];
  }
  // ================================
  // 在三维数组上执行A*路径规划算法
  // ================================
  aStarOnGrid3D(startIndex, endIndex) {
    console.log('开始在三维数组上执行A*路径规划算法...');
    console.log(`起点: (${startIndex.x}, ${startIndex.y}, ${startIndex.z})`);
    console.log(`终点: (${endIndex.x}, ${endIndex.y}, ${endIndex.z})`);
    // 清理算法数据结构
    this.openList = [];
    this.closedList = [];
    // 创建起始节点
    const startNode = {
      x: startIndex.x,
      y: startIndex.y,
      z: startIndex.z,
      g: 0,
      h: this.calculateHeuristic(startIndex, endIndex),
      f: 0,
      parent: null
    };
    startNode.f = startNode.g + startNode.h;
    this.openList.push(startNode);
    let iterations = 0;
    const maxIterations = 50000;
    let verticalMoves = 0;
    while (this.openList.length > 0 && iterations < maxIterations) {
      iterations++;
      // 找到f值最小的节点
      this.openList.sort((a, b) => a.f - b.f);
      const currentNode = this.openList.shift();
      // 如果到达终点
      if (currentNode.x === endIndex.x &&
          currentNode.y === endIndex.y &&
          currentNode.z === endIndex.z) {
        console.log(`A*算法完成,迭代次数:${iterations}`);
        console.log(`垂直移动次数:${verticalMoves}`);
        return this.reconstructPath(currentNode);
      }
      // 将当前节点移到关闭列表
      this.closedList.push(currentNode);
      // 获取邻居节点
      const neighbors = this.getNeighbors(currentNode);
      for (const neighbor of neighbors) {
        // 跳过已在关闭列表中的节点
        if (this.isInClosedList(neighbor)) {
          continue;
        }
        // 检查是否可通行
        if (!this.isTraversable(neighbor.x, neighbor.y, neighbor.z)) {
          continue;
        }
        // 计算移动成本
        const moveCost = this.getMoveCost(currentNode, neighbor);
        const tentativeG = currentNode.g + moveCost;
        // 统计垂直移动
        if (neighbor.z !== currentNode.z) {
          verticalMoves++;
        }
        // 检查是否已在开放列表中
        const existingNode = this.findInOpenList(neighbor);
        if (existingNode) {
          if (tentativeG < existingNode.g) {
            existingNode.g = tentativeG;
            existingNode.h = this.calculateHeuristic(neighbor, endIndex);
            existingNode.f = existingNode.g + existingNode.h;
            existingNode.parent = currentNode;
          }
        } else {
          const newNode = {
            x: neighbor.x,
            y: neighbor.y,
            z: neighbor.z,
            g: tentativeG,
            h: this.calculateHeuristic(neighbor, endIndex),
            f: 0,
            parent: currentNode
          };
          newNode.f = newNode.g + newNode.h;
          this.openList.push(newNode);
        }
      }
      // 每1000次迭代输出一次进度
      if (iterations % 1000 === 0) {
        const openCount = this.openList.length;
        const closedCount = this.closedList.length;
        console.log(`A*算法进度: ${iterations}次迭代, 开放列表: ${openCount}, 关闭列表: ${closedCount}`);
      }
    }
    console.warn(`A*算法未找到路径,迭代次数:${iterations}`);
    return null;
  }
  // ================================
  // 重构路径
  // ================================
  reconstructPath(endNode) {
    const path = [];
    let currentNode = endNode;
    while (currentNode) {
      path.unshift({
        x: currentNode.x,
        y: currentNode.y,
        z: currentNode.z
      });
      currentNode = currentNode.parent;
    }
    console.log(`路径重构完成,路径长度: ${path.length}个节点`);
    return path;
  }
  // ================================
  // 计算启发式函数(3D曼哈顿距离)
  // ================================
  calculateHeuristic(index1, index2) {
    const dx = Math.abs(index1.x - index2.x);
    const dy = Math.abs(index1.y - index2.y);
    const dz = Math.abs(index1.z - index2.z);
    // 使用3D曼哈顿距离,给垂直移动适当权重
    return dx + dy + (dz * this.config.verticalWeight);
  }
  // ================================
  // 获取邻居节点(3D:26连通性)
  // ================================
  getNeighbors(currentNode) {
    const neighbors = [];
    // 3D搜索:包括所有26个方向的邻居
    for (let dx = -1; dx <= 1; dx++) {
      for (let dy = -1; dy <= 1; dy++) {
        for (let dz = -1; dz <= 1; dz++) {
          if (dx === 0 && dy === 0 && dz === 0) continue;
          // 如果不允许对角线移动,只使用6连通性
          if (!this.config.enableDiagonal) {
            const isDirectional = (Math.abs(dx) + Math.abs(dy) + Math.abs(dz)) === 1;
            if (!isDirectional) continue;
          }
          const newX = currentNode.x + dx;
          const newY = currentNode.y + dy;
          const newZ = currentNode.z + dz;
          // 检查边界
          if (this.isValidGridIndex({ x: newX, y: newY, z: newZ })) {
            neighbors.push({ x: newX, y: newY, z: newZ });
          }
        }
      }
    }
    return neighbors;
  }
  // ================================
  // A*算法辅助方法
  // ================================
  isInClosedList(node) {
    return this.closedList.some(n =>
      n.x === node.x && n.y === node.y && n.z === node.z
    );
  }
  findInOpenList(node) {
    return this.openList.find(n =>
      n.x === node.x && n.y === node.y && n.z === node.z
    );
  }
  // ================================
  // 检查位置是否可通行
  // ================================
  isTraversable(x, y, z) {
    if (!this.isValidGridIndex({ x, y, z })) {
      return false;
    }
    const cell = this.grid3D[x][y][z];
    // 如果该位置不存在网格实体,则不可通行
    if (!cell.exists) {
      return false;
    }
    // 如果有实体但未被占用,可通行
    return !cell.occupied;
  }
  // ================================
  // 计算移动成本
  // ================================
  getMoveCost(fromNode, toNode) {
    const dx = Math.abs(toNode.x - fromNode.x);
    const dy = Math.abs(toNode.y - fromNode.y);
    const dz = Math.abs(toNode.z - fromNode.z);
    // 计算基础移动成本
    let baseCost;
    if (dx && dy && dz) {
      baseCost = Math.sqrt(3); // 3D对角线移动
    } else if ((dx && dy) || (dx && dz) || (dy && dz)) {
      baseCost = Math.sqrt(2); // 2D对角线移动
    } else {
      baseCost = 1; // 直线移动
    }
    // 如果是垂直移动,应用垂直权重
    if (dz > 0) {
      baseCost *= this.config.verticalWeight;
    }
    return baseCost;
  }
  // ================================
  // 通过索引找到对应的3D占用网格单元,复制并修改为黄色 - 使用properties数据
  // ================================
  visualizePathByGridCopy(pathIndices, startIndex, endIndex) {
    console.log('开始通过复制网格单元可视化路径...');
    console.log('路径索引序列:', pathIndices.map(p => `(${p.x},${p.y},${p.z})`).join(' -> '));
    console.log(`路径总长度: ${pathIndices.length}个栅格索引`);
    // 清除之前的路径
    this.clearPath();
    let copiedCount = 0;
    let missingCount = 0;
    for (let i = 0; i < pathIndices.length; i++) {
      const gridIndex = pathIndices[i];
      // 从3D数组中获取对应的网格单元
      const cell = this.grid3D[gridIndex.x][gridIndex.y][gridIndex.z];
      if (cell.exists && cell.entity) {
        // 获取原始网格实体的信息
        const sourceEntity = cell.entity;
        const sourcePosition = sourceEntity.position.getValue(this.viewer.clock.currentTime);
        const sourceDimensions = sourceEntity.box.dimensions.getValue();
        // 获取原始索引(用于显示和命名)
        const originalIndex = cell.originalIndex || {
          x: gridIndex.x + this.indexOffset.x,
          y: gridIndex.y + this.indexOffset.y,
          z: gridIndex.z + this.indexOffset.z
        };
        // 判断是否为起点或终点
        const isStartPoint = (gridIndex.x === startIndex.x &&
                             gridIndex.y === startIndex.y &&
                             gridIndex.z === startIndex.z);
        const isEndPoint = (gridIndex.x === endIndex.x &&
                           gridIndex.y === endIndex.y &&
                           gridIndex.z === endIndex.z);
        // 根据点的类型选择颜色和名称
        let entityColor, outlineColor, entityName, pointType;
        if (isStartPoint) {
          entityColor = this.config.startPointColor;
          outlineColor = this.config.startEndOutlineColor;
          entityName = `StartPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`;
          pointType = '起点';
        } else if (isEndPoint) {
          entityColor = this.config.endPointColor;
          outlineColor = this.config.startEndOutlineColor;
          entityName = `EndPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`;
          pointType = '终点';
        } else {
          entityColor = this.config.pathColor;
          outlineColor = this.config.pathOutlineColor;
          entityName = `PathGrid_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`;
          pointType = '路径';
        }
        // 复制网格单元,修改颜色
        const pathEntity = this.viewer.entities.add({
          name: entityName,
          position: sourcePosition,
          box: {
            dimensions: sourceDimensions,
            material: entityColor,
            outline: true,
            outlineColor: outlineColor,
            outlineWidth: this.config.outlineWidth
          },
          properties: {
            isPathGrid: true,
            gridIndex: originalIndex, // 使用原始索引
            arrayIndex: { x: gridIndex.x, y: gridIndex.y, z: gridIndex.z }, // 保存数组索引
            sourceEntityId: sourceEntity.id,
            pathStep: copiedCount,
            pointType: pointType
          }
        });
        this.pathEntities.push(pathEntity);
        copiedCount++;
        console.log(`复制网格单元 数组索引[${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 原始索引[${originalIndex.x}, ${originalIndex.y}, ${originalIndex.z}] -> ${pointType}网格`);
      } else {
        missingCount++;
        console.warn(`网格索引 [${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 对应的网格单元不存在`);
      }
    }
    // 设置路径状态
    this.hasActivePath = copiedCount > 0;
    // 如果启用了只显示路径模式,隐藏其他网格
    if (this.showOnlyPath && this.hasActivePath) {
      this.updateGridVisibility();
    }
    console.log(`路径可视化完成:`);
    console.log(`- 成功复制网格: ${copiedCount}个`);
    console.log(`- 缺失网格: ${missingCount}个`);
    // 显示起点和终点的原始索引
    const startOriginalIndex = this.grid3D[startIndex.x][startIndex.y][startIndex.z].originalIndex ||
      { x: startIndex.x + this.indexOffset.x, y: startIndex.y + this.indexOffset.y, z: startIndex.z + this.indexOffset.z };
    const endOriginalIndex = this.grid3D[endIndex.x][endIndex.y][endIndex.z].originalIndex ||
      { x: endIndex.x + this.indexOffset.x, y: endIndex.y + this.indexOffset.y, z: endIndex.z + this.indexOffset.z };
    console.log(`- 起点: 数组索引(${startIndex.x}, ${startIndex.y}, ${startIndex.z}) 原始索引(${startOriginalIndex.x}, ${startOriginalIndex.y}, ${startOriginalIndex.z}) -> 蓝色`);
    console.log(`- 终点: 数组索引(${endIndex.x}, ${endIndex.y}, ${endIndex.z}) 原始索引(${endOriginalIndex.x}, ${endOriginalIndex.y}, ${endOriginalIndex.z}) -> 蓝色`);
    if (missingCount > 0) {
      console.warn(`⚠️ 有${missingCount}个路径索引对应的网格单元缺失`);
    }
    // 统计路径的3D特征
    this.analyzePathCharacteristics(pathIndices);
  }
  // ================================
  // 分析路径特征
  // ================================
  analyzePathCharacteristics(pathIndices) {
    if (!pathIndices || pathIndices.length === 0) {
      return;
    }
    console.log('=== 路径特征分析 ===');
    // 计算路径的空间范围
    const xValues = pathIndices.map(p => p.x);
    const yValues = pathIndices.map(p => p.y);
    const zValues = pathIndices.map(p => p.z);
    const xRange = [Math.min(...xValues), Math.max(...xValues)];
    const yRange = [Math.min(...yValues), Math.max(...yValues)];
    const zRange = [Math.min(...zValues), Math.max(...zValues)];
    console.log(`X轴范围: [${xRange[0]}, ${xRange[1]}], 跨度: ${xRange[1] - xRange[0]}`);
    console.log(`Y轴范围: [${yRange[0]}, ${yRange[1]}], 跨度: ${yRange[1] - yRange[0]}`);
    console.log(`Z轴范围: [${zRange[0]}, ${zRange[1]}], 跨度: ${zRange[1] - zRange[0]}`);
    // 统计移动类型
    let verticalMoves = 0;
    let horizontalMoves = 0;
    let diagonalMoves = 0;
    for (let i = 1; i < pathIndices.length; i++) {
      const prev = pathIndices[i - 1];
      const curr = pathIndices[i];
      const dx = Math.abs(curr.x - prev.x);
      const dy = Math.abs(curr.y - prev.y);
      const dz = Math.abs(curr.z - prev.z);
      if (dz > 0) verticalMoves++;
      if (dx > 0 || dy > 0) horizontalMoves++;
      if ((dx && dy) || (dx && dz) || (dy && dz)) diagonalMoves++;
    }
    const totalMoves = pathIndices.length - 1;
    console.log(`总移动步数: ${totalMoves}`);
    console.log(`垂直移动: ${verticalMoves} (${(verticalMoves/totalMoves*100).toFixed(1)}%)`);
    console.log(`水平移动: ${horizontalMoves} (${(horizontalMoves/totalMoves*100).toFixed(1)}%)`);
    console.log(`对角移动: ${diagonalMoves} (${(diagonalMoves/totalMoves*100).toFixed(1)}%)`);
    // 检查3D路径特征
    const hasVerticalVariation = (zRange[1] - zRange[0]) > 0;
    if (hasVerticalVariation) {
      console.log('✅ 路径包含3D垂直变化,成功进行了立体路径规划');
    } else {
      console.log('ℹ️ 路径没有垂直变化,为平面路径');
    }
    console.log('=== 分析完成 ===');
  }
  // ================================
  // 清除路径
  // ================================
  clearPath() {
    // 从场景中移除路径网格实体
    for (const pathEntity of this.pathEntities) {
      if (this.viewer.entities.contains(pathEntity)) {
        this.viewer.entities.remove(pathEntity);
      }
    }
    this.pathEntities = [];
    this.hasActivePath = false; // 重置路径状态
    // 恢复所有网格的显示
    // this.updateGridVisibility();
    console.log('路径已清除,所有路径网格(包括起点、终点)已从场景中移除');
  }
  // ================================
  // 更新路径信息UI
  // ================================
  updatePathInfo(pathIndices) {
    const pathLengthElement = document.getElementById('pathLength');
    const pathStatusElement = document.getElementById('pathStatus');
    if (pathLengthElement) {
      pathLengthElement.textContent = pathIndices.length;
    }
    if (pathStatusElement) {
      pathStatusElement.textContent = '规划完成';
    }
  }
  // ================================
  // 设置只显示路径模式
  // ================================
  setShowOnlyPath(showOnly) {
    this.showOnlyPath = showOnly;
    this.updateGridVisibility();
    console.log(`只显示路径模式: ${showOnly ? '启用' : '禁用'}`);
  }
  // ================================
  // 更新网格显示状态
  // ================================
  updateGridVisibility() {
    if (!this.occupancyGrid.gridEntities.length) {
      return;
    }
    // 控制原始占用网格的显示
    for (const entity of this.occupancyGrid.gridEntities) {
      if (entity.show !== undefined) {
        if (this.showOnlyPath && this.hasActivePath) {
          // 只显示路径模式:隐藏原始网格
          entity.show = false;
        } else {
          // 正常显示模式:显示所有原始网格
          entity.show = true;
        }
      }
    }
    // 控制路径网格的显示(黄色路径网格始终显示,除非被清除)
    for (const pathEntity of this.pathEntities) {
      if (pathEntity.show !== undefined) {
        pathEntity.show = true; // 路径网格始终显示
      }
    }
    const pathGridCount = this.pathEntities.length;
    const originalGridCount = this.occupancyGrid.gridEntities.length;
    console.log(`网格显示状态已更新: ${this.showOnlyPath ? '只显示路径' : '显示全部'}`);
    console.log(`当前显示: 原始网格 ${this.showOnlyPath && this.hasActivePath ? 0 : originalGridCount} 个, 路径网格 ${pathGridCount} 个`);
  }
  // ================================
  // 验证栅格索引是否有效
  // ================================
  isValidGridIndex(index) {
    return index &&
           typeof index.x === 'number' &&
           typeof index.y === 'number' &&
           typeof index.z === 'number' &&
           index.x >= 0 && index.x < this.gridDimensions.width &&
           index.y >= 0 && index.y < this.gridDimensions.height &&
           index.z >= 0 && index.z < this.gridDimensions.depth;
  }
  // ================================
  // 首先显示起点和终点(蓝色)
  // ================================
  visualizeStartEndPoints(startIndex, endIndex) {
    console.log('开始显示起点和终点...');
    // 清除之前的路径(如果有)
    this.clearPath();
    // 显示起点(蓝色)
    this.createPointVisualization(startIndex, 'start');
    // 显示终点(蓝色)
    this.createPointVisualization(endIndex, 'end');
    console.log(`起点和终点已显示为蓝色:`);
    console.log(`- 起点: (${startIndex.x}, ${startIndex.y}, ${startIndex.z})`);
    console.log(`- 终点: (${endIndex.x}, ${endIndex.y}, ${endIndex.z})`);
  }
  // ================================
  // 创建单个点的可视化(起点或终点) - 使用properties数据
  // ================================
  createPointVisualization(gridIndex, pointType) {
    // 从3D数组中获取对应的网格单元
    const cell = this.grid3D[gridIndex.x][gridIndex.y][gridIndex.z];
    if (cell.exists && cell.entity) {
      // 获取原始网格实体的信息
      const sourceEntity = cell.entity;
      const sourcePosition = sourceEntity.position.getValue(this.viewer.clock.currentTime);
      const sourceDimensions = sourceEntity.box.dimensions.getValue();
      // 获取原始索引(用于显示和命名)
      const originalIndex = cell.originalIndex || {
        x: gridIndex.x + this.indexOffset.x,
        y: gridIndex.y + this.indexOffset.y,
        z: gridIndex.z + this.indexOffset.z
      };
      // 根据点类型设置属性
      let entityColor, outlineColor, entityName, pointTypeChinese;
      if (pointType === 'start') {
        entityColor = this.config.startPointColor;
        outlineColor = this.config.startEndOutlineColor;
        entityName = `StartPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`;
        pointTypeChinese = '起点';
      } else if (pointType === 'end') {
        entityColor = this.config.endPointColor;
        outlineColor = this.config.startEndOutlineColor;
        entityName = `EndPoint_${originalIndex.x}_${originalIndex.y}_${originalIndex.z}`;
        pointTypeChinese = '终点';
      }
      // 创建点的可视化实体
      const pointEntity = this.viewer.entities.add({
        name: entityName,
        position: sourcePosition,
        box: {
          dimensions: sourceDimensions,
          material: entityColor,
          outline: true,
          outlineColor: outlineColor,
          outlineWidth: this.config.outlineWidth
        },
        properties: {
          isPathGrid: true,
          gridIndex: originalIndex, // 使用原始索引
          arrayIndex: { x: gridIndex.x, y: gridIndex.y, z: gridIndex.z }, // 保存数组索引
          sourceEntityId: sourceEntity.id,
          pointType: pointTypeChinese
        }
      });
      this.pathEntities.push(pointEntity);
      console.log(`创建${pointTypeChinese}可视化: 数组索引[${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 原始索引[${originalIndex.x}, ${originalIndex.y}, ${originalIndex.z}]`);
    } else {
      console.warn(`网格索引 [${gridIndex.x}, ${gridIndex.y}, ${gridIndex.z}] 对应的网格单元不存在,无法创建${pointType}可视化`);
    }
  }
  // ================================
  // 设置路径实体透明度
  // ================================
  setPathAlpha(alpha) {
    if (alpha < 0.1 || alpha > 1.0) {
      console.warn('透明度值应该在0.1到1.0之间');
      return;
    }
    console.log(`设置路径透明度为: ${alpha}`);
    // 更新配置中的颜色透明度
    this.config.pathColor = Cesium.Color.YELLOW.withAlpha(alpha);
    this.config.startPointColor = Cesium.Color.BLUE.withAlpha(alpha);
    this.config.endPointColor = Cesium.Color.BLUE.withAlpha(alpha);
    // 更新所有现有的路径实体透明度
    let updatedCount = 0;
    for (const pathEntity of this.pathEntities) {
      if (pathEntity.box && pathEntity.box.material) {
        try {
          // 获取实体的类型
          const pointType = pathEntity.properties && pathEntity.properties.pointType ?
            pathEntity.properties.pointType.getValue() : '路径';
          // 根据实体类型设置对应的颜色
          let newColor;
          if (pointType === '起点' || pointType === '终点') {
            newColor = Cesium.Color.BLUE.withAlpha(alpha);
          } else {
            newColor = Cesium.Color.YELLOW.withAlpha(alpha);
          }
          // 更新实体的材质颜色
          pathEntity.box.material = newColor;
          updatedCount++;
        } catch (error) {
          console.warn('更新路径实体透明度时出错:', error);
        }
      }
    }
    console.log(`成功更新${updatedCount}个路径实体的透明度`);
  }
  // ================================
  // 获取当前路径透明度
  // ================================
  getPathAlpha() {
    // 从配置中获取当前透明度
    if (this.config.pathColor && this.config.pathColor.alpha !== undefined) {
      return this.config.pathColor.alpha;
    }
    return 0.8; // 默认透明度
  }
}
// ================================
// 导出模块(如果使用模块系统)
// ================================
if (typeof module !== 'undefined' && module.exports) {
  module.exports = PathPlanning;
}
export default PathPlanning
src/views/gridManagement/gridManagement.vue
New file
@@ -0,0 +1,623 @@
<template>
  <div class="gridManagement">
    <div class="search-box">
      <el-form :model="params" inline>
        <div style="display: flex;justify-content: space-between">
         <div>
          <el-form-item label="网格名称:">
            <el-input v-model="params.gridName" placeholder="请输入网格名称" clearable />
          </el-form-item>
         </div>
         <div>
          <el-form-item>
            <el-button type="primary" @click="getList">搜索</el-button>
            <el-button @click="cancelSearch">取消</el-button>
          </el-form-item>
         </div>
        </div>
      </el-form>
      <div>
        <el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增网格</el-button>
      </div>
    </div>
    <div class="mange-table">
            <el-table border :data="tableList" class="custom-header">
                <el-table-column label="序号" type="index" width="60"></el-table-column>
        <el-table-column prop="grid_name" label="网格名称" align="center" show-overflow-tooltip></el-table-column>
        <el-table-column prop="start_point" label="起点" align="center" show-overflow-tooltip></el-table-column>
        <el-table-column prop="end_point" label="终点" align="center" show-overflow-tooltip></el-table-column>
        <el-table-column prop="create_time" label="创建时间" align="center"></el-table-column>
        <el-table-column prop="nick_name" label="创建人" align="center"></el-table-column>
                <el-table-column label="操作" width="180" align="center">
                    <template #default="scope">
            <!-- <el-button icon="el-icon-view" type="text" @click="handleDetail(scope.row)">查看</el-button> -->
            <el-button icon="el-icon-edit" type="text" @click="handleEdit(scope.row)">编辑</el-button>
                        <el-button icon="el-icon-delete" type="text" @click="handleDelete(scope.row)">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>
        </div>
        <div class="pagination">
            <el-pagination class="ztzf-pagination" popper-class="custom-pagination-dropdown" background
                :page-sizes="[10, 20, 30, 40, 50, 100]" :size="size" v-model:current-page="params.current"
                v-model:page-size="params.size" layout="total, sizes, prev, pager, next, jumper" :total="total"
                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
        </div>
  </div>
  <el-dialog class="ztzf-dialog" append-to-body v-model="isShowEditView" :title="titleTxt"
        :width="pxToRem(1000)" :close-on-click-modal="false" :destroy-on-close="true" @close="handleClose">
        <div class="content-edit">
      <el-form ref="ruleFormRef" :model="editParams" :rules="rules" inline>
        <el-form-item label="网格名称" prop="grid_name">
          <el-input v-model="editParams.grid_name" />
        </el-form-item>
      </el-form>
      <div class="map-container">
                <div id="GridManagementMap" class="ztzf-cesium"></div>
            </div>
      <div class="btns">
        <el-button type="primary" @click="meshAnalysis"><el-icon><Location /></el-icon>开启网格</el-button>
        <el-button type="primary" @click="clearGrid"><el-icon><DeleteLocation /></el-icon>清除网格</el-button>
        <el-button v-if="titleTxt === '新增'" type="primary" @click="submit(ruleFormRef)"><el-icon><CirclePlus /></el-icon>确认</el-button>
        <el-button v-else type="primary" @click="submit(ruleFormRef)"><el-icon><CircleCheck /></el-icon>修改</el-button>
        <el-button @click="isShowEditView = false"><el-icon><CircleClose /></el-icon>取消</el-button>
      </div>
        <div v-show="showContextMenu" class="context-menu" :style="contextMenuStyle">
          <div class="menu-item" @click="clearAllPoints">清空网格</div>
        </div>
    </div>
    </el-dialog>
</template>
<script setup>
import { airGridPage, airGridUpdate, airGridAdd, airGridDelete } from '@/api/airspace/airspace';
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium'
import OccupancyGrid from './GridSettings/OccupancyGrid'
import PathPlanning from './GridSettings/PathPlanning'
import newStartPoint from '@/assets/images/newStartPoint.png'
import { cartesian3Convert,getLnglatAltitude } from '@/utils/cesium/mapUtil'
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import { getPolyLine } from '@/views/RoutePlan/PointAirLine/pointWayLineUtils'
let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
    color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
    directionColor: new Cesium.Color(1, 1, 1, 1),
    outlineColor: new Cesium.Color(1, 1, 1, 1),
    outlineWidth: 0,
    speed: 5,
})
const total = ref(0)
const params = ref({
  current: 1,
  size: 10,
  gridName: '',
});
let titleTxt = ref('新增')
let tableList = ref([])
let isShowView = ref(false)
let rowView = ref({})
const showContextMenu = ref(false)
const contextMenuStyle = ref({
    left: '0px',
    top: '0px',
})
let isShowEditView = ref(false)
const ruleFormRef = ref()
let editParams = ref({
  id: '',
  grid_name: '',
  start_point: '',
  end_point: '',
})
const rules = reactive({
  grid_name: [
    {
      required: true,
      message: '请输入网格名称',
      trigger: 'blur',
    },
  ],
})
function cancelSearch() {
  params.value = {
    id: '',
    gridName: '',
  }
  params.value.current = 1
  getList()
}
function getList() {
  airGridPage(params.value).then(res => {
    tableList.value = res.data.data.records || []
    total.value = res.data.data.total || 0
  })
}
function handleDelete (row) {
  ElMessageBox.confirm('确定删除吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => {
      airGridDelete(row.id).then(res => {
        ElMessage.success('删除成功')
        getList()
      }).catch(error => {
        ElMessage.error('删除失败');
      });
    })
    .catch(() => {
      ElMessage({
        type: 'info',
        message: '已取消删除',
      })
    });
}
async function handleAdd() {
  titleTxt.value = '新增'
  editParams.value.grid_name = ''
  isShowEditView.value = true
  await nextTick()
    initMap()
}
async function handleEdit(row) {
  titleTxt.value = '编辑'
  isShowEditView.value = true
  editParams.value = { ...row }
  const startPoint = row.start_point.split(',')
  const endPoint = row.end_point.split(',')
  const height = row.grid_height.split(',')
  pointList.value.push({
    longitude: Number(startPoint[0]),
    latitude: Number(startPoint[1]),
    height: Number(height[0]),
  })
  pointList.value.push({
    longitude: Number(endPoint[0]),
    latitude: Number(endPoint[1]),
    height: Number(height[0]),
  })
  await nextTick()
    initMap()
}
function handleSizeChange(val) {
  params.value.size = val
  getList()
}
function handleCurrentChange(val) {
  params.value.current = val
  getList()
}
async function submit(formValidate) {
  if (!formValidate) return
  await formValidate.validate((valid, fields) => {
    if (valid) {
      if (pointList.value.length === 0) {
        ElMessage.warning('请先添加起点和终点')
        return
      }
      const params = {
        id: editParams.value.id,
        grid_name: editParams.value.grid_name,
        start_point: `${pointList.value[0].longitude},${pointList.value[0].latitude}`,
        end_point: `${pointList.value[1].longitude},${pointList.value[1].latitude}`,
        grid_height: `${pointList.value[0].height},${pointList.value[1].height}`,
      }
      if (titleTxt.value === '新增') {
        airGridAdd(params).then(res => {
          isShowEditView.value = false
          ElMessage.success('新增成功')
          getList()
        })
      } else {
        airGridUpdate(params).then(res => {
          isShowEditView.value = false
          ElMessage.success('操作成功')
          getList()
        })
      }
    }
  })
}
let publicCesiumInstance = null
let viewer = null
const viewInstance = shallowRef(null);
let handler = null
let occupancyGrid = null
// 地图
const initMap = async () => {
    if (!document.getElementById('GridManagementMap')) {
        return
    }
    publicCesiumInstance = new PublicCesium({
        dom: 'GridManagementMap',
    flyToContour: false,
        flatMode: false,
        terrain: true,
        layerMode: 4,
        contour: true,
    })
    viewer = publicCesiumInstance.getViewer()
  viewInstance.value = publicCesiumInstance;
  viewer.scene.globe.depthTestAgainstTerrain = true;
  // viewer.flyTo({latitude: 28.624613214568868,longitude:115.85669891599704}, 4, 65)
  viewer?.camera.flyTo({
    destination: Cesium.Cartesian3.fromDegrees(115.85669891599704, 28.624613214568868, 1000),
    duration: 1,
    orientation: {
      heading: Cesium.Math.toRadians(0.0),
      pitch: Cesium.Math.toRadians(-90.0),
      roll: 0.0,
    },
  });
  addCustomLayers(tilesTreeData.value.title, tilesTreeData.value)
  occupancyGrid = new OccupancyGrid(viewer, gridConfig, gridParams)
  // 添加事件
  handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
    handler.setInputAction(singleClickEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
  handler.setInputAction(click => {
        // 获取鼠标在页面上的坐标
        const mousePosition = {
            x: event.clientX,
            y: event.clientY,
        }
        showContextMenu.value = true
        contextMenuStyle.value = {
            left: mousePosition.x + 'px',
            top: mousePosition.y + 'px',
            position: 'fixed', // 确保使用固定定位
            zIndex: 9999, // 确保菜单显示在最上层
        }
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
  cesiumContextMenu(false)
  if (titleTxt.value === '编辑') {
    ShowTwoPoints()
    // meshAnalysis()
  }
}
// 倾斜摄影
const tilesTreeData = ref(
    {
        id: '1',
        label: '空间大厦',
        type: 'layer-map',
        title: 'kjds_3Dtile',
        mapType: 'Cesium3DTile',
        src: '/3Dtile/kjds/tileset.json',
    },
)
const gridParams = reactive({
    hasGrid: false, //生成了网格
    hasRoute: false, //生成了航线
    showIdle: true, //显示占用网格
    showOccupancy: true, //显示空闲网格
})
// 初始化占用网格 - 使用自定义配置
const gridConfig = {
    // 网格尺寸配置
    gridSize: 6,
    gridWidth: 12,
    gridHeight: 12,
    gridDepth: 12,
    // 区域扩展配置(确保4层网格生成)
    heightExtension: 72, // 向上向下各扩展120米,确保4层网格(总高度240米,4×50=200米,留有余量)
    widthExtension: 12, // 水平方向扩展100米
    // 颜色配置
    occupiedColor: Cesium.Color.RED.withAlpha(0.05),
    freeColor: Cesium.Color.GREEN.withAlpha(0.05),
    occupiedOutlineColor: Cesium.Color.DARKRED,
    freeOutlineColor: Cesium.Color.DARKGREEN,
    outlineWidth: 1,
    // 性能配置100000
    maxGridCount: 500000,
    enableBatching: true,
    // 显示配置
    showOccupiedOnly: false,
    showFreeOnly: false,
    enableLogging: true,
}
const addCustomLayers = async (layerName, options) => {
    if (options.mapType === 'arcgis') {
        let imageryProvider = new Cesium.WebMapTileServiceImageryProvider({
            url: options.src,
            layer: options.title,
            style: 'default',
            format: 'image/png',
            tileMatrixSetID: 'default',
            tilingScheme: new Cesium.GeographicTilingScheme(),
            tileMatrixLabels: [
                '0',
                '1',
                '2',
                '3',
                '4',
                '5',
                '6',
                '7',
                '8',
                '9',
                '10',
                '11',
                '12',
                '13',
                '14',
                '15',
                '16',
                '17',
                '18',
                '19',
            ],
        })
         viewer?.imageryLayers.addImageryProvider(imageryProvider)
    }
    if (options.mapType === 'Cesium3DTile') {
        const tileset = await Cesium.Cesium3DTileset.fromUrl(options.src)
         viewer?.scene.primitives.add(tileset)
    }
}
let pointList = ref([])
async function singleClickEvent(movement) {
  cesiumContextMenu(false)
  if (pointList.value.length === 2) {
    ElMessage.warning('最多只能添加两个点,可以先清空')
    return
  }
  // 先清除point-
  viewer.entities.removeAll()
  const { longitude, latitude } = cartesian3Convert(viewer.scene.pickPosition(movement.position), viewer)
  let result = await getLnglatAltitude(longitude, latitude, viewer)
  let height = result.height + 60
  const point = { longitude, latitude, height }
  pointList.value.push(point)
  ShowTwoPoints()
}
// 生成两个点
function ShowTwoPoints() {
  pointList.value.map((item, index) => {
    const position = Cesium.Cartesian3.fromDegrees(
      Number(item.longitude),
      Number(item.latitude),
      Number(item.height)
    )
    viewer.entities.add({
      id: `point-${index}`,
      position: position,
      billboard: {
        image: new Cesium.ConstantProperty(newStartPoint),
        width: 40,
                height: 40,
      },
    })
    viewer?.entities.add({
                id: 'placement-' + index,
                position: position,
                polyline: getPolyLine(viewer, { value: pointList.value }, index),
            })
  })
  const position1 = Cesium.Cartesian3.fromDegrees(
      Number(pointList.value[0].longitude),
      Number(pointList.value[0].latitude),
      Number(pointList.value[0].height)
    )
    const position2= Cesium.Cartesian3.fromDegrees(
      Number(pointList.value[1].longitude),
      Number(pointList.value[1].latitude),
      Number(pointList.value[1].height)
    )
    // 获取两点在屏幕上的位置
    // const screenPos1 = Cesium.SceneTransforms?.wgs84ToWindowCoordinates?.(viewer.scene, position1)
    // || viewer.scene.cartesianToCanvasCoordinates(position1);
    // const screenPos2 = Cesium.SceneTransforms?.wgs84ToWindowCoordinates?.(viewer.scene, position2)
    // || viewer.scene.cartesianToCanvasCoordinates(position2);
    // // // 生成屏幕矩形
    // const boundingRect = new Cesium.BoundingRectangle(
    //   Math.min(screenPos1.x, screenPos2.x),
    //   Math.min(screenPos1.y, screenPos2.y),
    //   Math.abs(screenPos1.x - screenPos2.x),
    //   Math.abs(screenPos1.y - screenPos2.y)
    // );
    // const center = Cesium.Cartesian3.midpoint(screenPos1, screenPos2, new Cesium.Cartesian3());
    // const angle = Cesium.Math.PI_OVER_FOUR; // 45度
    // viewer.entities.add({
    //   position: center,
    //   rectangle: {
    //     coordinates: boundingRect,
    //     material: Cesium.Color.BLUE.withAlpha(0.5),
    //     rotation: angle // 弧度制旋转
    //   }
    // });
  viewer.entities.add({
    id: `line`,
    polyline: {
      width: 2,
      eyeOffset: new Cesium.Cartesian3(0, 0, -6),
      material: arrowLineMaterialProperty,
      clampToGround: false,
      positions: [position1, position2],
    },
  })
}
// 禁用浏览器默认行为处理
const preventDefault = event => {
    event.preventDefault()
    return
}
const cesiumContextMenu = (isAdd = true) => {
    let cesium = document.getElementById('GridManagementMap')
    if (!cesium) return
    if (isAdd) {
        cesium.addEventListener('contextmenu', preventDefault)
    } else {
        cesium.removeEventListener('contextmenu', preventDefault)
    }
}
// 生成网格
function meshAnalysis() {
  if (pointList.value.length !== 2) {
    ElMessage.warning('请先添加起点和终点')
    return
  }
    const pointList1 = pointList.value.map(i => {
        const cartographic = Cesium.Cartographic.fromDegrees(i.longitude, i.latitude, i.height)
        const { x, y, z } = Cesium.Cartesian3.fromRadians(
            cartographic.longitude,
            cartographic.latitude,
            cartographic.height
        )
        return { ...i, position: { x, y, z } }
    })
    window.uavApp = {
        waypoints: pointList1,
    }
    occupancyGrid.generateOccupancyGridWithTiles(pointList1)
}
// 清除网格
function clearGrid() {
    occupancyGrid.clearGrid()
    gridParams.hasGrid = false
}
const handleClose = () => {
  // 清除editParams
  editParams.value = {
    id: '',
    grid_name: '',
    grid_height: '',
    start_point: '',
    end_point: '',
  }
  pointList.value = []
    publicCesiumInstance?.viewerDestroy()
    publicCesiumInstance = null
    viewer = null
  isShowEditView.value = false
  viewer?.scene.primitives.remove(tilesTreeData.value.title)
  cesiumContextMenu(false)
}
function clearAllPoints() {
  showContextMenu.value = false
  pointList.value = []
  viewer?.entities.removeAll()
  clearGrid()
}
onMounted(() => {
  getList()
})
</script>
<style lang="scss" scoped>
  .gridManagement {
    height: 0;
    flex: 1;
    padding: 20px;
    margin: 0 10px 10px 10px;
    background-color: #ffffff;
    // padding: 10px 20px;
    border-radius: 5px;
    display: flex;
    flex-direction: column;
    .search-box {
      margin-top: 20px;
      height: 80px;
    }
    :deep(.el-input) {
      .el-input__wrapper {
        width: 200px;
      }
    }
    // 表格
    .mange-table {
      height: 0;
      flex: 1;
      margin-top: 18px;
      overflow: auto;
    }
    :deep(.el-pagination) {
      display: flex;
      justify-content: right;
    }
    :deep(.el-pagination button) {
      background: center center no-repeat none !important;
      color: #8eb8ea !important;
    }
  }
.content-edit {
  height: 550px;
  .map-container {
        height: 440px;
        #GridManagementMap {
            width: 100%;
            height: 100%;
        }
    }
  .btns {
    margin-top: 16px;
    display: flex;
    justify-content: center;
  }
  .context-menu {
            position: fixed;
            background: #0f1929;
            border: 1px solid #51a8ff;
            border-radius: 4px;
            padding: 4px 0;
            z-index: 9999;
            min-width: 100px; // 设置最小宽度
            box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
            .menu-item {
                padding: 8px 16px;
                color: #d3e9ff;
                cursor: pointer;
                &:hover {
                    background: rgba(81, 168, 255, 0.1);
                }
            }
        }
}
</style>
vite.config.mjs
@@ -5,7 +5,7 @@
// https://vitejs.dev/config/
export default ({ mode, command }) => {
  const env = loadEnv(mode, process.cwd())
  const { VITE_APP_ENV, VITE_APP_BASE, VITE_APP_URL } = env
  const { VITE_APP_ENV, VITE_APP_BASE, VITE_APP_URL, VITE_APP_MAP_TILE_URL } = env
  // 判断是打生产环境包
  const isProd = VITE_APP_ENV === 'production'
@@ -43,7 +43,13 @@
          target: VITE_APP_URL,
          changeOrigin: true,
          rewrite: path => path.replace(/^\/api/, ''),
        }
        },
        '/3Dtile': {
          target: VITE_APP_MAP_TILE_URL,
          changeOrigin: true,
          rewrite: path => path.replace(/^\/3Dtile/, ''),
        },
      },
    },
    assetsInclude: ['**/*.gltf'],