/** * ➡️ 경로 : 하수관망도 메인 페이지 * * 📄 Map Page * 지도 페이지 컴포넌트 * * - OpenLayers 기반 GIS 지도 렌더링 및 상호작용 * - 레이어 트리 데이터 관리 및 동적 레이어 표시/숨김 * - 피처 검색 및 선택 기능 (SearchModal, ProcSearchBar) * - 피처 클릭 이벤트 처리 및 상세 정보 팝업 표시 * - 수심(WaterDepth) 레이어 애니메이션 재생 기능 * - Zustand를 통한 검색 모달 상태 관리 * * 🔗 사용 API * - 레이어 설정 조회: GET /api/v1/map/lyrStngs/{lyrGrp} (sewerage-service) */ "use client"; import Gis from "@common/business_components/ui/map/Gis"; import { getRawJson } from "../../../components/lib/api"; import { useCallback, useEffect, useRef, useState } from "react"; import GisApp from "@common/business_components/ui/map/lib/gis.core"; import SearchModal from "./searchModal/searchModal"; import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup"; import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth"; import { ProcSearchBar } from "@common/business_components/ui/map/searchBar"; import useSearchModalStore from "@/store/useSearchModalStore"; const Map = () => { const { isSearchModalOpen, setIsSearchModalOpen } = useSearchModalStore(); const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const gisMaps = useRef(null); const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부 const [isPlaying, setIsPlaying] = useState(false); useEffect(() => { fetchData(); }, []); const fetchData = async () => { try { const data = await getRawJson("/sewerage-service/api/v1/map/lyrStngs/SN_NETWORK"); setTreeData(data); } catch (error) { console.error('JSON parsing error:', error); // JSON 파싱에 실패한 경우, 원본 텍스트 또는 null 반환 throw new Error('Invalid JSON response from server'); } }; // SearchModal, GisContent 등에서 호출 const onFeatureSelect = (data: any) => { let map = gisMaps.current; if (!map) { console.warn("지도가 초기화되지 않았습니다."); return; } // 항상 같은 select 객체 사용 const select = GisApp.Module.select as any; if (!select) return; // 1. 기존 선택 초기화 select.resetSelectedFeature(); // 2. data가 OpenLayers Feature 인스턴스인지 확인 let foundFeature = null; if ( data && typeof data.getGeometry === "function" && typeof data.setStyle === "function" ) { // 이미 Feature 인스턴스라면 그대로 사용 foundFeature = data; } else { // 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용 const featureId = data.fid || data.ftrIdn || data.id; map .getLayers() .getArray() .forEach((layer: any) => { const source = layer.getSource?.(); if (source?.getFeatureById) { const feature = source.getFeatureById(String(featureId).trim()); if (feature) foundFeature = feature; } }); } // 3. 피처 선택 if (foundFeature) { let geometry = foundFeature.getGeometry(); if (geometry) { let extent = geometry.getExtent(); map.getView().fit(extent, { duration: 300, padding: [50, 50, 50, 50], maxZoom: 16, constrainResolution: true, nearest: true, }); } select.selectFeature(foundFeature, true); } else { console.warn("OpenLayers Feature를 찾을 수 없습니다:", data); } setFeatureData(data); setIsOpen(true); setIsSearchModalOpen(false); }; const facilityTypes = [{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" }]; // WaterDepth 표시 여부를 결정하는 함수 const handleWaterDepthVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => { const waterDepthLayers = checkedNodesData.filter(node => WATER_DEPTH_LAYER_TYPES.includes(node.data?.lyrTyNm) ); const hasWaterDepthLayer = waterDepthLayers.length > 0; setShowWaterDepth(hasWaterDepthLayer); }, []); // 체크된 레이어 변경 처리 함수 const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => { handleWaterDepthVisibility(checkedIds, checkedNodesData); }, [handleWaterDepthVisibility]); return ( <>
} waterDepth={showWaterDepth ? WaterDepth : undefined} isPlaying={isPlaying} setIsPlaying={setIsPlaying as any} targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any} checkInterval={2500} showZoomLevel={true} defaultZoom={7.5} defaultLonLat={[127.75, 36]} />
); }; export default Map;