import * as Cesium from 'cesium' import { ref } from 'vue' import { MOCK_VIDEO_URL } from '../constants' /** * 地图点击事件处理 * * 职责: * 1. 管理地图点击事件监听器 * 2. 处理标记点点击逻辑 * 3. 显示 Tooltip 和详情弹窗 * * @param {Object} options - 配置选项 * @param {Object} options.tooltipComposable - useMapTooltip 返回的对象 * @param {Object} options.icons - 图标资源对象 * @param {Ref} options.rangeCircleEntity - 范围圈实体引用 * @returns {Object} 点击处理 API */ export function useMapClickHandler({ tooltipComposable, icons, rangeCircleEntity }) { const { showTooltip, hideTooltip, enableEntityTracking } = tooltipComposable // 事件处理器引用 let eventHandler = null /** * 设置地图点击事件处理器 * @param {Cesium.Viewer} viewer - Cesium viewer 实例 * @param {Function} registerEventHandlerFn - 注册事件处理器的函数(来自 useCesiumLifecycle) * @param {Function} registerPostRenderFn - 注册 postRender 的函数(来自 useCesiumLifecycle) */ const setupClickHandler = (viewer, registerEventHandlerFn, registerPostRenderFn) => { if (!viewer) { console.warn('[useMapClickHandler] viewer 为空') return } // 创建事件处理器 eventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas) // 注册到生命周期管理 registerEventHandlerFn(eventHandler) // 监听左键点击事件 eventHandler.setInputAction((click) => { handleMapClick(viewer, click, registerPostRenderFn) }, Cesium.ScreenSpaceEventType.LEFT_CLICK) console.log('[useMapClickHandler] 地图点击事件监听器已设置') } /** * 处理地图点击事件 * @param {Cesium.Viewer} viewer - Cesium viewer 实例 * @param {Object} click - 点击事件对象 * @param {Function} registerPostRenderFn - 注册 postRender 的函数 */ const handleMapClick = (viewer, click, registerPostRenderFn) => { // 获取点击位置的实体 const pickedObject = viewer.scene.pick(click.position) if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) { const entity = pickedObject.id // 过滤掉范围圈实体 if (entity === rangeCircleEntity.value) { console.log('[useMapClickHandler] 点击了范围圈,尝试穿透') handleRangeCircleClick(viewer, click, registerPostRenderFn) return } // 检查实体是否有 properties(标记点才有) if (entity.properties) { const type = entity.properties.type?.getValue() handleMarkerClick(viewer, entity, type, click, registerPostRenderFn) } } else { // 点击空白区域,隐藏 Tooltip hideTooltip() } } /** * 处理范围圈点击(穿透到下面的实体) * @param {Cesium.Viewer} viewer - Cesium viewer 实例 * @param {Object} click - 点击事件对象 * @param {Function} registerPostRenderFn - 注册 postRender 的函数 */ const handleRangeCircleClick = (viewer, click, registerPostRenderFn) => { const drillPickedObjects = viewer.scene.drillPick(click.position) for (const pickedObj of drillPickedObjects) { if (Cesium.defined(pickedObj.id) && pickedObj.id !== rangeCircleEntity.value) { const markerEntity = pickedObj.id if (markerEntity.properties) { const type = markerEntity.properties.type?.getValue() handleMarkerClick(viewer, markerEntity, type, click, registerPostRenderFn) return } } } // 没有找到标记点,隐藏 Tooltip hideTooltip() } /** * 处理标记点点击 * @param {Cesium.Viewer} viewer - Cesium viewer 实例 * @param {Cesium.Entity} entity - 被点击的实体 * @param {string} type - 标记类型 * @param {Object} click - 点击事件对象 * @param {Function} registerPostRenderFn - 注册 postRender 的函数 */ const handleMarkerClick = (viewer, entity, type, click, registerPostRenderFn) => { let icon = null // 根据类型选择图标 if (type === 'soldier') { icon = icons.soldierIcon } else if (type === 'device') { icon = icons.deviceIcon } else if (type === 'emergencyBase' || type === 'station') { const stationName = entity.properties.name?.getValue() || '' icon = stationName === '忠县公路交通应急物资储备中心' ? icons.emergencyCenterIcon : icons.emergencyBaseIcon } else if (type === 'reserveCenter' || type === 'presetPoint') { icon = type === 'reserveCenter' ? icons.reserveCenterIcon : icons.emergencyBaseIcon } if (icon) { showMarkerTooltip(viewer, entity, icon, registerPostRenderFn) } } /** * 显示标记点 Tooltip * @param {Cesium.Viewer} viewer - Cesium viewer 实例 * @param {Cesium.Entity} entity - 被点击的实体 * @param {string} icon - 图标路径 * @param {Function} registerPostRenderFn - 注册 postRender 的函数 */ const showMarkerTooltip = (viewer, entity, icon, registerPostRenderFn) => { const properties = entity.properties const type = properties.type?.getValue() // 获取实体的 3D 位置 const position = entity.position?.getValue(Cesium.JulianDate.now()) if (!position) { console.warn('[useMapClickHandler] 无法获取实体位置') return } // 获取地形高度 const cartographic = Cesium.Cartographic.fromCartesian(position) let clampedPosition = position if (viewer.scene.globe) { const height = viewer.scene.globe.getHeight(cartographic) if (Cesium.defined(height)) { cartographic.height = height clampedPosition = Cesium.Cartographic.toCartesian(cartographic) } } // 转换为屏幕坐标 const canvasPosition = viewer.scene.cartesianToCanvasCoordinates(clampedPosition) if (!Cesium.defined(canvasPosition)) { console.warn('[useMapClickHandler] 无法转换坐标到屏幕位置') return } // 计算容器偏移,将 canvas 本地坐标转换为视口坐标 // 在对比模式下,右侧 canvas 相对于视口有 50% 的偏移 const canvas = viewer.scene.canvas const containerRect = canvas.getBoundingClientRect() const viewportX = canvasPosition.x + containerRect.left const viewportY = canvasPosition.y + containerRect.top // 构建 Tooltip 数据 const tooltipData = buildTooltipData(type, properties) // 显示 Tooltip(使用视口坐标) showTooltip({ x: viewportX, y: viewportY, title: tooltipData.title, icon, data: tooltipData.data, entity, // 传递实体用于位置跟踪 }) // 启用实体位置跟踪 enableEntityTracking(viewer, registerPostRenderFn) } /** * 构建 Tooltip 数据 * @param {string} type - 标记类型 * @param {Object} properties - 实体属性 * @returns {Object} Tooltip 数据 */ const buildTooltipData = (type, properties) => { let title = '' const fields = [] const actions = [] let supportVideo = false let videoTitle = '' const videoSrc = MOCK_VIDEO_URL if (type === 'soldier') { // 应急人员 title = '应急人员' fields.push( { label: '姓名', value: properties.name?.getValue() || '-' }, { label: '部门', value: properties.department?.getValue() || '-' }, { label: '位置信息', value: properties.location?.getValue() || '-' }, { label: '预计到达时间', value: properties.estimatedArrival?.getValue() || '-' } ) actions.push({ label: '联动', type: 'link', data: properties, }) } else if (type === 'device') { // 应急装备 title = '应急装备' fields.push( { label: '设备名称', value: properties.name?.getValue() || '-' }, { label: '设备类型', value: properties.deviceType?.getValue() || '-' }, { label: '位置信息', value: properties.location?.getValue() || '-' }, { label: '预计到达时间', value: properties.estimatedArrival?.getValue() || '-' } ) } else if (type === 'emergencyBase') { // 应急基地 title = '应急基地' fields.push( { label: '基地名称', value: properties.name?.getValue() || '-' }, { label: '路线编号', value: properties.routeNumber?.getValue() || '-' }, { label: '隶属单位', value: properties.department?.getValue() || '-' }, { label: '位置信息', value: properties.location?.getValue() || '-' } ) actions.push({ label: '连线', type: 'connect', data: properties, }) } else if (type === 'station') { const stationName = properties.name?.getValue() || '' const distance = properties.distance?.getValue() || 0 if (stationName === '忠县公路交通应急物资储备中心') { title = '应急中心' fields.push( { label: '名称', value: '忠县应急中心' }, { label: '行政等级', value: '国道' }, { label: '隶属单位', value: '交通公路部门' }, { label: '位置信息', value: `目前为止距离现场${distance}公里` } ) supportVideo = true videoTitle = '应急中心' } else { title = '养护站' fields.push( { label: '名称', value: stationName || '-' }, { label: '距离', value: `${distance}公里` } ) } } else if (type === 'reserveCenter') { // 储备中心 title = '储备中心' fields.push( { label: '名称', value: properties.name?.getValue() || '-' }, { label: '位置信息', value: properties.location?.getValue() || '-' } ) supportVideo = true videoTitle = '储备中心' } else if (type === 'presetPoint') { // 预置点 title = '预置点' fields.push( { label: '名称', value: properties.name?.getValue() || '-' }, { label: '位置信息', value: properties.location?.getValue() || '-' } ) supportVideo = true videoTitle = '预置点' } return { title, data: { fields, actions: actions.length > 0 ? actions : undefined, supportVideo, videoTitle, videoSrc, }, } } /** * 销毁事件处理器 */ const destroy = () => { if (eventHandler && !eventHandler.isDestroyed()) { eventHandler.destroy() eventHandler = null } } return { setupClickHandler, destroy, } }