170 lines
5.7 KiB
TypeScript
170 lines
5.7 KiB
TypeScript
/**
|
|
* ➡️ 경로 : 하수관망도 메인 페이지
|
|
*
|
|
* 📄 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<any | null>(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 (
|
|
<>
|
|
<div className="bg-container">
|
|
<Gis
|
|
treeData={treeData}
|
|
isSearch={true}
|
|
gisMaps={gisMaps}
|
|
isMiniMap={false}
|
|
searchView={SearchModal}
|
|
detailView={FeatureClickEventPopup}
|
|
onFeatureSelect={onFeatureSelect}
|
|
featureData={featureData}
|
|
setFeatureData={setFeatureData}
|
|
isOpen={isOpen}
|
|
setIsOpen={setIsOpen}
|
|
isSearchModalOpen={isSearchModalOpen}
|
|
setIsSearchModalOpen={setIsSearchModalOpen}
|
|
onCheckedLayersChange={handleCheckedLayersChange}
|
|
searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />}
|
|
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]}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Map;
|