import { ref, reactive, onUnmounted, watch, computed } from 'vue'; import { useStatusStore } from '@/stores/useStatusStore'; import { useLoadingResourceStore } from '@/stores/useLoadingResourceStore'; import { useAroundAnalysisConfig } from './useAroundAnalysisConfig'; import type { PointResource, PointResourceCategory, AnalysisButtonConfig, AroundAnalysisState } from '@/types/common/useAroundAnalysisType'; import { CesiumUtilsSingleton } from '@/utils/cesium/CesiumUtils'; import { isCategoryVisible, loadAllPointData, calculateDistance } from '@/utils/aroundAnalysisUtils'; import { ScreenSpaceEventHandler, ScreenSpaceEventType, Cartesian2, Cartographic, Cartesian3, } from 'cesium'; import { useCircleDrawer } from './useCircleDrawer'; import { usePulseEffect } from './usePulseEffect'; import { useMarkerManager } from './useMarkerManager'; /** * 周边分析统一 Hook(合并按钮和搜索逻辑) */ export const useAroundAnalysis = (): AroundAnalysisState => { const statusStore = useStatusStore(); const { resourceConfigs, MIN_FLY_HEIGHT, FLY_HEIGHT_MULTIPLIER, FLY_DURATION } = useAroundAnalysisConfig(); // ==================== 响应式状态 ==================== const selectedButtonIndex = ref(-1); const showAreaDialog = ref(false); const radius = ref(10); const dialogPosition = reactive({ x: 0, y: 0 }); const pulsePoints = ref([]); const showPulsePointList = ref(false); const searchState = ref(''); const canSearch = computed(() => { return resourceConfigs.value.some(config => isCategoryVisible(config.category, config.forcedType)); }); let clickHandler: ScreenSpaceEventHandler | null = null; let currentCenterPosition: Cartesian3 | null = null; // ==================== 组合子 Hook ==================== const { drawCircle, clearCircle } = useCircleDrawer(); const { addPulseEffectToPoints, removePulseEffect } = usePulseEffect(); const { addMarker, removeMarker } = useMarkerManager(); // ==================== 核心功能 ==================== const getPointsInCircle = (centerPosition: Cartesian3, radiusKm: number): PointResource[] => { const cartographic = Cartographic.fromCartesian(centerPosition); const centerLon = cartographic.longitude * (180 / Math.PI); const centerLat = cartographic.latitude * (180 / Math.PI); const allPoints = loadAllPointData(resourceConfigs.value); const radiusMeters = radiusKm * 1000; const filteredPoints = allPoints.filter(point => { if (point.lon === undefined || point.lat === undefined) return false; const distance = calculateDistance(centerLon, centerLat, point.lon, point.lat); return distance <= radiusMeters && isCategoryVisible(point.category as PointResourceCategory, point.originalType); }); // 按坐标去重:相同经纬度的点只保留一个 const coordMap = new Map(); filteredPoints.forEach(point => { const coordKey = `${point.lon},${point.lat}`; if (!coordMap.has(coordKey)) { coordMap.set(coordKey, point); } }); return Array.from(coordMap.values()); }; const refreshPulseEffect = () => { if (!currentCenterPosition) return; removePulseEffect(); const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value); addPulseEffectToPoints(pointsInCircle); pulsePoints.value = pointsInCircle; showPulsePointList.value = true; }; const clearAllAnalysisResources = () => { removeMarker(); clearCircle(); removePulseEffect(); currentCenterPosition = null; pulsePoints.value = []; showPulsePointList.value = false; }; const clearVisualEffectsOnly = () => { clearCircle(); removePulseEffect(); pulsePoints.value = []; showPulsePointList.value = false; }; // ==================== 地图事件 ==================== const registerMapClickHandler = () => { const viewer = CesiumUtilsSingleton.getViewer(); if (!viewer) return; clickHandler = new ScreenSpaceEventHandler(viewer.scene.canvas); clickHandler.setInputAction((clickEvent: { position: Cartesian2 }) => { const cartesian = viewer.camera.pickEllipsoid(clickEvent.position, viewer.scene.globe.ellipsoid); if (cartesian) { currentCenterPosition = cartesian; const cartographic = Cartographic.fromCartesian(cartesian); console.log('点击位置:', { longitude: cartographic.longitude * (180 / Math.PI), latitude: cartographic.latitude * (180 / Math.PI) }); addMarker(cartesian); showAreaDialog.value = true; calculateDialogPosition(clickEvent.position); } }, ScreenSpaceEventType.LEFT_CLICK); }; const removeMapClickHandler = () => { if (clickHandler) { clickHandler.destroy(); clickHandler = null; } }; const calculateDialogPosition = (clickPosition: Cartesian2) => { const { innerWidth: screenWidth, innerHeight: screenHeight } = window; const { DIALOG_WIDTH: dialogWidth, DIALOG_HEIGHT: dialogHeight, DIALOG_PADDING: dialogPadding, DIALOG_OFFSET: dialogOffset } = useAroundAnalysisConfig().getConstants(); let x = clickPosition.x + dialogOffset; let y = clickPosition.y + dialogOffset; if (x + dialogWidth > screenWidth - dialogPadding) { x = clickPosition.x - dialogWidth - dialogOffset; } if (y + dialogHeight > screenHeight - dialogPadding) { y = clickPosition.y - dialogHeight - dialogOffset; } dialogPosition.x = Math.max(dialogPadding, Math.min(x, screenWidth - dialogWidth - dialogPadding)); dialogPosition.y = Math.max(dialogPadding, Math.min(y, screenHeight - dialogHeight - dialogPadding)); }; // ==================== 事件处理 ==================== const handleConfirm = () => { if (!currentCenterPosition) { console.error('中心点位置不存在'); return; } clearVisualEffectsOnly(); drawCircle(currentCenterPosition, radius.value); const pointsInCircle = getPointsInCircle(currentCenterPosition, radius.value); addPulseEffectToPoints(pointsInCircle); pulsePoints.value = pointsInCircle; showPulsePointList.value = true; const cartographic = Cartographic.fromCartesian(currentCenterPosition); const longitude = cartographic.longitude * (180 / Math.PI); const latitude = cartographic.latitude * (180 / Math.PI); const flyHeight = Math.max(radius.value * FLY_HEIGHT_MULTIPLIER, MIN_FLY_HEIGHT); CesiumUtilsSingleton.flyToTarget([longitude, latitude, flyHeight], FLY_DURATION); showAreaDialog.value = false; removeMapClickHandler(); const viewer = CesiumUtilsSingleton.getViewer(); if (viewer?.canvas) { statusStore.cursorStyle = 'default'; viewer.canvas.style.cursor = 'default'; } }; const handleCancel = () => { showAreaDialog.value = false; clearAllAnalysisResources(); }; const handleButtonClick = (index: number, callback: (status: boolean) => void) => { const isActive = selectedButtonIndex.value === index; if (isActive) { selectedButtonIndex.value = -1; callback(false); } else { if (selectedButtonIndex.value !== -1) { clearAllAnalysisResources(); showAreaDialog.value = false; } selectedButtonIndex.value = index; callback(true); } }; const startAreaAnalysisFromSearch = (point: PointResource) => { if (point.lon == null || point.lat == null) return; clearAllAnalysisResources(); currentCenterPosition = Cartesian3.fromDegrees(point.lon, point.lat, 0); selectedButtonIndex.value = 0; const viewer = CesiumUtilsSingleton.getViewer(); if (viewer?.canvas) { statusStore.cursorStyle = 'crosshair'; viewer.canvas.style.cursor = 'crosshair'; } addMarker(currentCenterPosition); showAreaDialog.value = true; const centerX = window.innerWidth / 2; const centerY = window.innerHeight / 2; const { DIALOG_WIDTH: dialogWidth, DIALOG_HEIGHT: dialogHeight, DIALOG_PADDING: dialogPadding, DIALOG_OFFSET: dialogOffset } = useAroundAnalysisConfig().getConstants(); dialogPosition.x = Math.max(dialogPadding, Math.min(centerX + dialogOffset, window.innerWidth - dialogWidth - dialogPadding)); dialogPosition.y = Math.max(dialogPadding, Math.min(centerY + dialogOffset, window.innerHeight - dialogHeight - dialogPadding)); }; // ==================== 搜索功能 ==================== const querySearch = (queryString: string, cb: (results: PointResource[]) => void) => { if (!canSearch.value) { cb([]); return; } const lowerQuery = queryString.toLowerCase(); const allResources = loadAllPointData(resourceConfigs.value); const filteredResults = allResources.filter(item => { const config = resourceConfigs.value.find(c => c.category === item.category); let isVisible = false; if (config) { isVisible = isCategoryVisible(config.category, config.forcedType || item.originalType); } if (!isVisible) return false; if (!queryString) return true; return (item.value || '').toLowerCase().includes(lowerQuery); }); cb(filteredResults); }; const handleSelect = async (item: PointResource) => { if (item.lon == null || item.lat == null) return; await CesiumUtilsSingleton.flyToTarget([item.lon, item.lat, 6000]); startAreaAnalysisFromSearch(item); }; const handleFocus = () => { // 触发数据刷新(如果需要) }; // ==================== 监听器 ==================== watch( () => useLoadingResourceStore().loadingResource, () => { if (currentCenterPosition && showPulsePointList.value) { refreshPulseEffect(); } }, { deep: true } ); const poi = computed(() => statusStore.poiLayers); const map = computed(() => statusStore.mapLayers); const infra = computed(() => statusStore.infrastructureLayers); watch([ () => poi.value.showSchool.show, () => poi.value.showHospital.show, () => poi.value.showDangerSource.show, () => poi.value.showRefugeeShelter.show, () => poi.value.showFireStation.show, () => poi.value.showReservePoint.show, () => poi.value.showSubwayStation.show, () => poi.value.showLandslideHiddenPoint.show, () => poi.value.showDebrisFlowHiddenPoint.show, () => poi.value.showWaterLoggingHiddenPoint.show, () => poi.value.showFlashFloodHiddenPoint.show, () => map.value.riskPointShow.show, () => infra.value.showBridge.show, () => infra.value.showReservoir.show, ], () => { if (currentCenterPosition && showPulsePointList.value) { refreshPulseEffect(); } }); onUnmounted(() => { clearAllAnalysisResources(); removeMapClickHandler(); }); // ==================== 按钮配置 ==================== const analysisButtons: AnalysisButtonConfig[] = [ { name: '标记区域分析', activeName: '取消区域分析', callback: (status: boolean) => { const viewer = CesiumUtilsSingleton.getViewer(); if (!viewer?.canvas) return; statusStore.cursorStyle = status ? 'crosshair' : 'default'; viewer.canvas.style.cursor = status ? 'crosshair' : 'default'; if (status) { registerMapClickHandler(); } else { removeMapClickHandler(); clearAllAnalysisResources(); showAreaDialog.value = false; } }, }, { name: '隐藏行政区划', callback: (status: boolean) => { statusStore.mapLayers.showAdministrativeDivision.show = !status; }, }, ]; return { selectedButtonIndex, showAreaDialog, radius, dialogPosition, analysisButtons, searchState, canSearch, pulsePoints, showPulsePointList, handleButtonClick, handleConfirm, handleCancel, refreshPulseEffect, startAreaAnalysisFromSearch, querySearch, handleSelect, handleFocus, }; };