sn: 지도 component 수정 반영
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -49,12 +49,14 @@ function Gis({
|
||||
searchView: SearchView,
|
||||
detailView: DetailView,
|
||||
onFeatureSelect,
|
||||
canSelectFeature,
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar,
|
||||
waterDepth: WaterDepth = null,
|
||||
layerSettingView: LayerSettingView = null,
|
||||
inlineLayerSettingView,
|
||||
treeSectionClassName = "",
|
||||
layerIconVisible,
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => { },
|
||||
@@ -73,6 +75,7 @@ function Gis({
|
||||
zoomGuide,
|
||||
showFloatingSearchButton = true,
|
||||
treeTogglePosition = "center",
|
||||
detachedTreeToggleButton = false,
|
||||
showZoomScaleInfo = false,
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
@@ -111,7 +114,7 @@ function Gis({
|
||||
(data) => {
|
||||
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
|
||||
},
|
||||
[mapInstanceRef],
|
||||
[onFeatureSelect],
|
||||
);
|
||||
|
||||
// 지도 생성 및 초기화
|
||||
@@ -315,7 +318,9 @@ function Gis({
|
||||
onProgressChange={handleProgressChange}
|
||||
layerSettingView={LayerSettingView}
|
||||
inlineLayerSettingView={inlineLayerSettingView}
|
||||
treeSectionClassName={treeSectionClassName}
|
||||
toggleButtonPosition={treeTogglePosition}
|
||||
detachedToggleButton={detachedTreeToggleButton}
|
||||
/>
|
||||
|
||||
{typeof WaterDepth === "function" && (
|
||||
@@ -354,7 +359,7 @@ function Gis({
|
||||
<GisZoomLevel zoomGuide={zoomGuide} showZoomScaleInfo={showZoomScaleInfo} />
|
||||
)}
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} canSelectFeature={canSelectFeature} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import { GisContext } from "./Gis";
|
||||
import GisApp from "./lib/gis.core";
|
||||
import GisOlSelect from "./lib/gis.select";
|
||||
|
||||
export default React.memo(function GisSelect({ onFeatureSelect }) {
|
||||
export default React.memo(function GisSelect({ onFeatureSelect, canSelectFeature }) {
|
||||
const { gisMap } = useContext(GisContext);
|
||||
const onFeatureSelectRef = useRef(onFeatureSelect);
|
||||
const canSelectFeatureRef = useRef(canSelectFeature);
|
||||
|
||||
useEffect(() => {
|
||||
onFeatureSelectRef.current = onFeatureSelect;
|
||||
}, [onFeatureSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
canSelectFeatureRef.current = canSelectFeature;
|
||||
}, [canSelectFeature]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함
|
||||
|
||||
// 이미 select가 있으면 재생성하지 않음
|
||||
const select = new GisOlSelect(gisMap);
|
||||
GisApp.Module.select = select;
|
||||
|
||||
// 피처 선택 시 콜백 등록
|
||||
select.getSelectedInfo(function (features) {
|
||||
if (features?.[0]) {
|
||||
onFeatureSelect(features[0]);
|
||||
const isSelectable = canSelectFeatureRef.current?.(features[0]);
|
||||
if (isSelectable === false) {
|
||||
select.resetSelectedFeature?.();
|
||||
return;
|
||||
}
|
||||
onFeatureSelectRef.current?.(features[0]);
|
||||
}
|
||||
});
|
||||
}, [gisMap, onFeatureSelect]);
|
||||
|
||||
return () => {
|
||||
select.removeInteration?.();
|
||||
};
|
||||
}, [gisMap]);
|
||||
|
||||
// const select = new GisOlSelect(gisMap);
|
||||
// GisApp.Module.select = select;
|
||||
|
||||
@@ -29,6 +29,7 @@ import GisApp from "./lib/gis.core";
|
||||
* - onProgressChange(total, current): 애니메이션 진행상태 콜백
|
||||
* - layerSettingView: (선택) 좌측 레이어 트리 하단에 표시할 설정 영역
|
||||
* - layerIconVisible: (선택) 레이어 행 우측 액션 아이콘 노출 여부를 결정하는 함수
|
||||
* - detachedToggleButton: (선택) 접기/펼치기 버튼을 패널 밖 고정 핸들로 노출할지 여부
|
||||
*/
|
||||
export default React.memo(function GisTree({
|
||||
checkedNodes = [],
|
||||
@@ -46,7 +47,10 @@ export default React.memo(function GisTree({
|
||||
layerSettingView: LayerSettingView = null,
|
||||
layerIconVisible,
|
||||
inlineLayerSettingView,
|
||||
treeSectionClassName = "",
|
||||
toggleButtonPosition = "center",
|
||||
showLayerPanelTitle = true,
|
||||
detachedToggleButton = false,
|
||||
}) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
|
||||
@@ -442,10 +446,11 @@ export default React.memo(function GisTree({
|
||||
* 레이어 정보로부터 범례 이미지 경로 또는 동적 생성 이미지를 반환합니다.
|
||||
*/
|
||||
const makeLegendSrc = (layerinfo) => {
|
||||
if (!layerinfo.gobjTy) return null;
|
||||
if (layerinfo.iconNm) {
|
||||
return GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm;
|
||||
} else {
|
||||
}
|
||||
if (!layerinfo.gobjTy) return null;
|
||||
else {
|
||||
return makeLegendImage(layerinfo).src;
|
||||
}
|
||||
};
|
||||
@@ -512,7 +517,10 @@ export default React.memo(function GisTree({
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
const toggleButtonPositionClass =
|
||||
toggleButtonPosition === "top" ? "top-[5.5rem]" : "top-1/2 -translate-y-1/2";
|
||||
toggleButtonPosition === "top" ? "top-4" : "top-1/2 -translate-y-1/2";
|
||||
const thinScrollbarClass =
|
||||
"[scrollbar-width:thin] [&::-webkit-scrollbar]:w-[4px]";
|
||||
const treeSectionClassNames = [treeSectionClassName, thinScrollbarClass].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -524,13 +532,18 @@ export default React.memo(function GisTree({
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`grow overflow-y-auto border-none p-2 pt-0 shadow-none ${activeTab === "layers" ? "block" : "hidden"
|
||||
className={`grow overflow-y-auto border-none p-2 ${showLayerPanelTitle ? "pt-0" : "pt-2"} shadow-none ${activeTab === "layers" ? "block" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<h3 className="gis-layer-panel-title mx-2 my-4 text-3xl font-bold text-gray-600 text-center">시설물</h3>
|
||||
<hr className="mb-4" />
|
||||
{showLayerPanelTitle && (
|
||||
<>
|
||||
<h3 className="gis-layer-panel-title mx-2 my-4 text-3xl font-bold text-gray-600 text-center">시설물</h3>
|
||||
<hr className="mb-4" />
|
||||
</>
|
||||
)}
|
||||
{/* DnD Tree */}
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<div className={treeSectionClassNames || undefined}>
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={nodes}
|
||||
rootId={null}
|
||||
@@ -542,6 +555,13 @@ export default React.memo(function GisTree({
|
||||
(child) =>
|
||||
child.parent === node.id && child.data.exprsYn === "Y"
|
||||
);
|
||||
const nodeLabel = String(node?.data?.lyrNm ?? "").trim();
|
||||
const isSinkholeRootTreeNode =
|
||||
String(node.id) === "SN_SINKHOLE" ||
|
||||
nodeLabel === "지반침하정보";
|
||||
const isFloodHistoryRootTreeNode = nodeLabel === "침수흔적도";
|
||||
const hideTreeLegendIcon =
|
||||
isSinkholeRootTreeNode || isFloodHistoryRootTreeNode;
|
||||
const showLayerActionButton = layerIconVisible?.(node);
|
||||
const showLayerActionIcon = depth > 0 && imageVisible;
|
||||
|
||||
@@ -550,9 +570,10 @@ export default React.memo(function GisTree({
|
||||
return (
|
||||
<div style={{ display: isVisible ? "block" : "none" }}>
|
||||
<div
|
||||
className="gis-layer-row flex items-center gap-2 rounded px-2 py-1 hover:bg-gray-100"
|
||||
className="gis-layer-row flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-gray-100"
|
||||
style={{
|
||||
marginInlineStart: depth * 16,
|
||||
width: `calc(100% - ${depth * 16}px)`,
|
||||
}}
|
||||
>
|
||||
{hasVisibleChildren ? (
|
||||
@@ -563,7 +584,7 @@ export default React.memo(function GisTree({
|
||||
title={isOpen ? "닫기" : "열기"}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ display: "inline-block", width: 16 }} />
|
||||
<span className="shrink-0" style={{ display: "inline-block", width: 16 }} />
|
||||
)}
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -571,14 +592,17 @@ export default React.memo(function GisTree({
|
||||
onChange={() => handleCheck(node.id)}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">
|
||||
{!hideTreeLegendIcon && node.icon && <span>{node.icon}</span>}
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate whitespace-nowrap text-[1.3rem] font-medium text-label"
|
||||
title={String(node.text ?? "")}
|
||||
>
|
||||
{node.text}
|
||||
</span>
|
||||
{showLayerActionButton && (
|
||||
<button
|
||||
type="button"
|
||||
className="gis-layer-action-button ml-1 shrink-0 rounded-sm border border-[#9cc6f0] bg-white px-2 py-[1px] text-[1.1rem] font-medium leading-5 text-[#2c6fb7] transition-colors hover:bg-[#eef6ff]"
|
||||
className="gis-layer-action-button ml-auto shrink-0 rounded-sm border border-[#9cc6f0] bg-white px-2 py-[1px] text-[1.1rem] font-medium leading-5 text-[#2c6fb7] transition-colors hover:bg-[#eef6ff]"
|
||||
onClick={() =>
|
||||
handleLayerImgClick(
|
||||
node.data.lyrTyNm,
|
||||
@@ -624,26 +648,41 @@ export default React.memo(function GisTree({
|
||||
return aOrder - bOrder;
|
||||
}}
|
||||
/>
|
||||
</DndProvider>
|
||||
</DndProvider>
|
||||
</div>
|
||||
</div>
|
||||
{LayerSettingView && (
|
||||
<div className="gis-layer-setting shrink-0 bg-white px-2 pb-3">
|
||||
<LayerSettingView />
|
||||
</div>
|
||||
)}
|
||||
{!detachedToggleButton && (
|
||||
<button
|
||||
onClick={() => setVisible(false)}
|
||||
className={`gis-layer-toggle absolute right-[-3px] z-50 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{detachedToggleButton && visible && (
|
||||
<button
|
||||
onClick={() => setVisible(false)}
|
||||
className={`gis-layer-toggle absolute right-[-3px] z-50 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
className={`gis-layer-toggle absolute left-[250px] z-50 flex h-[36px] w-[20px] transform items-center justify-center rounded-[4px] border border-[#d8dee8] bg-[rgba(255,255,255,0.96)] text-[#1e88e5] shadow-[0_8px_18px_rgba(15,23,42,0.14)] transition hover:bg-[#eaf4ff] ${toggleButtonPositionClass}`}
|
||||
title="접기"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!visible && (
|
||||
<button
|
||||
onClick={() => setVisible(true)}
|
||||
className={`gis-layer-toggle absolute left-[-3px] z-50 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
className={detachedToggleButton
|
||||
? `gis-layer-toggle absolute left-0 z-50 flex h-[36px] w-[20px] transform items-center justify-center rounded-[4px] border border-[#d8dee8] bg-[rgba(255,255,255,0.96)] text-[#1e88e5] shadow-[0_8px_18px_rgba(15,23,42,0.14)] transition hover:bg-[#eaf4ff] ${toggleButtonPositionClass}`
|
||||
: `gis-layer-toggle absolute left-[-3px] z-50 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
title="열기"
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
{detachedToggleButton ? <ChevronRight className="h-4 w-4" /> : <ChevronRight size={16} />}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -45,7 +45,7 @@ GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${G
|
||||
|
||||
// NGII 정보(배경지도)
|
||||
//GisApp.NGII_WMTS_URL = process.env.NEXT_PUBLIC_NGII_URL;
|
||||
GisApp.NGII_WMTS_URL = `/ngii-server/openapi/Gettile.do?apikey=${process.env.NEXT_PUBLIC_NGII_PARAM}`;
|
||||
GisApp.NGII_WMTS_PARAM = process.env.NEXT_PUBLIC_NGII_PARAM ?? '';
|
||||
|
||||
GisApp.EMAP_URL = '/emap/';
|
||||
GisApp.EMAP_SATELLITE_URL = '/emap/satellite/';
|
||||
@@ -79,6 +79,17 @@ GisApp.setLayerInfos = function (layerInfos) {
|
||||
GisApp.layerInfos = layerInfos;
|
||||
};
|
||||
|
||||
GisApp.getNgiiWmtsUrl = function (layerName, matrix, x, y) {
|
||||
const layerAwareParams = /(^|&)layer=[^&]*/i.test(GisApp.NGII_WMTS_PARAM)
|
||||
? GisApp.NGII_WMTS_PARAM.replace(/(^|&)layer=[^&]*/i, `$1layer=${layerName}`)
|
||||
: `${GisApp.NGII_WMTS_PARAM}&layer=${layerName}`;
|
||||
|
||||
return `/ngii-server/openapi/Gettile.do?apikey=${layerAwareParams}`
|
||||
.replace("{z}", matrix)
|
||||
.replace("{x}", x)
|
||||
.replace("{y}", y);
|
||||
};
|
||||
|
||||
/**
|
||||
* GisApp core
|
||||
* @requires gis.layer.js
|
||||
@@ -161,7 +172,7 @@ GisApp.core.prototype = {
|
||||
});
|
||||
|
||||
const wmtsSource = new TileWMTS({
|
||||
//url: GisApp.NGII_WMTS_URL,
|
||||
// url은 setTileUrlFunction에서 layer별로 동적으로 만든다.
|
||||
projection: projection,
|
||||
tileGrid: tileGrid,
|
||||
crossOrigin: "anonymous",
|
||||
@@ -175,11 +186,7 @@ GisApp.core.prototype = {
|
||||
wmtsSource.setTileUrlFunction((tileCoord) => {
|
||||
const [z, x, y] = tileCoord;
|
||||
const matrix = matrixIds[z];
|
||||
// console.log(GisApp.NGII_WMTS_URL.replace("{z}", matrix).replace("{x}", x).replace("{y}", y));
|
||||
return GisApp.NGII_WMTS_URL
|
||||
.replace("{z}", matrix)
|
||||
.replace("{x}", x)
|
||||
.replace("{y}", y);
|
||||
return GisApp.getNgiiWmtsUrl(_layer, matrix, x, y);
|
||||
});
|
||||
|
||||
return new TileLayer({
|
||||
|
||||
@@ -742,10 +742,13 @@ GisOlLayer.prototype = {
|
||||
const extent = this.combineExtents(geometries.map(g => g?.getExtent()));
|
||||
|
||||
let map = GisApp.Module.core.map;
|
||||
const targetZoom = 16;
|
||||
const currentZoom = Number(map.getView().getZoom());
|
||||
const maxZoom = Number.isFinite(currentZoom) && currentZoom > targetZoom ? currentZoom : targetZoom;
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16
|
||||
maxZoom: maxZoom
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -132,13 +132,15 @@ GisOlSelect.prototype = {
|
||||
const selectedLayer = e.target?.getLayer(feature) || feature.getLayer?.();
|
||||
const clckZoom = Number(selectedLayer?.get('clckZoom'));
|
||||
const targetZoom = Number.isFinite(clckZoom) && clckZoom > 0 ? clckZoom : 16;
|
||||
const currentZoom = Number(map.getView().getZoom());
|
||||
const maxZoom = Number.isFinite(currentZoom) && currentZoom > targetZoom ? currentZoom : targetZoom;
|
||||
const geometry = feature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: targetZoom
|
||||
maxZoom: maxZoom
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +343,13 @@ GisOlStyle.prototype = {
|
||||
//레이어 스타일 반환
|
||||
getLayerStyle: function (layer, options) {
|
||||
let gobjTy = layer.get('gobjTy');
|
||||
if (!gobjTy) return;
|
||||
if (!gobjTy || typeof this[gobjTy] !== 'function') return;
|
||||
let style = this[gobjTy](layer, options);
|
||||
return style;
|
||||
},
|
||||
getLayerStyleFunction: function (layer, options) {
|
||||
let gobjTy = layer.get('gobjTy');
|
||||
if (!gobjTy || typeof this[gobjTy] !== 'function') return;
|
||||
let styleFunction = function (feature, resolution) {
|
||||
return this[gobjTy](layer, options, feature, resolution);
|
||||
}.bind(this);
|
||||
|
||||
Reference in New Issue
Block a user