You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
8.7 KiB
297 lines
8.7 KiB
/**
|
|
* 地图事件处理相关的 composable
|
|
*/
|
|
import type { Map } from 'ol'
|
|
import type Overlay from 'ol/Overlay'
|
|
import type FeatureLike from 'ol/Feature'
|
|
import { MarkerData, FenceData } from '../types/map.types'
|
|
|
|
import dayjs from 'dayjs'
|
|
import { fromLonLat } from 'ol/proj'
|
|
import { TrajectoryService } from '../services/trajectory.service'
|
|
import { PopupService } from '../services/popup.service'
|
|
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
|
interface PopupContentGenerator {
|
|
handleTrajectoryPoint: (feature: FeatureLike) => string
|
|
handleTrajectoryLine: (feature: FeatureLike) => string
|
|
handleFence: (feature: FeatureLike) => string
|
|
handleMarker: (feature: FeatureLike) => string
|
|
}
|
|
// 创建弹窗内容生成器
|
|
function createPopupContentGenerator(
|
|
trajectoryService: TrajectoryService,
|
|
popupService: PopupService
|
|
): PopupContentGenerator {
|
|
return {
|
|
handleTrajectoryPoint: (feature: FeatureLike): string => {
|
|
const timeText = feature.get('timeText') || ''
|
|
const trajectoryId = feature.get('trajectoryId') || ''
|
|
const timestamp = feature.get('timestamp')
|
|
const deviceName =
|
|
trajectoryService?.getTrajectoryData().find((t) => t.deviceId === trajectoryId)?.name ||
|
|
trajectoryId
|
|
|
|
return `
|
|
<div style="font-size: 12px; color: #333;">
|
|
<div style="font-weight: bold; margin-bottom: 4px;">${deviceName}</div>
|
|
<div>时间: ${timeText}</div>
|
|
<div style="font-size: 10px; color: #666;">
|
|
${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}
|
|
</div>
|
|
</div>
|
|
`
|
|
},
|
|
|
|
handleTrajectoryLine: (feature: FeatureLike): string => {
|
|
const deviceId = feature.get('deviceId') || ''
|
|
const deviceName =
|
|
trajectoryService?.getTrajectoryData().find((t) => t.deviceId === deviceId)?.name ||
|
|
deviceId
|
|
|
|
return `
|
|
<div style="font-size: 12px; color: #333;">
|
|
<div style="font-weight: bold;">${deviceName} - 轨迹路径</div>
|
|
</div>
|
|
`
|
|
},
|
|
|
|
handleFence: (feature: FeatureLike): string => {
|
|
const fenceData: FenceData = feature.get('fenceData')
|
|
const statusText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_STATUS, fenceData.status)
|
|
const typeText = getDictLabel(DICT_TYPE.HAND_DETECTOR_FENCE_TYPE, fenceData.type)
|
|
|
|
return `
|
|
<div style="font-size: 12px; color: #333;">
|
|
<div style="font-weight: bold; margin-bottom: 4px;">${fenceData.name}</div>
|
|
<div>状态: ${statusText}</div>
|
|
<div>类型: ${typeText}</div>
|
|
<div style="font-size: 10px; color: #666; margin-top: 2px;">
|
|
${fenceData.remark || '无备注'}
|
|
</div>
|
|
</div>
|
|
`
|
|
},
|
|
|
|
handleMarker: (feature: FeatureLike): string => {
|
|
return popupService?.handlePopupContent(feature) || ''
|
|
}
|
|
}
|
|
}
|
|
|
|
export const useMapEvents = () => {
|
|
/**
|
|
* 设置地图事件监听器
|
|
*/
|
|
const setupMapEventListeners = (
|
|
map: Map,
|
|
popupOverlay: Overlay | null,
|
|
trajectoryService: TrajectoryService | null,
|
|
popupService: PopupService | null,
|
|
opts?: {
|
|
isDrawing?: () => boolean
|
|
onMarkerClick?: (markerData: MarkerData) => void
|
|
onClusterClick?: (features: FeatureLike[]) => void
|
|
onZoomEnd?: (zoom: number) => void
|
|
}
|
|
) => {
|
|
if (!trajectoryService || !popupService) {
|
|
return
|
|
}
|
|
|
|
const popupGenerator = createPopupContentGenerator(trajectoryService, popupService)
|
|
|
|
// 鼠标悬停事件
|
|
const handlePointerMove = (event: any) => {
|
|
// 绘制围栏时屏蔽 hover 弹窗
|
|
if (opts?.isDrawing && opts.isDrawing()) {
|
|
hidePopup(popupOverlay)
|
|
return
|
|
}
|
|
//
|
|
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: FeatureLike) => feature)
|
|
|
|
if (feature) {
|
|
const isPopupShown = showPopup(event, feature, popupOverlay, popupGenerator)
|
|
if (isPopupShown) {
|
|
map.getTargetElement().style.cursor = 'pointer'
|
|
} else {
|
|
hidePopup(popupOverlay)
|
|
}
|
|
} else {
|
|
map.getTargetElement().style.cursor = ''
|
|
hidePopup(popupOverlay)
|
|
}
|
|
}
|
|
|
|
// 点击事件
|
|
const handleClick = (event: any) => {
|
|
// 绘制围栏时屏蔽点击处理
|
|
if (opts?.isDrawing && opts.isDrawing()) {
|
|
return
|
|
}
|
|
const feature = map.forEachFeatureAtPixel(event.pixel, (feature: FeatureLike) => feature)
|
|
if (feature) {
|
|
handleFeatureClick(feature, map, opts)
|
|
}
|
|
}
|
|
|
|
// 地图移动结束事件(包括放缩)
|
|
const handleMoveEnd = () => {
|
|
// console.log('handleMoveEnd');
|
|
|
|
// OpenLayers的Cluster会自动重新计算聚合,只需要刷新样式
|
|
// if (opts?.markerLayer) {
|
|
// opts.markerLayer.changed()
|
|
// }
|
|
if (opts?.onZoomEnd) {
|
|
const zoom = map.getView().getZoom() || 0
|
|
opts.onZoomEnd(zoom)
|
|
}
|
|
}
|
|
|
|
map.on('pointermove', handlePointerMove)
|
|
map.on('click', handleClick)
|
|
map.on('moveend', handleMoveEnd)
|
|
|
|
return {
|
|
destroy: () => {
|
|
map.un('pointermove', handlePointerMove)
|
|
map.un('click', handleClick)
|
|
map.un('moveend', handleMoveEnd)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 显示弹窗
|
|
*/
|
|
const showPopup = (
|
|
event: any,
|
|
feature: FeatureLike,
|
|
popupOverlay: Overlay | null,
|
|
popupGenerator: PopupContentGenerator
|
|
): Boolean => {
|
|
if (!popupOverlay) return false
|
|
|
|
const popupElement = popupOverlay.getElement()
|
|
if (!popupElement) return false
|
|
|
|
const featureType = feature.get('type')
|
|
let popupContent = ''
|
|
|
|
switch (featureType) {
|
|
case 'trajectory-point':
|
|
popupContent = popupGenerator.handleTrajectoryPoint(feature)
|
|
break
|
|
case 'trajectory':
|
|
popupContent = popupGenerator.handleTrajectoryLine(feature)
|
|
break
|
|
case 'fence':
|
|
case 'fence-label':
|
|
// popupContent = popupGenerator.handleFence(feature)
|
|
break
|
|
default:
|
|
popupContent = popupGenerator.handleMarker(feature)
|
|
break
|
|
}
|
|
if (!popupContent) return false
|
|
// if (!popupContent) return
|
|
popupElement.innerHTML = popupContent
|
|
popupOverlay.setPosition(event.coordinate)
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 隐藏弹窗
|
|
*/
|
|
const hidePopup = (popupOverlay: any) => {
|
|
if (popupOverlay) {
|
|
popupOverlay.setPosition(undefined)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理特征点击事件
|
|
*/
|
|
const handleFeatureClick = (
|
|
feature: FeatureLike,
|
|
map: Map,
|
|
opts?: { onMarkerClick?: (markerData: any) => void }
|
|
) => {
|
|
const featureType = feature.get('type')
|
|
|
|
// 处理围栏点击
|
|
if (featureType === 'fence' || featureType === 'fence-label') {
|
|
const fenceData = feature.get('fenceData')
|
|
if (fenceData) {
|
|
console.log('围栏点击:', fenceData)
|
|
// 可以在这里添加围栏点击的自定义处理逻辑
|
|
}
|
|
return
|
|
}
|
|
|
|
// 处理标记点击
|
|
handleMarkerClick(feature, map, opts)
|
|
}
|
|
|
|
/**
|
|
* 处理标记点击事件
|
|
*/
|
|
const handleMarkerClick = (
|
|
feature: FeatureLike,
|
|
map: Map,
|
|
opts?: { onMarkerClick?: (markerData: any) => void; onClusterClick?: (features: FeatureLike[]) => void }
|
|
) => {
|
|
const markerData = feature.get('markerData')
|
|
const features = feature.get('features')
|
|
|
|
if (features && features.length > 1) {
|
|
// 处理聚合标记点击
|
|
// handleClusterClick(features, map)
|
|
|
|
opts?.onClusterClick?.(features)
|
|
} else if (features && features.length === 1) {
|
|
// 处理聚合中的单个标记点击
|
|
const singleMarkerData = features[0].get('markerData')
|
|
if (singleMarkerData) {
|
|
animateToCoordinate(singleMarkerData.coordinates, map, 15)
|
|
opts?.onMarkerClick?.(singleMarkerData)
|
|
}
|
|
} else if (markerData) {
|
|
// 处理非聚合的单个标记点击
|
|
animateToCoordinate(markerData.coordinates, map, 17)
|
|
opts?.onMarkerClick?.(markerData)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理聚合标记点击
|
|
*/
|
|
// const handleClusterClick = (features: FeatureLike[], map: Map) => {
|
|
// // 计算聚合标记的中心点
|
|
// const coordinates = features.map((f: FeatureLike) => f.get('markerData').coordinates)
|
|
// const centerLon =
|
|
// coordinates.reduce((sum: number, coord: any) => sum + coord[0], 0) / coordinates.length
|
|
// const centerLat =
|
|
// coordinates.reduce((sum: number, coord: any) => sum + coord[1], 0) / coordinates.length
|
|
|
|
// animateToCoordinate([centerLon, centerLat], map, 12)
|
|
// }
|
|
|
|
/**
|
|
* 动画移动到指定坐标
|
|
*/
|
|
const animateToCoordinate = (coordinates: [number, number], map: Map, zoom: number) => {
|
|
const view = map.getView()
|
|
|
|
view.animate({
|
|
center: fromLonLat(coordinates),
|
|
zoom: Math.max(view.getZoom() || 10, zoom),
|
|
duration: 1000
|
|
})
|
|
}
|
|
|
|
return {
|
|
setupMapEventListeners
|
|
}
|
|
}
|
|
|