317 lines
10 KiB
JavaScript
Raw Normal View History

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,
}
}