Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
353 lines
18 KiB
TypeScript
353 lines
18 KiB
TypeScript
/* =============================================================================
|
||
* B06_Section_UI_Cross_Culvert_Const.ts
|
||
* 배수관 세트 **치수 상수·형태별 한계** — 기하 계산(`_Cross_Culvert_Geom.ts`)에서
|
||
* 분리했다(700줄 제한). 값의 근거는 각 상수 주석에 지식DB 출처로 남긴다.
|
||
* ========================================================================== */
|
||
|
||
import type { CrossSection, CulvertSideSpec } from "./B06_Section_Api_Fetch";
|
||
import type { SpanRole, SpanValues } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||
|
||
/** 관 위 최소 토피(m) — 프론트 정의처(백엔드는 `B06_Section_Engine_Culvert.MIN_PIPE_COVER_M`).
|
||
* ⚠ 임도 배수관 토피 규정이 지식DB에 없어 별표2 교량·암거 "복토 50㎝ 이상"을 교차
|
||
* 참조로 채택(2026-08-19 사용자 확정). B05 배수관 측점 계획고 하향 차단 기준이자,
|
||
* 절토측 유입 벽에서 관 invert의 상한(노면 − 관경 − 토피)을 정한다. */
|
||
export const MIN_PIPE_COVER_M = 0.5;
|
||
|
||
/** 기슭막이 벽 두께(m) — 실무 견치돌 뒷길이 관측치 45㎝(울진 L3=45). 표시용 형상 값. */
|
||
export const REVET_THICKNESS_M = 0.45;
|
||
|
||
/** 기슭막이 전면 기울기(1:n) **종전값** — 돌쌓기가 아닌 형태(콘크리트·돌망태…)와 성토/절토를
|
||
* 못 가른 자리만 이 값. 돌쌓기 벽은 `B06_Section_UI_Cross_Lean.leanOf`(품셈 13-4-4 [주]⑪ 표준경사). */
|
||
export const REVET_LEAN_RATIO = 0.3;
|
||
|
||
/** 돌붙임 두께(m) — 실무 돌붙임 L3=45(울진 1공구 수량집계표). */
|
||
export const PITCHING_THICKNESS_M = 0.45;
|
||
|
||
/** 사다리꼴(배면부) 상단 폭(m) — 띠 두께의 절반(2026-08-20 사용자 확정). */
|
||
export const REVET_TRAP_TOP_M = REVET_THICKNESS_M / 2;
|
||
|
||
/**
|
||
* 기슭막이 여유고(m) — 관 상단에서 벽 상단까지의 간격. 유입·유출 **공통**이다.
|
||
*
|
||
* ⚠ 확정값 아님(2026-08-21 사용자 지정). 지식DB에 임도 배수관 기슭막이 여유고
|
||
* 규정이 없다(`통수단면.md` §여유고 — "임도·KDS 원문에 수치 없음"). 관 상단 최소
|
||
* 토피 0.5m와 같은 값을 잠정 채택했고, 추후 **사용자 입력값**으로 교체 예정이다.
|
||
*
|
||
* 이 값 때문에 벽 높이는 좌·우가 같은 규칙으로 정해진다 — 관이 경사져 벽 밑(invert)
|
||
* 이 서로 달라도 관 상단 위 간격은 항상 같다.
|
||
*/
|
||
export const REVET_FREEBOARD_M = 0.5;
|
||
|
||
/** 기슭막이 높이 눈금(m) — 계산 높이를 이 단위로 올린다(2026-08-21 사용자 확정). */
|
||
export const REVET_HEIGHT_STEP_M = 0.1;
|
||
|
||
/** 관경에 여유고를 더해 눈금(0.1m)으로 올린 기슭막이 높이(m). */
|
||
export function revetTargetHeight(diameterM: number): number {
|
||
const raw = diameterM + REVET_FREEBOARD_M;
|
||
return Math.ceil(raw / REVET_HEIGHT_STEP_M - 1e-9) * REVET_HEIGHT_STEP_M;
|
||
}
|
||
|
||
/**
|
||
* 관 벽 두께(m) — 관종별. 지식DB 조회(2026-08-20):
|
||
* · 파형강관: 강판 두께 **1.6T~3.2T**(교본 부록 4-6 §2.5.2, 표-1 표준형 강관 치수,
|
||
* KS D 3590). Ø1000 이상은 1.6~3.2 전 규격 사용 — 중간값 2.7T 채택.
|
||
* · 흄관·VR관(원심력 철근콘크리트관): 관경별 벽 두께 표가 지식DB에 없어
|
||
* 관경의 **1/12**을 적용한다(⚠ 근거 미확정 — KS F 4403 확보 시 교체).
|
||
* 도면상 내경·외경 2줄을 긋기 위한 값이다.
|
||
*/
|
||
export function pipeWallThicknessM(kind: string | null, diameterM: number): number {
|
||
if (kind && kind.includes("파형강관")) return 0.0027;
|
||
return Math.max(diameterM / 12, 0.05);
|
||
}
|
||
|
||
/**
|
||
* 기슭막이 근입 깊이(m) — 벽 바닥이 원지반 아래로 묻히는 깊이.
|
||
*
|
||
* 지식DB 조회 결과(2026-08-20):
|
||
* · 기슭막이 자체의 근입 깊이 **규정 없음** — 사방교본은 "기초 세굴 위험 시
|
||
* 밑다짐·바닥막이·수제 병행"만 정한다(`00_구조물_선정기준.md` §4).
|
||
* · 흙막이: "하부 땅밀림 방지 위해 **본바닥(원지반)에 기초**"(`흙막이.md`) —
|
||
* 원지반에 묻어야 한다는 원칙만 있고 수치는 없다.
|
||
* · 실무(울진소광 구조도): 돌기슭막이 **기초형 = 기초콘크리트 H=0.5**,
|
||
* 깬돌 ℓ3=45·1:0.3 (`_원단위라이브러리_울진소광.md` §4).
|
||
* → 실무 기초콘크리트 높이 0.5m를 근입 깊이로 채택한다. 임도 법정 확정값이 아니라
|
||
* **실무 관측 기반**이므로 확정 기준 확보 시 교체한다.
|
||
*/
|
||
export const REVET_EMBED_DEPTH_M = 0.5;
|
||
|
||
/**
|
||
* 기슭막이 형태별 높이 한계(m) — 지식DB `돌쌓기.md` §1 / `흙막이.md` §2:
|
||
* 돌 **찰쌓기 3.0m 이하**, 돌 **메쌓기 2.0m 이하**(교본 7-3 돌흙막이 기준),
|
||
* 돌망태 2m 정도. 큰돌쌓기는 총높이 한계 없음(1일 시공 1.5m 제한만).
|
||
* 초과 시 콘크리트 옹벽 등 상위 구조물 검토 대상이다.
|
||
* 통나무·목재틀·바자는 지식DB에 수치가 없어 **2.0m**로 둔다(2026-08-29 사용자 확정).
|
||
*/
|
||
export function revetHeightLimit(form: string | null | undefined): number {
|
||
if (!form) return 3.0;
|
||
if (form.includes("메")) return 2.0;
|
||
if (form.includes("찰")) return 3.0;
|
||
if (form.includes("돌망태")) return 2.0;
|
||
if (form.includes("통나무") || form.includes("목재") || form.includes("바자")) return 2.0;
|
||
if (form.includes("콘크리트")) return 5.0; // 프로젝트 확정값 — materialLimit과 동일
|
||
return 3.0;
|
||
}
|
||
|
||
/* ── 기슭막이 재질·높이 조작(2026-08-22 사용자 확정) ─────────────────────
|
||
* 높이 조작이 교본 형태별 한계와 부딪히면 **재질을 바꿔야** 더 올릴 수 있다:
|
||
* 메쌓기 2.0 / 찰쌓기 3.0(돌쌓기.md §1) / 콘크리트 5.0(프로젝트 기본값,
|
||
* 외부 기준 미확인). 높이 제어·표기·한계 기준은 모두 **순수 높이**(벽 바닥~상단,
|
||
* 근입 0.5m 포함) — 배관 기슭막이 2026-08-23, 추가·독립 기슭막이 2026-08-29
|
||
* 사용자 확정으로 기준을 하나로 맞췄다. */
|
||
export type RevetMaterial = "dry" | "wet" | "concrete";
|
||
|
||
/**
|
||
* 기슭막이 **형태** — B05 폼(`B05_Profile_UI_Drainage_Facility_Fields`)과 같은 한 벌이다.
|
||
* 조정창 선택지도 이 목록을 쓴다(2026-08-29 사용자: "B05 폼 형태로 가는 게 맞다").
|
||
* 높이 한계는 `revetHeightLimit(form)`, 도형의 돌 표현은 `materialFromForm(form)`이 정한다.
|
||
*/
|
||
export const REVET_FORMS = [
|
||
"돌쌓기(찰)",
|
||
"돌쌓기(메)",
|
||
"콘크리트",
|
||
"돌망태",
|
||
"통나무·목재틀",
|
||
"바자",
|
||
];
|
||
/** 형태 미지정 시 기본 — B05 독립 기슭막이 폼 기본값과 같다. */
|
||
export const REVET_FORM_DEFAULT = "돌쌓기(메)";
|
||
|
||
/**
|
||
* 조작값 `m`을 형태 문자열로 읽는다. `m`은 2026-08-29부터 **형태**를 담지만, 그 전
|
||
* 저장분은 재질 코드(dry/wet/concrete)라 그 셋은 대응 형태로 옮긴다. 조작이 없으면
|
||
* 구조물 정본의 형태(`spec.revet_form`)를 따른다.
|
||
*/
|
||
export function revetFormOf(m: string | null | undefined, specForm?: string | null): string {
|
||
if (m === "dry") return "돌쌓기(메)";
|
||
if (m === "wet") return "돌쌓기(찰)";
|
||
if (m === "concrete") return "콘크리트";
|
||
return m || specForm || REVET_FORM_DEFAULT;
|
||
}
|
||
|
||
export const REVET_MATERIALS: RevetMaterial[] = ["dry", "wet", "concrete"];
|
||
|
||
export function materialLimit(material: RevetMaterial): number {
|
||
return material === "dry" ? 2.0 : material === "wet" ? 3.0 : 5.0;
|
||
}
|
||
|
||
export function materialLabel(material: RevetMaterial): string {
|
||
return material === "dry" ? "메쌓기" : material === "wet" ? "찰쌓기" : "콘크리트";
|
||
}
|
||
|
||
/** 백엔드 spec 형식 문자열("돌쌓기(메)" 등) → 재질 기본값. 기본은 메쌓기. */
|
||
export function materialFromForm(form: string | null | undefined): RevetMaterial {
|
||
if (form?.includes("찰")) return "wet";
|
||
if (form?.includes("콘크리트")) return "concrete";
|
||
return "dry";
|
||
}
|
||
|
||
/**
|
||
* 벽 한 매의 제원(형태·재질·높이) — 배관 벽과 독립 기슭막이가 같이 쓴다.
|
||
*
|
||
* 조정창이 고르는 값은 **형태**(B05 폼과 같은 6종 — 2026-08-29 사용자)이고, 재질(돌
|
||
* 표현)과 높이 한계는 형태에서 파생된다. 높이 기준은 **순수 높이**(바닥~상단, 근입
|
||
* 0.5m 포함). 독립 기슭막이(관 숨김)는 관경이 없어 하한이 근입뿐이고 기본 높이는
|
||
* 사용자가 고른 형태 높이(`revet_height_m`)다 — 배관은 관이 벽 밖으로 삐져나오지
|
||
* 않도록 관경 기준 최소 높이를 하한·기본으로 쓴다.
|
||
*/
|
||
export function revetWallSpec(
|
||
spec: CulvertSideSpec,
|
||
adjust: { h?: number | null; m?: string | null },
|
||
hiddenPipe: boolean,
|
||
diameterM: number,
|
||
): { form: string; material: RevetMaterial; limit: number; pureHeight: number; height: number } {
|
||
const form = revetFormOf(adjust.m, spec.revet_form);
|
||
const limit = revetHeightLimit(form);
|
||
const floor = hiddenPipe ? REVET_EMBED_DEPTH_M : pipeWallMinPureHeight(diameterM);
|
||
const preferred = hiddenPipe ? (spec.revet_height_m ?? floor) : floor;
|
||
const pureHeight = Math.min(Math.max(adjust.h ?? preferred, floor), Math.max(limit, floor));
|
||
return {
|
||
form,
|
||
material: materialFromForm(form),
|
||
limit,
|
||
pureHeight,
|
||
height: pureHeight - REVET_EMBED_DEPTH_M,
|
||
};
|
||
}
|
||
|
||
/** 관 접속 절·성토선 물매 — 0도 대신 3도(2026-08-22 사용자). 방향은 물 흐름을
|
||
* 따른다: 유입측은 계류 쪽이 높고(물이 관으로), 유출측은 계류 쪽이 낮다(관에서 밖으로). */
|
||
export const PIPE_CONNECT_GRADE = Math.tan((3 * Math.PI) / 180);
|
||
|
||
/**
|
||
* 배관 기슭막이 관 하부 여유(m) — 관 invert 아래 벽 바닥까지의 깊이(2026-08-23
|
||
* 사용자 확정: 배관 기준 아래 0.5·위 0.5 여유). 값은 근입 깊이와 같은 0.5라
|
||
* 형상은 종전과 동일하고, **순수 높이 하한 = 관경 + 위 0.5 + 아래 0.5**가 된다.
|
||
* 종전 고정 하한·기본 2.0m(관경 무관)는 폐기.
|
||
*/
|
||
export function pipeWallMinPureHeight(diameterM: number): number {
|
||
return revetTargetHeight(diameterM) + REVET_EMBED_DEPTH_M;
|
||
}
|
||
/** 일반(추가) 기슭막이 높이 조작 하한(m) — 프로젝트 기본값 0.5m. */
|
||
export const EXTRA_WALL_MIN_HEIGHT_M = 0.5;
|
||
/** 일반(추가) 기슭막이 기본 높이(m). */
|
||
export const EXTRA_WALL_DEFAULT_HEIGHT_M = 1.5;
|
||
|
||
/**
|
||
* 성토 비탈 기울기(1:n) 허용 범위 — 지식DB `성토_비탈면.md` §1:
|
||
* "**1:1.2~2.0** 범위에서 토질·용수 등 지형여건을 종합 고려해 설정".
|
||
* 1.2는 **범위의 급한 끝**이지 고정값이 아니다 — 배수관 구조물이 서는 측점에서는
|
||
* 사면선이 기슭막이 이음선 상단점을 지나도록 이 범위 안에서 물매를 정한다
|
||
* (2026-08-21 사용자 지시).
|
||
*/
|
||
export const FILL_SLOPE_RATIO_MIN = 1.2;
|
||
export const FILL_SLOPE_RATIO_MAX = 2.0;
|
||
|
||
/** 성토사면 길이 한계(m) — `성토_비탈면.md` §2 "5m 이내". 초과 시 옹벽·석축 등 의무. */
|
||
export const FILL_SLOPE_MAX_LENGTH_M = 5.0;
|
||
|
||
/**
|
||
* 배관 기슭막이 기본 자리 — 노견에서 **성토사면 경사길이 4m 지점**(2026-08-23 사용자
|
||
* 확정). 상하(d) 조작의 0점은 성토선 길이 0 지점(벽 상단 = 노견)이고, d는 그 점에서
|
||
* 1:1.2 성토선을 타고 내려간 수평 성분(m)이다. 기본 자리가 안 되는 지형(매몰·역경사·
|
||
* 토피)은 배치 풀이의 기존 한계 논리가 안쪽으로 되돌린다.
|
||
*/
|
||
export const PIPE_WALL_DEFAULT_SLOPE_M = 4.0;
|
||
/** 기본 자리(경사길이 4m)의 수평 성분(m) — d 기본값. */
|
||
export const PIPE_WALL_DEFAULT_RUN_M =
|
||
(PIPE_WALL_DEFAULT_SLOPE_M * FILL_SLOPE_RATIO_MIN) / Math.hypot(1, FILL_SLOPE_RATIO_MIN);
|
||
|
||
/**
|
||
* 성토 전체고(노견 − 원지반) 기준(m) — 이 높이를 넘으면 1:1.2로도 사면이 5m를 넘어
|
||
* 구조물이 필요해진다(3m × √(1+1.2²) = 4.69m, 3.2m에서 5.0m). 기슭막이를 자연 사면
|
||
* 끝이 아니라 **사면길이 5m 지점**으로 끌어올리는 판정에 쓴다(2026-08-21 사용자 지시).
|
||
*/
|
||
export const FILL_STRUCTURE_HEIGHT_M = 3.0;
|
||
|
||
/**
|
||
* 성토 사면이 성립하는 **최소 성토고**(m) — 노견 − 벽 이음선 상단.
|
||
*
|
||
* 이보다 낮은 자리에 벽을 세우면 사면선이 노견에서 벽 상단으로 **올라가** 역전된다
|
||
* (2026-08-21 사용자 지적). 자동 배치는 물매 조건이 이미 막지만, 조정창으로 손수
|
||
* 미는 경우는 이 하한으로 막는다.
|
||
*/
|
||
export const FILL_MIN_RISE_M = 0.3;
|
||
|
||
/**
|
||
* 집수정 바닥 여유(m) — **관 하단 아래**로 두는 깊이. 상부 여유는 기슭막이와 같은
|
||
* `REVET_FREEBOARD_M`(0.5)을 쓴다. 집수정 깊이 = 바닥 여유 + 관경 + 상부 여유
|
||
* (2026-08-21 사용자 확정 — 종전에는 성토 경사선과의 교점으로 높이를 잡았다).
|
||
*/
|
||
export const BASIN_BOTTOM_CLEARANCE_M = 0.5;
|
||
|
||
/**
|
||
* 집수정 내부 높이(m) — 바닥판이 있는 ㄴ(L)·ㄷ형의 **바닥 윗면~벽 상단** 깊이.
|
||
* 프로젝트 기본값 1.2m(2026-08-22 사용자 확정). 외부 기준은 확인되지 않았다.
|
||
* I형(공유벽뿐)은 이 값을 쓰지 않고 노견 끝 접점까지 벽을 키운다.
|
||
*/
|
||
export const BASIN_INNER_HEIGHT_M = 1.2;
|
||
|
||
/**
|
||
* 집수정 전환 기준 성토사면 길이(m) — 유입측 성토사면이 이 길이 **이하**면 기슭막이
|
||
* +배관을 두기 어려워 집수정이 기본이 된다(2026-08-22 사용자 확정). 절토측 유입은
|
||
* 성토사면이 없으므로 자동 포함. 사용자 선택(기슭막이 유지)은 향후 추가 예정.
|
||
*/
|
||
export const BASIN_MAX_FILL_SLOPE_M = 3.0;
|
||
|
||
/** 집수정 부재(벽·바닥판) 두께(m) — 2026-08-22 사용자 지정 0.2. */
|
||
export const BASIN_MEMBER_THICKNESS_M = 0.2;
|
||
|
||
/* ---------------------------------------------------------------------------
|
||
* 구조물의 종방향 점유 구간(기준측점 전/후 m)
|
||
*
|
||
* 기슭막이 연장(길이·전/후)과 집수정 연장을 **한 곳에서** 정한다 — B06 링크 판정
|
||
* (`_Cross_Culvert_Wire.ts`)과 3D 스윕(`B05_Profile_UI_Corridor_Structures.ts`)이
|
||
* 서로 다른 값을 쓰면 2D와 3D가 어긋난다(2026-08-24).
|
||
* ------------------------------------------------------------------------- */
|
||
|
||
/** 기준측점 전/후 몫(m). */
|
||
export interface StructureSpan {
|
||
beforeM: number;
|
||
afterM: number;
|
||
}
|
||
|
||
/** 길이 + 전/후 저장분에서 구간을 확정 — 전/후가 없으면 길이 절반씩. */
|
||
function spanFrom(
|
||
lengthM: number,
|
||
beforeM: number | null | undefined,
|
||
afterM: number | null | undefined,
|
||
): StructureSpan {
|
||
const before = Math.max(beforeM ?? lengthM / 2, 0);
|
||
const after = Math.max(afterM ?? Math.max(lengthM - before, 0), 0);
|
||
return { beforeM: before, afterM: after };
|
||
}
|
||
|
||
/** 기슭막이 한 벽의 종방향 구간 — 기본 10m(전 5·후 5). */
|
||
export function revetSpanOfSpec(spec: CulvertSideSpec | undefined): StructureSpan {
|
||
return spanFrom(spec?.revet_length_m ?? 10, spec?.revet_before_m, spec?.revet_after_m);
|
||
}
|
||
|
||
/** 집수정의 종방향 구간 — 기본 길이 2m, 전/후 각 1m(2026-08-24 사용자). */
|
||
export function basinSpanOfSpec(spec: CulvertSideSpec | undefined): StructureSpan {
|
||
const length = Math.max(spec?.basin_length_m ?? 2, 0.1);
|
||
return spanFrom(length, spec?.basin_before_m, spec?.basin_after_m);
|
||
}
|
||
|
||
/** 역할별 옵션 키 — 배수관 레지스트리 옵션 이름 그대로다. */
|
||
export const SPAN_OPTION_KEYS: Record<SpanRole, { length: string; before: string; after: string }> =
|
||
{
|
||
inlet: {
|
||
length: "inlet_revet_length_m",
|
||
before: "inlet_revet_before_m",
|
||
after: "inlet_revet_after_m",
|
||
},
|
||
outlet: {
|
||
length: "outlet_revet_length_m",
|
||
before: "outlet_revet_before_m",
|
||
after: "outlet_revet_after_m",
|
||
},
|
||
basin: {
|
||
length: "inlet_basin_length_m",
|
||
before: "inlet_basin_before_m",
|
||
after: "inlet_basin_after_m",
|
||
},
|
||
};
|
||
|
||
/**
|
||
* 다단(추가) 기슭막이 한 단의 **기본 구간값** — 고정 10m(전 5·후 5, 2026-08-29 사용자
|
||
* 확정). 기준벽 연장을 상속하지 않는다: 계곡부·능선부에서 아래 단일수록 연장이 달라져
|
||
* 자동 규칙으로 못 잡고 사용자가 손으로 넣는다.
|
||
*/
|
||
export const EXTRA_SPAN_DEFAULT: SpanValues = { lengthM: 10, beforeM: 5, afterM: 5 };
|
||
|
||
/**
|
||
* 구간값 한 벌에 조작을 얹는다 — 기준벽·다단이 같은 산식을 쓴다.
|
||
* 길이를 바꾸면 **늘어난 몫만** 지금 비율대로 나눠 담는다(매번 총길이에서 비율로 다시
|
||
* 계산하면 0.1m 반올림이 쌓여 5.0/5.0이 5.6/5.4로 어긋난다 — 2026-08-24 화면 실측).
|
||
* 전·후를 바꾸면 길이는 둘의 합이다.
|
||
*/
|
||
export function applySpanPatch(current: SpanValues, patch: Partial<SpanValues>): SpanValues {
|
||
let { beforeM, afterM } = current;
|
||
if (patch.lengthM !== undefined) {
|
||
const total = Math.max(patch.lengthM, 0);
|
||
const delta = total - current.lengthM;
|
||
const ratio = current.lengthM > 1e-9 ? current.beforeM / current.lengthM : 0.5;
|
||
beforeM = Math.max(Math.round((current.beforeM + delta * ratio) * 10) / 10, 0);
|
||
afterM = Math.max(Math.round((total - beforeM) * 10) / 10, 0);
|
||
}
|
||
if (patch.beforeM !== undefined) beforeM = Math.max(patch.beforeM, 0);
|
||
if (patch.afterM !== undefined) afterM = Math.max(patch.afterM, 0);
|
||
return { lengthM: Math.round((beforeM + afterM) * 10) / 10, beforeM, afterM };
|
||
}
|
||
|
||
/** 소유 측점의 구간값(길이·전/후) — 저장 옵션과 기본값을 함께 푼 결과. */
|
||
export const spanValuesOf = (owner: CrossSection, role: SpanRole): SpanValues | null => {
|
||
const spec = role === "outlet" ? owner.culvert?.outlet : owner.culvert?.inlet;
|
||
if (!spec) return null;
|
||
const span = role === "basin" ? basinSpanOfSpec(spec) : revetSpanOfSpec(spec);
|
||
return {
|
||
lengthM: Math.round((span.beforeM + span.afterM) * 10) / 10,
|
||
beforeM: Math.round(span.beforeM * 10) / 10,
|
||
afterM: Math.round(span.afterM * 10) / 10,
|
||
};
|
||
};
|