Merge commit '81afc9f3413a3dd92515566c0e1bf361274e911d' into iipbs/sn/oi

This commit is contained in:
JIeunhuh
2025-04-28 09:34:28 +09:00
9 changed files with 146 additions and 90 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ const Map = () => {
return (
<div className="bg-container">
<Gis treeData={treeData}/>
<Gis treeData={treeData} isSearch={true} />
</div>
);
};
@@ -10,19 +10,18 @@ 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 GisToolBar from './GisToolBar';
import GisSearch from './GisSearch';
import GisTab from './GisTab';
import MapToggleBtn from './MapToggleBtn';
export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null, checkedLayerIds:null});
export const useGisContext = () => useContext(GisContext);
function Gis({ treeData = [] }) {
function Gis({ treeData = [], isSearch = false }) {
const mapRef = useRef();
const overviewMapRef = useRef(null);
const [mapObject, setMapObject] = useState({});
const [tabVisible, setTabVisible] = useState(true);
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
//const [treeData, setTreeData] = useState([]);
const checkedLayerIds = useRef([]);
@@ -126,33 +125,20 @@ function Gis({ treeData = [] }) {
}
}
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
return (
<>
<GisContext.Provider value={{gisCore, gisMap, treeData, checkedLayerIds}}>
<>
<div className="h-[calc(100vh-10rem)] flex relative">
<div className="absolute top-0 left-0 z-40 w-[280px] h-[calc(100vh-10rem)] bg-white border rounded-md shadow-md flex flex-col overflow-hidden">
<Tabs defaultValue="layers" className="flex-1 flex flex-col overflow-hidden">
<TabsList className="flex-none border-b text-[70%]">
<TabsTrigger value="layers" className="flex-1">레이어</TabsTrigger>
<TabsTrigger value="search" className="flex-1">시설검색</TabsTrigger>
</TabsList>
<TabsContent value="layers" className="grow overflow-y-auto p-2 min-h-0">
<GisTree />
</TabsContent>
<TabsContent value="search" className="grow overflow-y-auto p-2 min-h-0">
<GisSearch />
</TabsContent>
</Tabs>
</div>
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} />
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
<GisLonLat />
<GisToolBar />
<MapToggleBtn />
<GisLonLat/>
<GisToolBar/>
<MapToggleBtn/>
<GisContent/>
</div>
</>
</GisContext.Provider>
</>
);
}
@@ -6,7 +6,7 @@ import GisSelect from './GisSelect';
import GisToolBar from './GisToolBar';
import './lib/css/gis.edit.css';
import MapToggleBtn from './MapToggleBtn';
//import GisEdit from './GisEdit';
import GisEdit from './GisEdit';
export default function GisContent({ edit = false }) {
const { gisMap, gisCore } = useGisContext();
@@ -15,14 +15,14 @@ export default function GisContent({ edit = false }) {
const [playShow, setPlayShow] = useState({ show: false });
//셀렉트 버튼 이벤트
const clickFeature = useCallback(feature => {
if (feature) {
const layer = feature.getLayer();
setVisibleComponents(prevState => ({
...prevState,
}));
}
}, []);
// const clickFeature = useCallback(feature => {
// if (feature) {
// const layer = feature.getLayer();
// setVisibleComponents(prevState => ({
// ...prevState,
// }));
// }
// }, []);
const selectRslt = useCallback(feature => {
GisApp.Module.layer.highLightFeature(
@@ -52,9 +52,9 @@ export default function GisContent({ edit = false }) {
<>
{/*<GisTree />*/}
{/* <MapToggleBtn useCesium={useCesium} /> */}
<GisLonLat />
<GisToolBar />
<GisSelect clickFeature={clickFeature} />
{/* { <GisLonLat /> */}
{/* <GisSelect clickFeature={clickFeature} /> */}
<GisSelect />
{/* {edit && <GisEdit />} */}
</>
);
@@ -1,9 +1,9 @@
import GisApp from './lib/gis.core';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { GisContext, setTree } from './Gis';
import Button from '../button/Button';
import { Button } from "@common/components";
import { gAlert, gConfirm } from './lib/util';
import { fclApi } from '../../js/apis/fcl/fclApi';
// import { fclApi } from '../../js/apis/fcl/fclApi';
export default React.memo(function GisEdit() {
const { editLayerItems = [], siteSn, locgovCd, checkedLayerIds } = useContext(GisContext);
@@ -96,10 +96,10 @@ export default React.memo(function GisEdit() {
return;
}
if (!gConfirm('CONFIRM', 'SAVE_QUEST')) return false;
const result = await fclApi.saveEditFeatures(editFeatures);
if (result.code === 200) {
editModule.clearUndoRedo();
}
// const result = await fclApi.saveEditFeatures(editFeatures);
// if (result.code === 200) {
// editModule.clearUndoRedo();
// }
};
useEffect(() => {
@@ -3,7 +3,7 @@ import GisApp from './lib/gis.core';
import GisOlSelect from './lib/gis.select';
import { useContext } from 'react';
import { GisContext } from './Gis';
export default React.memo(function GisSelect({ clickFeature }) {
export default React.memo(function GisSelect() {
const { gisMap } = useContext(GisContext);
const select = new GisOlSelect(gisMap);
GisApp.Module.select = select;
@@ -12,11 +12,11 @@ export default React.memo(function GisSelect({ clickFeature }) {
};
select.getSelectedInfo(function (features) {
clickFeature(features[0]);
//clickFeature(features[0]);
});
return (
<>
<div className="select_control map_control_div ol-unselectable ol-control" style={{ pointerEvents: 'auto', zIndex: '1' }}>
{/* <div className="select_control map_control_div ol-unselectable ol-control" style={{ pointerEvents: 'auto', zIndex: '1' }}>
<h3>선,면,다각형을 이용한 시설물 선택 영역</h3>
<ul>
<li>
@@ -30,7 +30,7 @@ export default React.memo(function GisSelect({ clickFeature }) {
</div>
</li>
</ul>
</div>
</div> */}
</>
);
});
@@ -0,0 +1,67 @@
import React, { useState } from 'react';
import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs';
import GisTree from './GisTree';
import GisSearch from './GisSearch';
import { ChevronLeft, ChevronRight } from 'lucide-react';
function GisTab({ visible, setVisible, isSearch }) {
const [activeTab, setActiveTab] = useState('layers');
return (
<>
<div
className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40
${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`}
>
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden">
<TabsList className="flex-none mt-4 border-none rounded-none">
<TabsTrigger value="layers" className="flex-1 h-10">레이어</TabsTrigger>
{isSearch && (
<TabsTrigger value="search" className="flex-1 h-10">검색</TabsTrigger>
)}
</TabsList>
{/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */}
<div
className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${
activeTab === 'layers' ? 'block' : 'hidden'
}`}
>
<GisTree />
</div>
{/* GisSearch도 동일하게 처리 */}
{isSearch && (
<div
className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${
activeTab === 'search' ? 'block' : 'hidden'
}`}
>
<GisSearch />
</div>
)}
</Tabs>
{/* 접기 버튼 */}
<button
onClick={() => setVisible(false)}
className="absolute top-1/2 right-[-3px] transform -translate-y-1/2 z-50 bg-white border border-l border-gray-300 rounded-l-none rounded-r px-1 py-2 shadow-sm"
>
<ChevronLeft size={16} />
</button>
</div>
{/* 열기 버튼 */}
{!visible && (
<button
onClick={() => setVisible(true)}
className="absolute top-1/2 left-[-3px] transform -translate-y-1/2 z-50 bg-white border border-r border-gray-300 rounded-r px-1 py-2 shadow-sm"
>
<ChevronRight size={16} />
</button>
)}
</>
);
}
export default React.memo(GisTab);
@@ -23,7 +23,6 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
setNodes(nodeData);
gisCore.setAllLayersOnMap(false);
}
console.log(treeData)
}, [treeData]);
useEffect(() => {
@@ -37,7 +36,6 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
setExpanded(defaultExpanded);
gisCore.setLayerOnMap(defaultChecked, true);
}
console.log(nodes)
}, [nodes]);
const getDefaultCheckedNodes = (nodes, values = []) => {
@@ -113,27 +111,11 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
parent: item.upperLyrSn,
droppable: !!item.children,
data: item,
icon: src ? <img src={src} alt={item.lyrNm} className="w-4 h-4" /> : '',
icon: src ? <img src={src} alt={item.lyrNm} /> : '',
};
});
};
const findUpLyrId = (nodes, targetLyrId) => {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.data && node.data.lyrSn === targetLyrId) {
return node.data.upperLyrSn;
}
if (node.children) {
const upLyrId = findUpLyrId(node.children, targetLyrId);
if (upLyrId) {
return upLyrId;
}
}
}
return null;
};
const makeLegendSrc = layerinfo => {
if(!layerinfo.gobjTy) return null;
@@ -208,18 +190,13 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
style={{ marginInlineStart: depth * 16 + (!node.droppable ? 16 : 0) }}
>
{node.droppable && (
<button onClick={onToggle} className="text-sm w-4 focus:outline-none" title={isOpen ? '닫기' : '열기'}>
{isOpen ? '▾' : '▸'}
</button>
<i onClick={onToggle} className={`diveicon di-arrow-forward mr-2 text-base text-label ${isOpen ? 'rotate-90' : ''}`}
title={isOpen ? '닫기' : '열기'}>
</i>
)}
<input
type="checkbox"
checked={checked.includes(node.id)}
onChange={() => handleCheck(node.id)}
className="cursor-pointer"
/>
{node.icon && <span className="inline-block w-4 h-4 mr-1">{node.icon}</span>}
<span className="text-sm truncate">{node.text}</span>
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer"/>
{node.icon && <span>{node.icon}</span>}
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
</div>
)}
sort={(a, b) => {
@@ -102,7 +102,7 @@
.tool-bar {
position: absolute;
top: 140px;
top: 70px;
right: 20px;
z-index: 2;
}
@@ -244,7 +244,7 @@
background: url(../images/toolbar/btn_cont_zoomin_on.png) no-repeat center #67aee3;
}
.btn_control_zoomout {
background: url(../images/toolbar/btn_cont_zoomout.png) no-repeat center !important;
background: url(../images/toolbar/btn_cont_zoomout.png) no-repeat center;
}
.btn_control_zoomout:hover {
background: url(../images/toolbar/btn_cont_zoomout_on.png) no-repeat center #67aee3;
@@ -346,7 +346,7 @@
.select_control {
position: absolute;
top: 80px;
left: 250px;
left: 450px;
}
.select_control h3 {
overflow: hidden;
@@ -759,8 +759,6 @@
.puGrayTb {
position: relative;
width: 98.3%;
cellpadding: 0;
cellspcing: 0;
margin-top: 5px;
table-layout: fixed;
font-family: Verdana;
@@ -48,19 +48,31 @@ GisOlSelect.prototype = {
this.removeInteration();
}
let baseStyle = new Style({
stroke: new Stroke({
color: 'rgba(135, 116, 243, 0.4)',
width: 10,
image : new CircleStyle({
fill : new Fill({
color : 'rgba(135, 116, 243, 0.4)',
}),
radius : 30
}),
stroke : new Stroke({
color : 'rgba(135, 116, 243, 0.4)',
width : 10,
})
});
this.selectInteraction = new Select({
style: function (feature) {
style: function (feature, resolution) {
let styles = [];
let layer = feature.getLayer();
let style = GisApp.Module.style.getLayerStyle(layer, null);
if (style) {
styles.push(style);
let styleFn = GisApp.Module.style.getLayerStyleFunction(layer, null);
if (styleFn) {
const style = styleFn(feature, resolution);
if (Array.isArray(style)) {
styles.push(...style);
} else {
styles.push(style);
}
}
styles.push(baseStyle);
return styles;
@@ -72,9 +84,25 @@ GisOlSelect.prototype = {
condition: click,
hitTolerance: me.defaults.hitTolerance,
});
map.addInteraction(this.selectInteraction);
map.addInteraction(this.selectInteraction);
this.selectedFeatures = this.selectInteraction.getFeatures();
// 선택된 피처 중심으로 지도 이동
this.selectInteraction.on('select', function (e) {
const feature = e.selected[0];
if (feature) {
const geometry = feature.getGeometry();
if (geometry) {
const extent = geometry.getExtent();
map.getView().fit(extent, {
duration: 300,
padding: [50, 50, 50, 50],
maxZoom: 16
});
}
}
});
},
/**
* SELECT 피처 초기화