PLAN 2026-08-17 「B05 구조물 컨테이너 병합」 프론트엔드.
- B05_Profile_Util_Station.ts 신설: 측점번호+잔여거리("3+18.0") ↔ 누가거리
변환·서식·해석. 누가거리 직접 입력도 허용, 부동소수 올림 보정.
- 구조물 패널: 위치 입력을 측점 표기 텍스트로 재편. 점형 = 기준 측점 1칸,
구간형 = 기준점(비우면 시작)+시작+종료 3칸, 시작≤기준≤종료 검증과 잘못된
입력 붉힘. 목록 표기도 측점식. 구간형 마크 드래그는 기준점 기준으로 시작·
종료가 따라온다. 상세(detail) 옵션은 폼에서 숨긴다 — B05는 유무·종류·위치
단계(2026-08-17 사용자 확정).
- 우클릭 메뉴 필터 완화: managed_by와 b05 phase 필수만 제외 — 상세 필수였던
타입(옹벽·돌쌓기 등)도 우클릭 한 번으로 추가된다.
- B05_Profile_UI_Drainage_Facility.ts 신설(패널 700줄 제한 대응 분리): 관 마커
선택 시 시설 종류(배관/BOX암거/물넘이/세월교)·구간·세월교 관 종류/크기/수량
폼. FacilityStore가 시설 확장 정보를 보관하고 세부유역 재계산·저장 요청에
되붙인다(관 이동 후에도 최근접 승계 — 백엔드 carry와 같은 기준). 응답이
정본이라 apply()에서 전량 재구성.
- B04_PreProcess_Api_Fetch.ts: PipeFacility·DetailPipeInput 타입, 요청·응답에
시설 필드 반영.
npm run typecheck·npm run build 통과. 통합 서클마크 표시와 구 비정규 측점
이관·폐기(3단계)는 배관 측점선 체계 이전과 묶어 다음 작업 — PLAN.md 체크리스트
에 미완 사유 기록.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
55 lines
2.6 KiB
TypeScript
55 lines
2.6 KiB
TypeScript
/* =============================================================================
|
||
* B05_Profile_Util_Station.ts
|
||
* 측점 표기 공용 유틸 — 측점번호+잔여거리 ↔ 누가거리(chainage) 변환·서식.
|
||
*
|
||
* 구조물 위치는 저장은 누가거리(m)로 하되 **화면 입출력은 측점번호+잔여거리**로 한다
|
||
* (2026-08-17 사용자 확정). 근거: 중심선측량 측점 20m + 구조물 지점 보조말뚝
|
||
* (지식DB 설계제원_총괄 §8). 표기 예: "3+18.0" = 측점 3번에서 18.0m.
|
||
*
|
||
* 변환식은 비정규 측점 UI(`B05_Profile_UI_IrregularStations.ts`)와 같다 —
|
||
* chainage_m = 측점번호 × 측점간격(m) + 잔여거리(m). 측점간격은 호출 시점에 받는다
|
||
* (프로젝트 설정에 따라 바뀌므로 상수로 굳히지 않는다).
|
||
* ========================================================================== */
|
||
|
||
/** 측점번호 X + 잔여거리 XX(m) → 누가거리(m). */
|
||
export function stationToChainage(station: number, remainder: number, intervalM: number): number {
|
||
return station * intervalM + remainder;
|
||
}
|
||
|
||
/** 누가거리(m) → 측점번호 + 잔여거리. 잔여거리는 [0, 간격) 범위로 정규화한다. */
|
||
export function chainageToStation(
|
||
chainageM: number,
|
||
intervalM: number,
|
||
): { station: number; remainder: number } {
|
||
if (!(intervalM > 0)) return { station: 0, remainder: Math.max(0, chainageM) };
|
||
const clamped = Math.max(0, chainageM);
|
||
let station = Math.floor(clamped / intervalM);
|
||
let remainder = clamped - station * intervalM;
|
||
// 부동소수 잔여가 간격과 사실상 같으면 다음 측점의 0으로 올린다 (19.999… → 1+0.0).
|
||
if (intervalM - remainder < 0.005) {
|
||
station += 1;
|
||
remainder = 0;
|
||
}
|
||
return { station, remainder };
|
||
}
|
||
|
||
/** 누가거리 → "3+18.0" 표기. */
|
||
export function formatStation(chainageM: number, intervalM: number): string {
|
||
const { station, remainder } = chainageToStation(chainageM, intervalM);
|
||
return `${station}+${remainder.toFixed(1)}`;
|
||
}
|
||
|
||
/** "3+18.0" · "3 + 18" · "76.5"(누가거리 직접) 입력을 누가거리로 해석한다.
|
||
* 해석 불가하면 null — 호출부가 칸을 붉히고 진행을 멈춘다. */
|
||
export function parseStationText(text: string, intervalM: number): number | null {
|
||
const trimmed = text.trim();
|
||
if (!trimmed) return null;
|
||
const match = /^(\d+)\s*\+\s*(\d+(?:\.\d+)?)$/.exec(trimmed);
|
||
if (match) {
|
||
const chainage = stationToChainage(Number(match[1]), Number(match[2]), intervalM);
|
||
return Number.isFinite(chainage) ? chainage : null;
|
||
}
|
||
const direct = Number(trimmed);
|
||
return Number.isFinite(direct) && direct >= 0 ? direct : null;
|
||
}
|