diff --git a/B04_wf1_Surface/B04_wf1_Surface_Api_Fetch.ts b/B04_wf1_Surface/B04_wf1_Surface_Api_Fetch.ts index 1703f021..bdc38774 100644 --- a/B04_wf1_Surface/B04_wf1_Surface_Api_Fetch.ts +++ b/B04_wf1_Surface/B04_wf1_Surface_Api_Fetch.ts @@ -457,6 +457,8 @@ export interface DetailBasin { tc_minutes?: number | null; intensity_mm_hr?: number | null; design_flow_m3s?: number | null; + /** 유효직경이 관 최대 규격 초과 — 세월교·물넘이·교량 검토 대상(임도설치규정 제12조). */ + bridge_required?: boolean; } export interface DetailBasinResponse { diff --git a/B04_wf1_Surface/B04_wf1_Surface_Router_Basins.py b/B04_wf1_Surface/B04_wf1_Surface_Router_Basins.py index 63cf5f12..0d91f71d 100644 --- a/B04_wf1_Surface/B04_wf1_Surface_Router_Basins.py +++ b/B04_wf1_Surface/B04_wf1_Surface_Router_Basins.py @@ -130,6 +130,8 @@ def _payload( "tc_minutes": basin.tc_minutes, "intensity_mm_hr": basin.intensity_mm_hr, "design_flow_m3s": basin.design_flow_m3s, + # 유효직경이 관 최대 규격 초과 — 세월교·물넘이·교량 검토 대상(임도설치규정 제12조). + "bridge_required": basin.bridge_required, } for basin in detail.basins ], @@ -161,6 +163,7 @@ def _basin_features( "tc_minutes": basin.tc_minutes, "intensity_mm_hr": basin.intensity_mm_hr, "design_flow_m3s": basin.design_flow_m3s, + "bridge_required": basin.bridge_required, }, "geometry": {"type": "Polygon", "coordinates": [ring]}, } diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Panel.ts b/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Panel.ts index dcbb38f7..c05b6709 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Panel.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Panel.ts @@ -398,12 +398,16 @@ export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): Dra summaryText(summary, pipeEditor.pipes().length, basins.length, zSource); callbacks.onPipesChanged?.( // 관마다 담당 유역의 배수 유효직경을 붙인다(±0.5m 매칭). 유역 없는 관은 null. - pipeEditor.chainages().map((chainage) => ({ - chainage_m: chainage, - effective_diameter_mm: - basins.find((basin) => Math.abs(basin.chainage_m - chainage) < 0.51)?.pipe_diameter_mm ?? - null, - })), + // 세월교 검토 유역(bridge_required)도 null — 관 규격을 최대치로 올려 봐야 무의미하고, + // 그 지점은 세월교·물넘이로 별도 설계한다(임도설치규정 제12조). + pipeEditor.chainages().map((chainage) => { + const basin = basins.find((entry) => Math.abs(entry.chainage_m - chainage) < 0.51); + return { + chainage_m: chainage, + effective_diameter_mm: + basin && !basin.bridge_required ? (basin.pipe_diameter_mm ?? null) : null, + }; + }), ); } diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Parts.ts b/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Parts.ts index 211b7eb9..fdd9e912 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Parts.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Drainage_Parts.ts @@ -130,8 +130,13 @@ export function renderBasinRows( const metrics = document.createElement("span"); metrics.className = "b05-drainage__basin-metrics"; // 유효직경은 강우량표(rainfall_table.json)가 생기기 전까지 null → "미정" 표기. - const pipe = - basin.pipe_diameter_mm === null + // 관 최대 규격 초과 계류 유역은 관이 아니라 세월교 대상 — "세월교 검토"로 표기한다. + const pipe = basin.bridge_required + ? L("B05_Drainage_Basin_Bridge").replace( + "{d}", + String(Math.round(basin.pipe_diameter_mm ?? 0)), + ) + : basin.pipe_diameter_mm === null ? L("B05_Drainage_Basin_Undecided") : `Ø${Math.round(basin.pipe_diameter_mm)}mm`; metrics.textContent = L("B05_Drainage_Basin_Metrics") diff --git a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts index f5c3136b..30ef0f5a 100644 --- a/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts +++ b/B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts @@ -484,13 +484,18 @@ export function createRouteProfilePanel( // 그래프 40% : 테이블 60% (상단 정보 라인은 본문 밖이라 애초에 빠져 있다). // 가로 스크롤바를 `overflow-x: scroll`로 항상 띄우므로 clientHeight에서 이미 빠져 있다. const available = Math.max(120, body.clientHeight); - // 테이블은 유토곡선과 같은 바닥 고정 오버레이 서브패널로 옮겼다(2026-08-05 사용자 지시). - // 종단도는 본문 전체 높이를 쓰고, 바닥에 접힌 손잡이 띠(24px)만 남긴다 — 띠 없이는 - // 손잡이가 그래프 편집 버튼 위에 떠서 입력 클릭을 가로챈다(2026-08-04 사용자 보고). - const chartHeight = Math.max(MIN_CHART_HEIGHT, available - MASSHAUL_HANDLE_GUTTER_PX); - // 오버레이 순서(위→아래): 종단도 → 테이블 → 유토곡선. 유토곡선이 열려 있으면 그 높이만큼 - // 테이블 오버레이를 위로 밀어 쌓는다. - tableOverlay.setBottomOffset(massHaul.overlay.hidden ? 0 : massHaul.overlay.offsetHeight); + // 서브패널은 종단도를 **밀고 올라온다**(2026-08-05 사용자 확정 — 덮지 않는다). + // · 둘 다 접힘: 종단도가 전체 높이(손잡이 띠 24px만 남김). + // · 하나만 펼침: 그 패널 높이만큼 종단도가 줄어든다. 테이블 높이는 고정. + // · 둘 다 펼침: 위→아래 = 종단도 → 테이블 → 유토곡선. 유토곡선 높이를 끌어 늘리면 + // 테이블은 그대로 두고 종단도가 그만큼 줄어든다(리사이저 onResize → draw 재계산). + const massHeight = massHaul.overlay.hidden ? 0 : massHaul.overlay.offsetHeight; + const tableOverlayHeight = tableOverlay.isOpen() ? tableOverlay.overlay.offsetHeight : 0; + tableOverlay.setBottomOffset(massHeight); + const chartHeight = Math.max( + MIN_CHART_HEIGHT, + available - massHeight - tableOverlayHeight - MASSHAUL_HANDLE_GUTTER_PX, + ); const tableHeight = tableOverlay.contentHeight(); const table = @@ -614,7 +619,8 @@ export function createRouteProfilePanel( }), ); } - canvas.style.height = `${available}px`; + // 캔버스 높이 = 종단도 영역만. 서브패널이 차지한 아래 공간을 덮지 않는다(밀어올리기). + canvas.style.height = `${chartHeight}px`; canvas.append(chartWrap); body.replaceChildren(canvas); body.scrollLeft = scrollLeft; diff --git a/common_util/common_util_drainage_detail.py b/common_util/common_util_drainage_detail.py index b67dc928..326126d2 100644 --- a/common_util/common_util_drainage_detail.py +++ b/common_util/common_util_drainage_detail.py @@ -44,6 +44,7 @@ from common_util.common_util_route_geometry import ( ) from common_util.common_util_wamis_rainfall import idf_intensity from config.config_system import ( + DRAINAGE_BRIDGE_THRESHOLD_MM, DRAINAGE_DESIGN_FLOW_FACTOR, DRAINAGE_DITCH_SAMPLE_M, DRAINAGE_FLOW_AREA_RATIO, @@ -104,6 +105,9 @@ class WatershedBasin: tc_minutes: float | None = None intensity_mm_hr: float | None = None design_flow_m3s: float | None = None + # 유효직경이 관 최대 규격을 넘는 계류 유역 — 관이 아니라 세월교·물넘이·교량 대상 + # (임도설치규정 제12조). 화면은 "세월교 검토"로 표기하고 관경 자동 지정을 하지 않는다. + bridge_required: bool = False @dataclass @@ -529,6 +533,9 @@ def assemble_basins( tc_minutes=sizing["tc_minutes"] if sizing else None, intensity_mm_hr=sizing["intensity_mm_hr"] if sizing else None, design_flow_m3s=sizing["design_flow_m3s"] if sizing else None, + bridge_required=bool( + sizing and sizing["diameter_mm"] > DRAINAGE_BRIDGE_THRESHOLD_MM + ), ) ) return basins @@ -595,8 +602,8 @@ def size_pipe( pipe_slope = math.tan(math.radians(DRAINAGE_PIPE_SLOPE_DEG)) diameter = 0.5 for _ in range(20): - velocity = (1.0 / DRAINAGE_MANNING_N) * (diameter / 4.0) ** (2.0 / 3.0) * math.sqrt( - pipe_slope + velocity = ( + (1.0 / DRAINAGE_MANNING_N) * (diameter / 4.0) ** (2.0 / 3.0) * math.sqrt(pipe_slope) ) velocity = min(DRAINAGE_VELOCITY_MAX_MS, max(DRAINAGE_VELOCITY_MIN_MS, velocity)) required_area = design_flow / velocity diff --git a/config/config_system.py b/config/config_system.py index c7691314..d6acb9d9 100644 --- a/config/config_system.py +++ b/config/config_system.py @@ -341,6 +341,10 @@ DRAINAGE_VELOCITY_MIN_MS = float(os.getenv("DRAINAGE_VELOCITY_MIN_MS", "0.8")) DRAINAGE_VELOCITY_MAX_MS = float(os.getenv("DRAINAGE_VELOCITY_MAX_MS", "3.0")) # 원형관 통수단면 비율. 도로설계요령: 관 단면의 70%만 통수 고려. DRAINAGE_FLOW_AREA_RATIO = float(os.getenv("DRAINAGE_FLOW_AREA_RATIO", "0.7")) +# 세월교 검토 문턱(mm). 유효직경이 관 최대 규격(파형강관 D2000)을 넘으면 관이 아니라 +# 세월교·물넘이·교량 대상이다 — 「임도설치 및 관리 등에 관한 규정」 제12조: 계류 횡단 +# 구간은 배수구 막힘 우려가 없는 물넘이 포장(세월교) 또는 교량으로 설계(2026-08-05 조사). +DRAINAGE_BRIDGE_THRESHOLD_MM = float(os.getenv("DRAINAGE_BRIDGE_THRESHOLD_MM", "2000")) # ── B05 구조물 옵션 (드롭다운 목록·기본값, 2026-08-05 사용자 확정) ── # 프론트는 이 값을 /api 설정 응답 또는 빌드타임 복사로 받아 쓴다. 수정은 여기서만 한다. @@ -349,8 +353,27 @@ STRUCTURE_DEFAULT_TYPE = "배관" PIPE_DIAMETERS_MM = { "이중벽관": (150, 200, 250, 300, 400, 450, 500, 600, 700, 800, 900, 1000, 1200, 1500), "삼중벽관": (200, 250, 300, 400, 450, 500, 600, 700, 800, 900, 1000, 1200, 1500), - "파형강관": (150, 200, 250, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, - 1200, 1350, 1500, 1650, 1800, 2000), + "파형강관": ( + 150, + 200, + 250, + 300, + 350, + 400, + 450, + 500, + 600, + 700, + 800, + 900, + 1000, + 1200, + 1350, + 1500, + 1650, + 1800, + 2000, + ), } PIPE_DEFAULT_TYPE = "파형강관" # 자동 지정 기본 관경. 별표2 (나) 예외 하한 800mm(사용자 결정 — 계산값이 더 크면 바로 위 규격). diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index 0b6b8f4a..a236d0ac 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -77,6 +77,8 @@ export const ui_locales_b2 = { ], B05_Drainage_Status_LoadFailed: ["배경도를 불러오지 못했습니다.", "Failed to load the basemap."], B05_Drainage_Basin_Undecided: ["미정", "TBD"], + /* {d}=계산 유효직경(mm). 관 최대 규격 초과 계류 유역 — 관이 아니라 세월교 대상 */ + B05_Drainage_Basin_Bridge: ["세월교 검토 (Ø{d}mm)", "Ford crossing req. (Ø{d}mm)"], /* {area}=배수 유효면적, {relief}=표고차, {flow}=유하장, {pipe}=배수 유효직경 */ B05_Drainage_Basin_Metrics: [ "유효면적 {area} · 표고 {relief}m · 유하 {flow}m · 유효직경 {pipe}",