From 744be26032980317d34d22643bf7bdd59a8dbedb Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Sat, 13 Sep 2025 17:32:57 +0800
Subject: [PATCH] feat:更新网格管理
---
vite.config.mjs | 10
src/views/gridManagement/GridSettings/OccupancyGrid.js | 1293 +++++++++++++++++++++++++
src/api/airspace/airspace.js | 34
src/utils/cesium/publicCesium.js | 1
src/views/gridManagement/gridManagement.vue | 623 ++++++++++++
src/views/gridManagement/GridSettings/PathPlanning.js | 1058 ++++++++++++++++++++
src/views/airspace/airspaceType.vue | 15
7 files changed, 3,028 insertions(+), 6 deletions(-)
diff --git a/src/api/airspace/airspace.js b/src/api/airspace/airspace.js
index 8d9c3f2..5ff6871 100644
--- a/src/api/airspace/airspace.js
+++ b/src/api/airspace/airspace.js
@@ -62,4 +62,36 @@
params
})
}
-// ----------空域录入相关接口调用----------
\ No newline at end of file
+// ----------空域录入相关接口调用----------
+
+// ----------网格管理相关接口调用----------
+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',
+ })
+}
\ No newline at end of file
diff --git a/src/utils/cesium/publicCesium.js b/src/utils/cesium/publicCesium.js
index ef65590..e0d3d88 100644
--- a/src/utils/cesium/publicCesium.js
+++ b/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
diff --git a/src/views/airspace/airspaceType.vue b/src/views/airspace/airspaceType.vue
index 0f927b4..42613b6 100644
--- a/src/views/airspace/airspaceType.vue
+++ b/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 = '新增'
diff --git a/src/views/gridManagement/GridSettings/OccupancyGrid.js b/src/views/gridManagement/GridSettings/OccupancyGrid.js
new file mode 100644
index 0000000..8f9dc1a
--- /dev/null
+++ b/src/views/gridManagement/GridSettings/OccupancyGrid.js
@@ -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
diff --git a/src/views/gridManagement/GridSettings/PathPlanning.js b/src/views/gridManagement/GridSettings/PathPlanning.js
new file mode 100644
index 0000000..abc18a9
--- /dev/null
+++ b/src/views/gridManagement/GridSettings/PathPlanning.js
@@ -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
diff --git a/src/views/gridManagement/gridManagement.vue b/src/views/gridManagement/gridManagement.vue
new file mode 100644
index 0000000..fbe3f7a
--- /dev/null
+++ b/src/views/gridManagement/gridManagement.vue
@@ -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>
\ No newline at end of file
diff --git a/vite.config.mjs b/vite.config.mjs
index 7c1f6b1..9a62dc8 100644
--- a/vite.config.mjs
+++ b/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'],
--
Gitblit v1.9.3