import { ref } from 'vue' import * as Cesium from 'cesium' import { useMapStore } from '@/map' import { BEFORE_IMAGERY_CONFIG, AFTER_IMAGERY_CONFIG, SPLIT_CONFIG, getModelCompareConfig } from '../config/modelCompare.config' import { use3DTiles } from './use3DTiles' import { useMapMarkers } from './useMapMarkers' /** * 调试模式开关 * 生产环境自动关闭详细日志 */ const DEBUG = import.meta.env.DEV /** * 图层标识常量 * @constant {string} BEFORE_LAYER_ID - 灾前现场实景图层 ID * @constant {string} AFTER_LAYER_ID - 灾后现场实景图层 ID */ const BEFORE_LAYER_ID = BEFORE_IMAGERY_CONFIG.id const AFTER_LAYER_ID = AFTER_IMAGERY_CONFIG.id /** * 模型对比(灾前/灾后影像对比)业务逻辑 * * 技术方案: * - 使用单个 Cesium 实例,通过 imagery split(影像分屏)实现左右对比视图 * - 左侧显示灾前现场实景,右侧显示灾后现场实景 * - 默认只显示灾后影像,启用对比模式后同时显示两套影像 * * 使用示例: * ```js * const { isModelCompareActive, initModelCompareLayers, toggleModelCompare } = useModelCompare() * * // 在地图就绪后初始化图层 * mapStore.onReady(async () => { * await initModelCompareLayers() * }) * * // 切换对比模式 * await toggleModelCompare(true) // 启用 * await toggleModelCompare(false) // 禁用 * ``` * * @returns {Object} 模型对比相关状态和方法 * @returns {Ref} isModelCompareActive - 模型对比模式是否激活 * @returns {Function} initModelCompareLayers - 初始化灾前/灾后图层 * @returns {Function} enableModelCompare - 启用模型对比模式 * @returns {Function} disableModelCompare - 禁用模型对比模式 * @returns {Function} toggleModelCompare - 切换模型对比模式 */ export function useModelCompare() { const mapStore = useMapStore() // 初始化 3D Tiles 管理 const { load3DTileset, waitForTilesetReady, remove3DTileset } = use3DTiles() /** 模型对比模式是否激活 */ const isModelCompareActive = ref(false) /** 图层是否已初始化 */ const initialized = ref(false) /** 是否正在执行切换操作(防止并发) */ const isToggling = ref(false) /** * 其他影像图层的原始可见性状态 * 用于在禁用对比模式后恢复原始状态,而非强制全部打开 * Map */ const originalLayerVisibility = new Map() /** 灾前 Tileset 引用(用于在禁用时移除) */ let beforeTilesetRef = null /** * 从 viewer 中查找指定配置的 3D Tileset * @param {Cesium.Viewer} viewer * @param {string} configId - 配置ID,'before' 或 'after' * @returns {Cesium.Cesium3DTileset | null} */ const findTilesetByConfig = (viewer, configId) => { if (!viewer?.scene?.primitives) return null const config = getModelCompareConfig() const targetUrl = configId === 'after' ? config.after3DTiles.url : config.before3DTiles.url // 遍历所有 primitives 查找匹配的 tileset for (let i = 0; i < viewer.scene.primitives.length; i++) { const primitive = viewer.scene.primitives.get(i) if (primitive instanceof Cesium.Cesium3DTileset) { // 比较 URL(去除查询参数和尾部斜杠) const primitiveUrl = primitive.resource?.url || primitive._url || '' const normalizedPrimitiveUrl = primitiveUrl.split('?')[0].replace(/\/$/, '') const normalizedTargetUrl = targetUrl.split('?')[0].replace(/\/$/, '') if (normalizedPrimitiveUrl === normalizedTargetUrl) { return primitive } } } return null } /** * 设置所有 entities 的 splitDirection * @param {Cesium.Viewer} viewer * @param {Cesium.SplitDirection} splitDirection */ const setEntitiesSplitDirection = (viewer, splitDirection) => { if (!viewer?.entities) return console.log(`[useModelCompare] 设置所有 entities 的 splitDirection 为: ${splitDirection}`) let updatedCount = 0 const entities = viewer.entities.values for (let i = 0; i < entities.length; i++) { const entity = entities[i] // 设置 entity 级别的 splitDirection entity.splitDirection = splitDirection // 设置图形属性的 splitDirection if (entity.billboard) { if (typeof entity.billboard.splitDirection === 'object' && entity.billboard.splitDirection.setValue) { entity.billboard.splitDirection.setValue(splitDirection) } else { entity.billboard.splitDirection = new Cesium.ConstantProperty(splitDirection) } } if (entity.polygon) { if (typeof entity.polygon.splitDirection === 'object' && entity.polygon.splitDirection.setValue) { entity.polygon.splitDirection.setValue(splitDirection) } else { entity.polygon.splitDirection = new Cesium.ConstantProperty(splitDirection) } } updatedCount++ } console.log(`[useModelCompare] 已更新 ${updatedCount} 个 entities 的 splitDirection`) } /** * 初始化灾前/灾后影像图层 * * - 仅在首次调用时创建图层 * - 如果地图未就绪,会自动等待地图就绪后再执行 * - 使用占位符 URL,需在接入真实数据时替换为实际影像服务地址 * * @async * @throws {Error} 当图层创建失败时抛出错误 */ const initModelCompareLayers = async () => { // 防止重复初始化 if (initialized.value) { console.log('[useModelCompare] 图层已初始化,跳过重复初始化') return } /** * 实际的图层初始化逻辑 * @async * @private */ const doInit = async () => { try { const { layer } = mapStore.services() // 获取当前环境的配置 const config = getModelCompareConfig() // 检查图层是否已存在(可能被其他模块创建) const beforeExists = layer.getLayer(BEFORE_LAYER_ID) const afterExists = layer.getLayer(AFTER_LAYER_ID) // 创建灾前影像图层 if (!beforeExists) { if (DEBUG) console.log('[useModelCompare] 创建灾前影像图层...') await layer.addLayer({ id: config.before.id, type: 'WebTileLayer', url: config.before.url, options: { visible: config.before.visible, }, meta: { title: config.before.name, sceneType: 'before', description: config.before.description } }) if (DEBUG) console.log('[useModelCompare] 灾前影像图层创建成功') } else { if (DEBUG) console.log('[useModelCompare] 灾前影像图层已存在') } // 创建灾后影像图层 if (!afterExists) { if (DEBUG) console.log('[useModelCompare] 创建灾后影像图层...') await layer.addLayer({ id: config.after.id, type: 'WebTileLayer', url: config.after.url, options: { visible: config.after.visible, }, meta: { title: config.after.name, sceneType: 'after', description: config.after.description } }) if (DEBUG) console.log('[useModelCompare] 灾后影像图层创建成功') } else { if (DEBUG) console.log('[useModelCompare] 灾后影像图层已存在') } initialized.value = true if (DEBUG) console.log('[useModelCompare] 图层初始化完成') } catch (error) { console.error('[useModelCompare] 图层初始化失败:', error) throw new Error(`模型对比图层初始化失败: ${error.message}`) } } // 如果地图已就绪,直接执行初始化 if (mapStore.isReady()) { await doInit() return } // 否则等待地图就绪后再执行 console.log('[useModelCompare] 等待地图就绪...') await new Promise((resolve, reject) => { mapStore.onReady(async () => { try { await doInit() resolve() } catch (error) { reject(error) } }) }) } /** * 启用模型对比模式 * * 启用后: * - 左半屏显示灾前影像 * - 右半屏显示灾后影像 * - 分割线位置默认为中心(0.5) * * @async */ const enableModelCompare = async () => { if (DEBUG) console.log('[useModelCompare] 启用模型对比模式...') // 确保图层已初始化 await initModelCompareLayers() // 如果地图未就绪,无法操作 if (!mapStore.isReady()) { console.warn('[useModelCompare] 地图未就绪,无法启用对比模式') return } try { const { layer } = mapStore.services() // 检查图层是否存在 const beforeLayer = layer.getLayer(BEFORE_LAYER_ID) const afterLayer = layer.getLayer(AFTER_LAYER_ID) if (!beforeLayer || !afterLayer) { console.error('[useModelCompare] 图层不存在,无法启用对比模式') throw new Error('模型对比图层不存在') } // 🔧 修复:保存其他影像图层的原始可见性状态 // 在隐藏图层前先记录它们的状态,以便后续恢复 originalLayerVisibility.clear() // 清空旧状态 const allLayers = layer.listLayers() allLayers.forEach(layerRecord => { if (layerRecord.type === 'imagery' && layerRecord.id !== BEFORE_LAYER_ID && layerRecord.id !== AFTER_LAYER_ID) { // 保存原始可见性状态 originalLayerVisibility.set(layerRecord.id, layerRecord.show) // 隐藏图层,避免遮挡分屏效果 layer.showLayer(layerRecord.id, false) } }) if (DEBUG) { console.log('[useModelCompare] 已保存图层状态:', Array.from(originalLayerVisibility.entries())) } // 左侧:灾前影像;右侧:灾后影��� console.log('[useModelCompare] 设置灾前图层为左侧...') layer.setSplit(BEFORE_LAYER_ID, 'left') console.log('[useModelCompare] 设置灾后图层为右侧...') layer.setSplit(AFTER_LAYER_ID, 'right') // 设置分割位置为中心(可以后续扩展为可拖动调整) console.log('[useModelCompare] 设置分割位置为 0.5...') layer.setSplitPosition(0.5) // 直接使用新 API 设置分割位置(绕过可能的旧 API 问题) const viewer = mapStore.viewer if (viewer) { // 尝试新 API if ('splitPosition' in viewer.scene) { viewer.scene.splitPosition = 0.5 console.log('[useModelCompare] 使用新 API: scene.splitPosition = 0.5') } // 兼容旧 API if ('imagerySplitPosition' in viewer.scene) { viewer.scene.imagerySplitPosition = 0.5 console.log('[useModelCompare] 使用旧 API: scene.imagerySplitPosition = 0.5') } console.log('[useModelCompare] viewer.scene.splitPosition:', viewer.scene.splitPosition) console.log('[useModelCompare] viewer.scene.imagerySplitPosition:', viewer.scene.imagerySplitPosition) // 检查所有影像图层 const imageryLayers = viewer.imageryLayers console.log('[useModelCompare] 影像图层总数:', imageryLayers.length) for (let i = 0; i < imageryLayers.length; i++) { const imgLayer = imageryLayers.get(i) console.log(`[useModelCompare] 图层 ${i}:`, { show: imgLayer.show, alpha: imgLayer.alpha, splitDirection: imgLayer.splitDirection }) } } // 确保两个图层都可见 console.log('[useModelCompare] 显示灾前图层...') layer.showLayer(BEFORE_LAYER_ID, true) console.log('[useModelCompare] 显示灾后图层...') layer.showLayer(AFTER_LAYER_ID, true) // 调试:检查设置后的状态 const beforeLayerAfter = layer.getLayer(BEFORE_LAYER_ID) const afterLayerAfter = layer.getLayer(AFTER_LAYER_ID) console.log('[useModelCompare] 设置后的灾前图层:', beforeLayerAfter) console.log('[useModelCompare] 灾前图层 splitDirection (设置后):', beforeLayerAfter?.obj?.splitDirection) console.log('[useModelCompare] 灾前图层 show:', beforeLayerAfter?.obj?.show) console.log('[useModelCompare] 设置后的灾后图层:', afterLayerAfter) console.log('[useModelCompare] 灾后图层 splitDirection (设置后):', afterLayerAfter?.obj?.splitDirection) console.log('[useModelCompare] 灾后图层 show:', afterLayerAfter?.obj?.show) // 再次检查所有图层的最终状态 if (viewer) { console.log('[useModelCompare] === 最终影像图层状态 ===') const imageryLayers = viewer.imageryLayers for (let i = 0; i < imageryLayers.length; i++) { const imgLayer = imageryLayers.get(i) console.log(`[useModelCompare] 最终图层 ${i}:`, { show: imgLayer.show, alpha: imgLayer.alpha, splitDirection: imgLayer.splitDirection }) } } // ============ 处理 3D Tiles 模型 ============ console.log('[useModelCompare] 开始处理 3D Tiles 模型分割...') // 查找灾后模型 const afterTileset = findTilesetByConfig(viewer, 'after') if (afterTileset) { console.log('[useModelCompare] 找到灾后3D模型,设置为右侧显示') afterTileset.splitDirection = Cesium.SplitDirection.RIGHT } else { console.warn('[useModelCompare] 未找到灾后3D模型') } // 查找或加载灾前3D模型 let beforeTileset = findTilesetByConfig(viewer, 'before') if (!beforeTileset) { console.log('[useModelCompare] 加载灾前3D模型...') beforeTileset = await load3DTileset( viewer, 'before', false, // 不自动缩放 Cesium.SplitDirection.LEFT // 左侧显示 ) if (beforeTileset) { // 保存引用,用于禁用时移除 beforeTilesetRef = beforeTileset console.log('[useModelCompare] 灾前3D模型加载成功,等待就绪...') await waitForTilesetReady(beforeTileset) console.log('[useModelCompare] 灾前3D模型已就绪') } else { console.warn('[useModelCompare] 灾前3D模型加载失败') } } else { console.log('[useModelCompare] 找到已存在的灾前3D模型,设置为左侧显示') beforeTileset.splitDirection = Cesium.SplitDirection.LEFT beforeTilesetRef = beforeTileset } // ============ 处理标记点和实体 ============ console.log('[useModelCompare] 设置所有实体为右侧显示(灾后场景)...') setEntitiesSplitDirection(viewer, Cesium.SplitDirection.RIGHT) isModelCompareActive.value = true console.log('[useModelCompare] 模型对比模式已启用(包含3D模型分割和标记点)') } catch (error) { console.error('[useModelCompare] 启用模型对比模式失败:', error) throw new Error(`启用模型对比模式失败: ${error.message}`) } } /** * 禁用模型对比模式 * * 禁用后: * - 取消影像分屏 * - 隐藏灾前影像 * - 保留灾后影像作为默认视图 * - 恢复其他图层的原始可见性状态 * * @async */ const disableModelCompare = async () => { if (DEBUG) console.log('[useModelCompare] 禁用模型对比模式...') // 如果地图未就绪,仅更新状态即可 if (!mapStore.isReady()) { isModelCompareActive.value = false console.warn('[useModelCompare] 地图未就绪,仅更新状态') return } try { const { layer } = mapStore.services() const viewer = mapStore.viewer // ============ 处理影像图层 ============ // 取消影像分屏 layer.setSplit(BEFORE_LAYER_ID, 'none') layer.setSplit(AFTER_LAYER_ID, 'none') // 隐藏灾前图层,保留灾后图层 layer.showLayer(BEFORE_LAYER_ID, false) layer.showLayer(AFTER_LAYER_ID, true) // 🔧 修复:恢复其他影像图层的原始可见性状态 // 而非强制全部打开 if (originalLayerVisibility.size > 0) { originalLayerVisibility.forEach((visible, layerId) => { layer.showLayer(layerId, visible) }) if (DEBUG) { console.log('[useModelCompare] 已恢复影像图层状态:', Array.from(originalLayerVisibility.entries())) } // 清空已恢复的状态记录 originalLayerVisibility.clear() } // ============ 处理 3D Tiles 模型 ============ console.log('[useModelCompare] 开始恢复 3D Tiles 模型状态...') // 查找并恢复灾后模型为全屏显示 const afterTileset = findTilesetByConfig(viewer, 'after') if (afterTileset) { console.log('[useModelCompare] 恢复灾后3D模型为全屏显示') afterTileset.splitDirection = Cesium.SplitDirection.NONE } // 移除灾前模型 if (beforeTilesetRef) { console.log('[useModelCompare] 移除灾前3D模型') viewer.scene.primitives.remove(beforeTilesetRef) beforeTilesetRef = null } // ============ 处理标记点和实体 ============ console.log('[useModelCompare] 恢复所有实体为全屏显示...') setEntitiesSplitDirection(viewer, Cesium.SplitDirection.NONE) isModelCompareActive.value = false if (DEBUG) console.log('[useModelCompare] 模型对比模式已禁用(包含3D模型和标记点恢复)') } catch (error) { console.error('[useModelCompare] 禁用模型对比模式失败:', error) throw new Error(`禁用模型对比模式失败: ${error.message}`) } } /** * 切换模型对比模式 * * @async * @param {boolean} active - true 启用,false 禁用 */ const toggleModelCompare = async (active) => { if (DEBUG) console.log(`[useModelCompare] 切换模型对比模式: ${active ? '启用' : '禁用'}`) // 防止并发切换 if (isToggling.value) { console.warn('[useModelCompare] 正在执行切换操作,忽略本次请求') return } // 如果地图未就绪,延迟执行 if (!mapStore.isReady()) { console.warn('[useModelCompare] 地图未就绪,将在地图就绪后执行切换') await new Promise((resolve) => { mapStore.onReady(() => resolve()) }) } // 保存之前的状态,用于错误回滚 const previousState = isModelCompareActive.value isToggling.value = true try { if (active) { await enableModelCompare() } else { await disableModelCompare() } } catch (error) { console.error('[useModelCompare] 切换模型对比模式失败:', error) // 在发生错误时恢复到之前的状态 isModelCompareActive.value = previousState throw error } finally { isToggling.value = false } } return { // 状态 isModelCompareActive, // 方法 initModelCompareLayers, enableModelCompare, disableModelCompare, toggleModelCompare } } export default useModelCompare