// ================================ // 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