Merge branch 'iipbs/sn/dev'

This commit is contained in:
dongsilove
2026-03-18 23:01:51 +09:00
35 changed files with 921 additions and 600 deletions
@@ -19,6 +19,7 @@ import GisMapToggle from "./GisMapToggle";
import GisSelect from "./GisSelect";
import GisToolBar from "./GisToolBar";
import GisTree from "./GisTree";
import GisZoomLevel from "./GisZoomLevel";
import "./lib/css/gis.common.css";
import GisApp from "./lib/gis.core";
import LoadingOverlay from "@common/business_components/ui/loadingBar";
@@ -58,6 +59,7 @@ function Gis({
droughtDates = { startYmd: '', endYmd: '' },
defaultZoom = 15,
defaultLonLat = [127.4875, 37.4916],
showZoomLevel = false,
}) {
const mapRef = useRef();
const [mapObject, setMapObject] = useState({});
@@ -325,6 +327,7 @@ function Gis({
</div>
)}
<GisToolBar />
{showZoomLevel && <GisZoomLevel />}
<GisMapToggle />
<GisSelect onFeatureSelect={handleFeatureSelect} />
</div>
@@ -0,0 +1,113 @@
import React, { useEffect, useRef, useState } from 'react';
import { useGisContext } from './Gis';
export default React.memo(function GisZoomLevel() {
const { gisMap } = useGisContext();
const trackRef = useRef(null);
const [zoomInfo, setZoomInfo] = useState({
zoom: 0,
minZoom: 0,
maxZoom: 0,
});
useEffect(() => {
if (!gisMap) return undefined;
const view = gisMap.getView();
if (!view) return undefined;
const updateZoomInfo = () => {
setZoomInfo({
zoom: view.getZoom() ?? 0,
minZoom: view.getMinZoom?.() ?? 0,
maxZoom: view.getMaxZoom?.() ?? 20,
});
};
updateZoomInfo();
view.on('change:resolution', updateZoomInfo);
return () => {
view.un('change:resolution', updateZoomInfo);
};
}, [gisMap]);
if (!gisMap) return <></>;
const { zoom, minZoom, maxZoom } = zoomInfo;
const range = Math.max(maxZoom - minZoom, 1);
const normalized = Math.min(Math.max((zoom - minZoom) / range, 0), 1);
const fillHeight = `${normalized * 100}%`;
const thumbBottom = `calc(${fillHeight} - 1px)`;
const updateZoomFromClientY = clientY => {
if (!gisMap || !trackRef.current) return;
const view = gisMap.getView();
const rect = trackRef.current.getBoundingClientRect();
const offsetY = rect.bottom - clientY;
const nextNormalized = Math.min(Math.max(offsetY / rect.height, 0), 1);
const nextZoom = minZoom + nextNormalized * range;
view.setZoom(nextZoom);
};
const zoomIn = () => {
const view = gisMap.getView();
if (view.getMaxZoom() > view.getZoom()) {
view.animate({
zoom: view.getZoom() + 1,
duration: 250,
});
}
};
const zoomOut = () => {
const view = gisMap.getView();
if (view.getMinZoom() < view.getZoom()) {
view.animate({
zoom: view.getZoom() - 1,
duration: 250,
});
}
};
const handleTrackPointerDown = event => {
event.preventDefault();
updateZoomFromClientY(event.clientY);
const handlePointerMove = moveEvent => {
updateZoomFromClientY(moveEvent.clientY);
};
const handlePointerUp = () => {
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
};
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
};
return (
<div className="zoom-level-control" title={`현재 줌 레벨 ${zoom.toFixed(1)}`}>
<div className="zoom-level-panel">
<button type="button" className="zoom-level-btn" onClick={zoomIn} aria-label="확대">
+
</button>
<div className="zoom-level-gauge">
<div
ref={trackRef}
className="zoom-level-track"
onPointerDown={handleTrackPointerDown}
>
<div className="zoom-level-fill" style={{ height: fillHeight }} />
<div className="zoom-level-thumb" style={{ bottom: thumbBottom }} />
</div>
</div>
<button type="button" className="zoom-level-btn" onClick={zoomOut} aria-label="축소">
-
</button>
</div>
</div>
);
});
@@ -173,6 +173,100 @@
text-indent: -9999px;
}
.zoom-level-control {
position: absolute;
right: 20px;
bottom: 60px;
z-index: 2;
}
.zoom-level-panel {
overflow: hidden;
width: 40px;
background: #fff;
border: 1px solid #9aa7b8;
border-radius: 3px;
box-shadow: 0px 0px 5px #999;
box-sizing: border-box;
}
.zoom-level-btn {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 30px;
padding: 0;
border: 0;
border-bottom: 1px solid #eeeeee;
background: #fff;
color: #333;
font-size: 26px;
line-height: 1;
cursor: pointer;
}
.zoom-level-panel .zoom-level-btn:last-child {
border-top: 1px solid #eeeeee;
border-bottom: 0 none;
}
.zoom-level-btn:hover {
background: #67aee3;
color: #fff;
}
.zoom-level-gauge {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 128px;
background: #fff;
}
.zoom-level-track {
position: relative;
width: 12px;
height: 96px;
border-radius: 6px;
cursor: ns-resize;
touch-action: none;
background:
linear-gradient(to top, #b8b8b8, #b8b8b8) center / 4px 100% no-repeat,
repeating-linear-gradient(
to top,
transparent 0,
transparent 8px,
#d9d9d9 8px,
#d9d9d9 9px
),
linear-gradient(to top, #f5f5f5, #fafafa);
}
.zoom-level-fill {
position: absolute;
left: 50%;
bottom: 0;
width: 4px;
background: #3b82f6;
border-radius: 999px;
transform: translateX(-50%);
}
.zoom-level-thumb {
position: absolute;
left: 50%;
width: 22px;
height: 5px;
border: 0.5px solid #111;
border-radius: 2px;
background: #fff;
transform: translateX(-50%);
box-sizing: border-box;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.ol-scale-line {
background: rgba(0, 60, 136, 0.3);
border-radius: 4px;
@@ -118,7 +118,7 @@ GisOlLayer.prototype = {
let toProj = GisApp.TO_PROJ;
let lyrTyNm = options.lyrTyNm;
let typename = this.storageNm + lyrTyNm;
let filter = options?.flterSql && options.flterSql && options.flterSql.trim() !== '';
const filter = (options?.flterSql ?? '').trim();
let params = {};
params['TYPENAME'] = typename;