import { ref, onUnmounted } from 'vue' import * as Cesium from 'cesium' import { use3DTiles } from './use3DTiles' /** * 双地图对比模式 * 使用两个独立的Cesium Viewer实现并排对比 * 左侧显示灾前场景,右侧显示灾后场景 * 单向相机同步:右侧主地图驱动,左侧对比地图跟随 */ export function useDualMapCompare() { /** 左侧Viewer引用 */ const leftViewer = ref(null) /** 右侧Viewer引用 */ const rightViewer = ref(null) /** 对比模式是否激活 */ const isCompareMode = ref(false) /** 相机同步监听器移除函数 */ let cameraSyncRemover = null /** 左侧3D Tileset(灾前) */ let leftTileset = null /** 右侧3D Tileset(灾后,主地图的tileset) */ let rightTileset = null const { load3DTileset } = use3DTiles() /** * 初始化左侧Viewer(灾前场景) * @param {HTMLElement} container - 容器元素 * @returns {Cesium.Viewer} */ const initLeftViewer = (container) => { if (!container) { console.error('[useDualMapCompare] 左侧容器元素不存在') return null } // 验证容器尺寸 const { clientWidth, clientHeight } = container console.log(`[useDualMapCompare] 左侧容器尺寸: ${clientWidth}x${clientHeight}`) if (clientWidth <= 0 || clientHeight <= 0) { console.error(`[useDualMapCompare] 左侧容器尺寸无效 (width=${clientWidth}, height=${clientHeight})`) return null } // 创建左侧viewer const viewer = new Cesium.Viewer(container, { animation: false, baseLayerPicker: false, fullscreenButton: false, geocoder: false, homeButton: false, infoBox: false, sceneModePicker: false, selectionIndicator: false, timeline: false, navigationHelpButton: false, scene3DOnly: true, shouldAnimate: false, }) // 移除默认的Cesium logo和版权信息 viewer.cesiumWidget.creditContainer.style.display = 'none' // 完全禁用左侧Viewer的所有交互(只作为对比显示) viewer.scene.screenSpaceCameraController.enableRotate = false viewer.scene.screenSpaceCameraController.enableTranslate = false viewer.scene.screenSpaceCameraController.enableZoom = false viewer.scene.screenSpaceCameraController.enableTilt = false viewer.scene.screenSpaceCameraController.enableLook = false leftViewer.value = viewer console.log('[useDualMapCompare] 左侧Viewer初始化成功(交互已禁用)') return viewer } /** * 设置单向相机同步(右→左) * 右侧主地图驱动,左侧对比地图跟随 * 使用 postRender 实现逐帧同步,直接传递 position 引用以获得最佳性能 * @param {Cesium.Viewer} rightViewerInstance - 右侧Viewer(主地图,驱动者) * @param {Cesium.Viewer} leftViewerInstance - 左侧Viewer(对比地图,跟随者) */ const setupCameraSync = (rightViewerInstance, leftViewerInstance) => { if (!rightViewerInstance || !leftViewerInstance) { console.warn('[useDualMapCompare] Viewer未初始化,无法设置相机同步') return } console.log('[useDualMapCompare] 设置单向相机同步(右→左)...') /** * 相机同步处理函数 * 在右侧 Viewer 的 postRender 中调用,将右侧相机状态同步到左侧 */ const handleCameraSync = () => { // 检查 Viewer 是否有效 if (!leftViewerInstance || leftViewerInstance.isDestroyed()) return if (!rightViewerInstance || rightViewerInstance.isDestroyed()) return const rightCamera = rightViewerInstance.camera // 直接传递 position 引用(不 clone),获得最佳性能 // Cesium 内部会处理拷贝,且在单向同步场景下安全 leftViewerInstance.camera.setView({ destination: rightCamera.position, orientation: { heading: rightCamera.heading, pitch: rightCamera.pitch, roll: rightCamera.roll } }) } // 只在右侧 Viewer 的 postRender 中注册同步函数 // 确保右侧相机更新完成后再同步到左侧 rightViewerInstance.scene.postRender.addEventListener(handleCameraSync) // 保存移除函数 cameraSyncRemover = () => { rightViewerInstance.scene.postRender.removeEventListener(handleCameraSync) console.log('[useDualMapCompare] 相机同步已移除') } console.log('[useDualMapCompare] 单向相机同步设置完成') } /** * 启用对比模式 * @param {Cesium.Viewer} rightViewerInstance - 右侧Viewer实例(主地图,灾后场景) * @param {Object} options - 配置选项 * @param {boolean} options.skipLeftModelLoad - 是否跳过左侧模型加载 * @param {boolean} options.loadLeftModel - 是否仅加载左侧模型(不重新初始化Viewer) */ const enableCompareMode = async (rightViewerInstance, options = {}) => { const { skipLeftModelLoad = false, loadLeftModel = false } = options if (!rightViewerInstance) { console.error('[useDualMapCompare] 右侧主地图Viewer未初始化') return } // 如果只是加载左侧模型(Viewer已存在) if (loadLeftModel && leftViewer.value) { console.log('[useDualMapCompare] 加载左侧灾前模型...') try { const tileset = await load3DTileset(leftViewer.value, 'before', false) if (tileset) { leftTileset = tileset console.log('[useDualMapCompare] 左侧灾前模型加载完成') } } catch (error) { console.error('[useDualMapCompare] 左侧模型加载失败:', error) } return } console.log('[useDualMapCompare] 启用对比模式...') rightViewer.value = rightViewerInstance // 查找左侧容器(容器已存在于DOM中) const leftContainer = document.getElementById('leftCesiumContainer') if (!leftContainer) { console.error('[useDualMapCompare] 找不到左侧容器元素') return } // 先设置状态,触发CSS动画 isCompareMode.value = true // 等待CSS过渡完成(300ms + 50ms buffer) await new Promise(resolve => setTimeout(resolve, 350)) // 初始化左侧Viewer const leftViewerInstance = initLeftViewer(leftContainer) if (!leftViewerInstance) { console.error('[useDualMapCompare] 左侧Viewer初始化失败') isCompareMode.value = false return } // 立即同步右侧相机的当前位置到左侧 console.log('[useDualMapCompare] 同步初始相机位置...') const rightCamera = rightViewerInstance.camera leftViewerInstance.camera.setView({ destination: rightCamera.position.clone(), orientation: { heading: rightCamera.heading, pitch: rightCamera.pitch, roll: rightCamera.roll } }) // 设置相机同步(单向:右→左) setupCameraSync(rightViewerInstance, leftViewerInstance) // 根据选项决定是否加载左侧模型 if (!skipLeftModelLoad) { // 异步加载灾前模型到左侧(不阻塞对比模式启用) console.log('[useDualMapCompare] 开始异步加载左侧灾前模型...') load3DTileset(leftViewerInstance, 'before', false) .then(tileset => { if (tileset) { leftTileset = tileset console.log('[useDualMapCompare] 左侧灾前模型加载完成') } }) .catch(error => { console.error('[useDualMapCompare] 左侧模型加载失败:', error) }) } else { console.log('[useDualMapCompare] 跳过左侧模型加载(将延迟加载)') } // 右侧保持灾后模型(已加载) // 触发左侧viewer resize setTimeout(() => { if (leftViewerInstance && leftViewerInstance.canvas) { leftViewerInstance.resize() leftViewerInstance.camera.changed.raiseEvent() } // 同时触发右侧viewer resize if (rightViewerInstance && rightViewerInstance.canvas) { rightViewerInstance.resize() } }, 350) console.log('[useDualMapCompare] 对比模式已启用') } /** * 禁用对比模式 */ const disableCompareMode = () => { console.log('[useDualMapCompare] 禁用对比模式...') // 移除相机同步 if (cameraSyncRemover) { cameraSyncRemover() cameraSyncRemover = null } // 销毁左侧Viewer if (leftViewer.value && !leftViewer.value.isDestroyed()) { // 清理左侧tileset if (leftTileset) { leftViewer.value.scene.primitives.remove(leftTileset) leftTileset = null } leftViewer.value.destroy() leftViewer.value = null console.log('[useDualMapCompare] 左侧Viewer已销毁') } // 触发右侧viewer resize恢复全屏 if (rightViewer.value && !rightViewer.value.isDestroyed()) { setTimeout(() => { if (rightViewer.value && rightViewer.value.canvas) { rightViewer.value.resize() } }, 350) } isCompareMode.value = false console.log('[useDualMapCompare] 对比模式已禁用') } /** * 切换对比模式 * @param {boolean} active - true启用,false禁用 * @param {Cesium.Viewer} rightViewerInstance - 右侧Viewer实例(主地图) * @param {Object} options - 配置选项(传递给 enableCompareMode) */ const toggleCompareMode = async (active, rightViewerInstance, options) => { if (active) { await enableCompareMode(rightViewerInstance, options) } else { disableCompareMode() } } // 清理 onUnmounted(() => { disableCompareMode() }) return { leftViewer, rightViewer, isCompareMode, enableCompareMode, disableCompareMode, toggleCompareMode } } export default useDualMapCompare