Files
Aislo/B05_Profile/B05_Profile_Util_Station.ts
T
eomsangdonandClaude Opus 5 deaf51778f feat(B05): 구조물 컨테이너 병합 2단계 — 측점 표기·기준점 입력·계곡 통과 시설 UI
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>
2026-08-17 09:06:19 +09:00

55 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* 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;
}