diff --git a/apps/sn/app/(map-layout)/map/ui/map-page-client.tsx b/apps/sn/app/(map-layout)/map/ui/map-page-client.tsx index 4379195b9..b801bb665 100644 --- a/apps/sn/app/(map-layout)/map/ui/map-page-client.tsx +++ b/apps/sn/app/(map-layout)/map/ui/map-page-client.tsx @@ -3211,7 +3211,7 @@ const MapPageClient = () => { ) : null} {selectedWaterRiskMapView ? ( // Capacity와 Flooding 색상은 SWMM 결과의 업무 의미색이므로 테마와 무관하게 유지한다. -
+
Capacity
{[ diff --git a/apps/sn/docs/작업내용/SN-도시침수지도-정보제공포털-목록보기/001-fe-SN-도시침수지도-정보제공포털-목록보기-통합구현기록.md b/apps/sn/docs/작업내용/SN-도시침수지도-정보제공포털-목록보기/001-fe-SN-도시침수지도-정보제공포털-목록보기-통합구현기록.md index e695d60c5..b5c9824e9 100644 --- a/apps/sn/docs/작업내용/SN-도시침수지도-정보제공포털-목록보기/001-fe-SN-도시침수지도-정보제공포털-목록보기-통합구현기록.md +++ b/apps/sn/docs/작업내용/SN-도시침수지도-정보제공포털-목록보기/001-fe-SN-도시침수지도-정보제공포털-목록보기-통합구현기록.md @@ -52,7 +52,7 @@ | 행 선택 지도 이동 | 기존 그룹 범위 이동 유지 | | 일반 지도 통신 | 빈도 부모 3개 TileWMS | | 빈도 제한 | 부모 `flterSql`을 WMS `CQL_FILTER`로 전달 | -| 침수심도 스타일 | 자식 5건 기반 동적 SLD | +| 침수심도 스타일 | GeoServer 레이어 기본 스타일(2026-10-16부터, 11절). 이전에는 자식 5건 기반 동적 SLD | | 범례 | 숨겨진 자식 설정의 기존 색상·명칭 유지 | | 100년 빈도 | 화면 및 WMS 전환 대상에서 제외 | | 기존 다른 레이어 | 기존 WFS 경로 유지 | @@ -88,3 +88,16 @@ - 운영에서 행 선택 후 그룹 extent API가 `ST_Envelope` 스키마 해석 오류로 실패해 지도 이동이 중단된 원인을 백엔드 SQL에서 보완했다. - 기존 시군구·침수빈도 그룹 범위 이동 계약과 화면 동작은 유지한다. - 재배포 후 목록 행 클릭, 지도 중심 이동, 선택 빈도 WMS 표출을 회귀 확인한다. + +## 11. 2026-10-16 WMS SLD_BODY 제거 + +- 근거: `COMMON_DOCS_ROOT/작업계획/SN-도시침수지도-정보제공포털-목록보기/003-de-SN-도시침수지도-WMS502-SLD_BODY제거-작업계획.md` (SFR-011-008·009, 화면설계서 24·28쪽 SN_MN010205·SN_MN010206) +- 운영에서 `SLD_BODY`가 포함된 WMS 요청이 nginx 502로 실패해, 침수심도 스타일을 GeoServer 레이어 기본 스타일로 옮기고 요청에서 `SLD_BODY`를 뺐다. +- `gis.layer.js` + - `getWmsLayer()`에서 도시침수지도 `SLD_BODY` 생성 분기와 `gis.wms-sld.js` import를 제거했다. + - `STYLES=''`(레이어 기본 스타일), 빈도 `CQL_FILTER`, `drawOrdr`, `minViewSize`는 그대로다. +- `gis.wms-sld.js`: 삭제했다(사용처가 `gis.layer.js` 하나뿐). +- 범례(`water-depth.tsx`)는 기존처럼 숨겨진 자식 5건의 DB 색상을 쓴다. GeoServer 기본 스타일이 제공 SLD(`tb_ms_flood_dngr_map_depth.sld`)로 교체되어야 지도 색과 범례가 일치한다. +- 검증: 남은 참조 없음, `git diff --check`·ESLint 통과, 개발 GeoServer 동일 파라미터 요청 HTTP 200 PNG. +- 미수행: 사용자 개발 서버 실행 중이라 `business_components`·`sn` 빌드 보류, 로그인 필요로 브라우저 Network 확인 보류. +- 롤백: `gis.layer.js`의 SLD 분기와 `gis.wms-sld.js`를 복원한다. diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index b6935c383..8baf58bb9 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -21,11 +21,6 @@ import Tile from 'ol/layer/Tile.js'; import TileWMS from 'ol/source/TileWMS.js'; import gisCore from './gis.core.js'; import { showBusinessUserMessage } from '../../../lib/user-message'; -import { - buildFloodDngrSldBody, - FLOOD_DNGR_MAP_LAYER_TYPE, - getFloodDngrSldSettings, -} from './gis.wms-sld.js'; /** * GisApp.layer @@ -195,19 +190,12 @@ GisOlLayer.prototype = { params['FORMAT'] = 'image/png'; params['TRANSPARENT'] = true; params['TILED'] = true; + // 빈 값이면 GeoServer 레이어의 기본 스타일(Default Style)로 그린다. + // 도시침수지도(tb_ms_flood_dngr_map)의 침수심도 색상도 GeoServer에 등록한 기본 스타일을 사용하며, + // 긴 SLD_BODY를 URL에 싣지 않아 운영 프록시의 헤더 크기 초과(502)를 피한다. 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, diff --git a/packages/common/business_components/src/ui/map/lib/gis.wms-sld.js b/packages/common/business_components/src/ui/map/lib/gis.wms-sld.js deleted file mode 100644 index 131378ae0..000000000 --- a/packages/common/business_components/src/ui/map/lib/gis.wms-sld.js +++ /dev/null @@ -1,103 +0,0 @@ -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( - `${escapeXml(depthCode)}${escapeXml(layerSetting?.lyrNm || depthCode)}` - + `${FLOOD_DEPTH_ATTRIBUTE}${escapeXml(depthCode)}` - + `${fillColor.hex}${fillColor.opacity}` - + `${strokeColor.hex}${strokeColor.opacity}${lineWidth}` - ); - } - - return `` - + `` - + `${escapeXml(layerName)}${rules.join('')}` - + ``; -}; - -export { FLOOD_DNGR_MAP_LAYER_TYPE };