gis 공통

This commit is contained in:
yschoi
2025-06-05 20:15:59 +09:00
parent 2919d7d637
commit 213ac7b8cf
3 changed files with 439 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
"use client";
import Gis, { useGisContext } from "@common/business_components/ui/map/Gis";
import { mapService } from "@service";
import { useCallback, useEffect, useRef, useState } from "react";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import SearchModal from "./SearchModal";
import FeatureClickEventPopup from "./featureClickEventPopup";
const Map = () => {
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태
const gisMaps = useRef(null);
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
const fetchData = async () => {
try {
const response = await mapService.getLyrStngs();
setTreeData(response.data); // API 응답 저장
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
};
// 컴포넌트 마운트 시 1회 실행
useEffect(() => {
fetchData();
}, []);
// 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);
};
return (
<div className="bg-container">
<Gis
treeData={treeData}
isSearch={true}
gisMaps={gisMaps}
isMiniMap={true}
searchView={SearchModal}
detailView={FeatureClickEventPopup}
onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
featureData={featureData}
setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
/>
</div>
);
};
export default Map;
@@ -0,0 +1,246 @@
import './lib/css/gis.common.css';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react';
import OlMap from 'ol/Map';
import View from 'ol/View';
import { defaults as defaultControls, OverviewMap } from 'ol/control';
import { defaults } from 'ol/interaction';
import * as olProj from 'ol/proj';
import proj4 from 'proj4';
import { register } from 'ol/proj/proj4';
import GisApp from './lib/gis.core';
import GisContent from './GisContent';
import GisTree from './GisTree';
import GisLonLat from './GisLonLat';
import GisSelect from './GisSelect';
import GisToolBar from './GisToolBar';
import GisMapToggle from './GisMapToggle';
import { Search } from 'lucide-react';
export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null });
export const useGisContext = () => useContext(GisContext);
function Gis({ isOpen , gisMaps, isSearchModalOpen, treeData = [], isSearch = false, isMiniMap = false, setIsOpen, setFeatureData, featureData, searchView : SearchView, detailView: DetailView, onFeatureSelect, setIsSearchModalOpen }) {
const mapRef = useRef();
const [mapObject, setMapObject] = useState({});
const [tabVisible, setTabVisible] = useState(true);
// const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
// const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터
// const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
const mapInstanceRef = useRef(null);
const checkedLayerIds = useRef([]);
let gisMap = null;
const closePopup = () => {
setIsOpen(false); // 팝업 닫기
setFeatureData(null)
};
// SearchModal, GisContent 등에서 호출
const handleFeatureSelect = useCallback((data) => {
gisMap = mapInstanceRef.current;
if (!gisMap) {
console.warn('지도가 초기화되지 않았습니다.');
return;
}
// 항상 같은 select 객체 사용
const select = GisApp.Module.select;
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;
gisMap.getLayers().getArray().forEach(layer => {
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();
gisMap.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);
}, [mapInstanceRef]);
useEffect(() => {
if (!mapRef.current) return;
if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
// EPSG:5179 등록
proj4.defs([
[
'EPSG:5179',
'+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
],
]);
register(proj4);
//맵 생성
const map = new OlMap({
layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']],
renderer: 'webgl',
target: mapRef.current,
interactions: defaults({ doubleClickZoom: false }),
controls: defaultControls({
zoom: false,
rotate: false,
attributionOptions: {
collapsible: false,
},
}).extend([]),
view: new View({
projection: GisApp.TO_PROJ,
zoom: gisCore.defaults.zoom,
center: olProj.transform(
[126.6757, 37.5585],
GisApp.FROM_PROJ,
GisApp.TO_PROJ
),
minZoom: gisCore.defaults.minZoom,
maxZoom: gisCore.defaults.maxZoom,
}),
});
gisMaps.current = map
//미니맵
if(isMiniMap){
const overviewMap = new OverviewMap({
layers: [GisApp.Module.core.baseMiniMapLayers['base']],
view: new View({
projection: GisApp.TO_PROJ,
minZoom: 9,
}),
collapsed: false,
collapseLabel: '\u00BB',
label: '\u00AB',
});
map.addControl(overviewMap);
}
gisCore.__proto__.map = map;
gisCore.scaleControl();
gisCore.changeBaseLayer('base');
setMapObject({ map: map });
mapInstanceRef.current = map; // 생성 완료 플래그 설정
}, []);
useEffect(() => {
if (!treeData || treeData.length === 0) return;
gisCore.deleteAllOverlay();
gisCore.deleteAllLayer();
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn');
GisApp.layerCode = layerCode;
//setTreeData(data);
const layerItems = getLayerItems(layerCode);
//setEditLayerItems(prev => [...layerItems])
}, [treeData]);
const getLayerItems = (layerCode) => {
const layerItems = Object.entries(layerCode)
.filter(([key, obj], idx) => obj?.updtPsblYn === 'Y')
.map(([key, obj]) => ({
...obj,
name: obj.lyrNm,
label: obj.lyrNm,
value: obj.lyrSn + '',
}))
.sort((a, b) => {
if (a.sortOrdr > b.sortOrdr) return 1;
if (a.sortOrdr < b.sortOrdr) return -1;
return 0;
});
return layerItems;
};
if (mapObject.map) {
try {
gisMap = mapObject.map;
} catch (e) {
console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
}
}
return (
<>
<GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}>
<div className="h-[calc(100vh-10rem)] flex relative">
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
<GisTree visible={tabVisible} setVisible={setTabVisible} />
{/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */}
{/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
{isOpen && featureData && !isSearchModalOpen && (
<DetailView
featureData={featureData}
onClose={closePopup} // 닫기 콜백 전달
/>
)}
<SearchView
open={isSearchModalOpen}
onOpenChange={setIsSearchModalOpen}
onFeatureSelect={onFeatureSelect}
/>
<GisLonLat/>
<GisToolBar/>
<GisMapToggle/>
<GisSelect onFeatureSelect={onFeatureSelect} />
</div>
{isSearch && (
<div className="absolute right-8 top-[305px] z-20">
<button
type="button"
onClick={() => setIsSearchModalOpen(true)}
className="overflow-hidden w-[40px] h-[40px] bg-white
border border-[#9aa7b8]
rounded
shadow-[0_0_5px_#999]
flex items-center justify-center
p-0
"
>
<Search size={24} className="text-gray-700" />
</button>
</div>
)}
</GisContext.Provider>
</>
);
}
export default React.memo(Gis);
@@ -0,0 +1,79 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import GisApp from './lib/gis.core';
import { useGisContext } from './Gis';
import GisLonLat from './GisLonLat';
import GisSelect from './GisSelect';
import GisToolBar from './GisToolBar';
import './lib/css/gis.edit.css';
import GisMapToggle from './GisMapToggle';
import { Search } from 'lucide-react';
export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) {
const { gisMap, gisCore } = useGisContext();
const [visibleComponents, setVisibleComponents] = useState({});
const addressResultLayer = useRef(null);
const [playShow, setPlayShow] = useState({ show: false });
//셀렉트 버튼 이벤트
// const clickFeature = useCallback(feature => {
// if (feature) {
// const layer = feature.getLayer();
// setVisibleComponents(prevState => ({
// ...prevState,
// }));
// }
// }, []);
const selectRslt = useCallback(feature => {
GisApp.Module.layer.highLightFeature(
[feature],
{
id: GisApp.hlLayerId,
zIndex: 10,
opacity: 1,
selectable: true,
},
{ width: 30 }
);
clickFeature(feature);
}, []);
useEffect(() => {
if (gisMap) {
setVisibleComponents(prevState => ({
...prevState,
}));
}
}, [gisMap]);
if (!gisMap) return null;
return (
<>
{/*<GisTree />*/}
{/* <GisMapToggle />
<GisLonLat />
<GisToolBar /> */}
{/* <GisSelect onFeatureSelect={onFeatureSelect} /> */}
{/* 검색 모달 */}
{isSearch && (
<div className="absolute right-8 top-[254px] z-20">
<button
type="button"
onClick={() => setIsSearchModalOpen(true)}
className="overflow-hidden w-[40px] h-[40px] bg-white
border border-[#9aa7b8]
rounded
shadow-[0_0_5px_#999]
flex items-center justify-center
p-0
"
>
<Search size={24} className="text-gray-700" />
</button>
</div>
)}
</>
);
}