Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev
# Conflicts: # apps/sn/src/features/windowPopup/model/popupDefinitions.ts # apps/sn/src/features/windowPopup/registry.tsx
This commit is contained in:
@@ -346,7 +346,13 @@ export default React.memo(function GisTree({
|
||||
|
||||
// orderedLayers 순서를 우선 적용하면서, 타입 필터에 맞는 타겟 노드만 선별
|
||||
const getTargetNodesInOrder = () => {
|
||||
const target = nodes.filter((n) => n.data?.actlLyrYn === 'N' && layerTypes.includes(n.data?.lyrTyNm));
|
||||
const target = nodes.filter((node) => {
|
||||
const hasChildren = nodes.some((child) => child.parent === node.id);
|
||||
const isWmsGroupLayer = node.data?.srvcNm === 'wms' && hasChildren;
|
||||
return node.data?.exprsYn === 'Y'
|
||||
&& layerTypes.includes(node.data?.lyrTyNm)
|
||||
&& (node.data?.actlLyrYn === 'N' || isWmsGroupLayer);
|
||||
});
|
||||
|
||||
// drawOrdr 값을 기준으로 오름차순 정렬
|
||||
target.sort((a, b) => {
|
||||
|
||||
@@ -392,6 +392,7 @@ GisApp.core.prototype = {
|
||||
*/
|
||||
addWmsLayer: function (layerCodeObj) {
|
||||
let layer = GisApp.Module.layer.getWmsLayer(layerCodeObj);
|
||||
if (!layer) return null;
|
||||
this.deleteLayer(layer.get('id'));
|
||||
this.map.addLayer(layer);
|
||||
return layer;
|
||||
|
||||
@@ -20,6 +20,11 @@ import GisOlStyle from './gis.style.js';
|
||||
import Tile from 'ol/layer/Tile.js';
|
||||
import TileWMS from 'ol/source/TileWMS.js';
|
||||
import gisCore from './gis.core.js';
|
||||
import {
|
||||
buildFloodDngrSldBody,
|
||||
FLOOD_DNGR_MAP_LAYER_TYPE,
|
||||
getFloodDngrSldSettings,
|
||||
} from './gis.wms-sld.js';
|
||||
|
||||
/**
|
||||
* GisApp.layer
|
||||
@@ -179,6 +184,7 @@ GisOlLayer.prototype = {
|
||||
let url = GisApp.GEOSERVER_WMS_URL;
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
const lyrTyNm = options.lyrTyNm;
|
||||
const filter = String(options?.flterSql ?? '').trim();
|
||||
let params = {};
|
||||
params['LAYERS'] = this.storageNm + lyrTyNm;
|
||||
params['SERVICE'] = 'WMS';
|
||||
@@ -186,7 +192,21 @@ GisOlLayer.prototype = {
|
||||
params['REQUEST'] = 'GetMap';
|
||||
params['SRS'] = toProj;
|
||||
params['FORMAT'] = 'image/png';
|
||||
params['TRANSPARENT'] = true;
|
||||
params['TILED'] = true;
|
||||
params['STYLES'] = '';
|
||||
if (filter) params['CQL_FILTER'] = filter;
|
||||
|
||||
if (lyrTyNm === FLOOD_DNGR_MAP_LAYER_TYPE) {
|
||||
const sldSettings = getFloodDngrSldSettings(GisApp.layerCode, options?.id);
|
||||
const sldBody = buildFloodDngrSldBody(lyrTyNm, sldSettings);
|
||||
if (!sldBody) {
|
||||
console.warn('[GisApp] 도시침수지도 WMS SLD 생성에 필요한 침수심도 설정 5건이 유효하지 않습니다.', options);
|
||||
return null;
|
||||
}
|
||||
params['SLD_BODY'] = sldBody;
|
||||
}
|
||||
|
||||
const wmsLayer = new Tile({
|
||||
...layerOptions,
|
||||
id: layerOptions?.id,
|
||||
@@ -196,8 +216,9 @@ GisOlLayer.prototype = {
|
||||
serverType: 'geoserver',
|
||||
}),
|
||||
});
|
||||
wmsLayer.setZIndex(options.draw0rdr);
|
||||
wmsLayer.setMinZoom(options.minViewSize);
|
||||
wmsLayer.set('id', String(layerOptions?.id));
|
||||
wmsLayer.setZIndex(Number(options?.drawOrdr) || 1);
|
||||
wmsLayer.setMinZoom(Number(options?.minViewSize) || 0);
|
||||
this.layers.push(wmsLayer);
|
||||
return wmsLayer;
|
||||
},
|
||||
|
||||
@@ -29,9 +29,11 @@ GisOlSelect.prototype = {
|
||||
dragBox: null,
|
||||
dragPolygon: null,
|
||||
selectedFeatures: [],
|
||||
focusedBoundaryFeatures: null,
|
||||
selectFeatureFlag: false,
|
||||
initialize: function (map, options) {
|
||||
this.map = map;
|
||||
this.focusedBoundaryFeatures = new WeakSet();
|
||||
Object.assign(this.defaults, options);
|
||||
this.setInteraction();
|
||||
this.pointerMoveEventHandler();
|
||||
@@ -107,7 +109,8 @@ GisOlSelect.prototype = {
|
||||
}
|
||||
}
|
||||
const geometryType = feature?.getGeometry?.()?.getType?.();
|
||||
if (geometryType === 'LineString' || geometryType === 'MultiLineString') {
|
||||
const focusedBoundary = me.focusedBoundaryFeatures.has(feature);
|
||||
if (focusedBoundary || geometryType === 'LineString' || geometryType === 'MultiLineString') {
|
||||
styles.push(...focusedLineStyle);
|
||||
} else {
|
||||
styles.push(baseStyle);
|
||||
@@ -329,6 +332,7 @@ GisOlSelect.prototype = {
|
||||
selectedFeatures.remove(feature);
|
||||
});
|
||||
selectedFeatures.clear();
|
||||
this.focusedBoundaryFeatures = new WeakSet();
|
||||
},
|
||||
/**
|
||||
* SELECT 이벤트 활성화
|
||||
@@ -491,6 +495,20 @@ GisOlSelect.prototype = {
|
||||
auto: auto,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 폴리곤을 선형 피처와 동일한 다중 경계 스타일로 강조한다.
|
||||
*/
|
||||
selectFocusedBoundaryFeature: function (feature, auto) {
|
||||
let me = this;
|
||||
me.focusedBoundaryFeatures.add(feature);
|
||||
me.selectedFeatures.push(feature);
|
||||
me.selectInteraction.dispatchEvent({
|
||||
type: 'select',
|
||||
selected: me.selectedFeatures,
|
||||
deselected: [],
|
||||
auto: auto,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default GisOlSelect;
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
const FLOOD_DNGR_MAP_LAYER_TYPE = 'tb_ms_flood_dngr_map';
|
||||
const FLOOD_DEPTH_ATTRIBUTE = 'sbmrg_dpthl_cd';
|
||||
const FLOOD_DEPTH_CODES = ['N330', 'N331', 'N332', 'N333', 'N334'];
|
||||
|
||||
const escapeXml = (value) => String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const toHex = (value) => Math.round(value).toString(16).padStart(2, '0');
|
||||
|
||||
const parseCssColor = (value) => {
|
||||
const normalized = String(value ?? '').trim();
|
||||
const rgbaMatch = normalized.match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(0(?:\.\d+)?|1(?:\.0+)?))?\s*\)$/i);
|
||||
if (rgbaMatch) {
|
||||
const red = Number(rgbaMatch[1]);
|
||||
const green = Number(rgbaMatch[2]);
|
||||
const blue = Number(rgbaMatch[3]);
|
||||
const opacity = rgbaMatch[4] == null ? 1 : Number(rgbaMatch[4]);
|
||||
if ([red, green, blue].some((channel) => channel < 0 || channel > 255) || opacity < 0 || opacity > 1) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
hex: `#${toHex(red)}${toHex(green)}${toHex(blue)}`,
|
||||
opacity,
|
||||
};
|
||||
}
|
||||
|
||||
const hexMatch = normalized.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
||||
if (!hexMatch) return null;
|
||||
const rawHex = hexMatch[1].length === 3
|
||||
? hexMatch[1].split('').map((character) => character + character).join('')
|
||||
: hexMatch[1];
|
||||
return {
|
||||
hex: `#${rawHex.toLowerCase()}`,
|
||||
opacity: 1,
|
||||
};
|
||||
};
|
||||
|
||||
const getFloodDepthCode = (filterSql) => {
|
||||
const match = String(filterSql ?? '').match(/\bsbmrg_dpthl_cd\s*=\s*'([^']+)'/i);
|
||||
const depthCode = match?.[1]?.trim();
|
||||
return FLOOD_DEPTH_CODES.includes(depthCode) ? depthCode : null;
|
||||
};
|
||||
|
||||
const getLineWidth = (value) => {
|
||||
const lineWidth = Number(value);
|
||||
return Number.isFinite(lineWidth) && lineWidth >= 0 && lineWidth <= 20 ? lineWidth : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 빈도 부모 레이어 바로 아래에 있는 침수심도 설정만 선별한다.
|
||||
* 부모는 WMS 요청을 담당하고, 반환된 자식 5건은 SLD 규칙의 필터와 색상 원본으로 사용한다.
|
||||
*/
|
||||
export const getFloodDngrSldSettings = (layerCode, parentLayerId) => Object.values(layerCode ?? {})
|
||||
.filter((layerSetting) => (
|
||||
String(layerSetting?.upperLyrSn ?? '') === String(parentLayerId ?? '')
|
||||
&& layerSetting?.lyrTyNm === FLOOD_DNGR_MAP_LAYER_TYPE
|
||||
))
|
||||
.sort((left, right) => Number(left?.sortOrdr ?? 0) - Number(right?.sortOrdr ?? 0));
|
||||
|
||||
/**
|
||||
* DB의 침수심도 필터와 RGBA 스타일을 GeoServer가 해석할 수 있는 SLD 1.0 XML로 변환한다.
|
||||
* 다섯 침수심도 코드나 색상값 중 하나라도 유효하지 않으면 잘못된 기본 스타일 표출을 막기 위해 null을 반환한다.
|
||||
*/
|
||||
export const buildFloodDngrSldBody = (layerName, layerSettings) => {
|
||||
if (!Array.isArray(layerSettings) || layerSettings.length !== FLOOD_DEPTH_CODES.length) return null;
|
||||
|
||||
const settingsByDepthCode = new Map();
|
||||
for (const layerSetting of layerSettings) {
|
||||
const depthCode = getFloodDepthCode(layerSetting?.flterSql);
|
||||
if (depthCode && !settingsByDepthCode.has(depthCode)) {
|
||||
settingsByDepthCode.set(depthCode, layerSetting);
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsByDepthCode.size !== FLOOD_DEPTH_CODES.length) return null;
|
||||
|
||||
const rules = [];
|
||||
for (const depthCode of FLOOD_DEPTH_CODES) {
|
||||
const layerSetting = settingsByDepthCode.get(depthCode);
|
||||
const fillColor = parseCssColor(layerSetting?.polgnColor);
|
||||
const strokeColor = parseCssColor(layerSetting?.lnColor);
|
||||
const lineWidth = getLineWidth(layerSetting?.lnWdth);
|
||||
if (!fillColor || !strokeColor || lineWidth == null) return null;
|
||||
|
||||
rules.push(
|
||||
`<sld:Rule><sld:Name>${escapeXml(depthCode)}</sld:Name><sld:Title>${escapeXml(layerSetting?.lyrNm || depthCode)}</sld:Title>`
|
||||
+ `<ogc:Filter><ogc:PropertyIsEqualTo><ogc:PropertyName>${FLOOD_DEPTH_ATTRIBUTE}</ogc:PropertyName><ogc:Literal>${escapeXml(depthCode)}</ogc:Literal></ogc:PropertyIsEqualTo></ogc:Filter>`
|
||||
+ `<sld:PolygonSymbolizer><sld:Fill><sld:CssParameter name="fill">${fillColor.hex}</sld:CssParameter><sld:CssParameter name="fill-opacity">${fillColor.opacity}</sld:CssParameter></sld:Fill>`
|
||||
+ `<sld:Stroke><sld:CssParameter name="stroke">${strokeColor.hex}</sld:CssParameter><sld:CssParameter name="stroke-opacity">${strokeColor.opacity}</sld:CssParameter><sld:CssParameter name="stroke-width">${lineWidth}</sld:CssParameter></sld:Stroke></sld:PolygonSymbolizer></sld:Rule>`
|
||||
);
|
||||
}
|
||||
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>`
|
||||
+ `<sld:StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc">`
|
||||
+ `<sld:NamedLayer><sld:Name>${escapeXml(layerName)}</sld:Name><sld:UserStyle><sld:FeatureTypeStyle>${rules.join('')}</sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer>`
|
||||
+ `</sld:StyledLayerDescriptor>`;
|
||||
};
|
||||
|
||||
export { FLOOD_DNGR_MAP_LAYER_TYPE };
|
||||
@@ -269,6 +269,19 @@ export const ProcSearchBar = ({
|
||||
[sigunData],
|
||||
);
|
||||
|
||||
// 외부 지도 진입 이벤트가 시군구 원천 조회보다 먼저 도착해도,
|
||||
// 원천 목록이 준비되는 즉시 현재 시도에 맞는 선택 후보를 다시 구성한다.
|
||||
useEffect(() => {
|
||||
if (sidoValue === "0") {
|
||||
setFilteredSigunData([{ text: "시군구", val: "0" }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const prefix = sidoValue.substring(0, 2);
|
||||
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
|
||||
setFilteredSigunData([{ text: "시군구", val: "0" }, ...filtered]);
|
||||
}, [sidoValue, sigunData]);
|
||||
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
if (type === "ctpvCd") {
|
||||
|
||||
Reference in New Issue
Block a user