법정 위반 표시가 없던 유일한 자리였음(평면 곡선반경·성토사면 길이는 이미 있음). 절토 경사비는 표준 횡단면 설정에서 오는 입력값일 뿐이라 범위를 벗어나도 아무 표시가 없었음. - 기준 — 별표2(지식DB `01_임도/02_상세설계/절토_비탈면.md` §1): 경암 1:0.3~0.8 · 연암 1:0.5~1.2 · 토사 1:0.8~1.5. **작업임도는 규정 없음 → 검사 제외.** - 판정은 **구간별 경사**(`cut_slope_segments`)로 함. 소단이 서면 실효 경사가 완만해져 **위반이 사라진 것처럼** 보임(폭 1.0·간격 2 이면 1:1 이 1:1.71). 그 함정을 시험으로 못박음. - 표시는 **기존 방식 그대로** — 성토사면·미폐합 경고와 같은 자리·같은 클래스. 새 방식 안 만듦. - 지반유형(리핑암·발파암) → 별표2 줄 매핑은 법령 근거가 아니라 **프로그램 설정**이라 표준 횡단면 설정에서 **사용자가 고르게** 함(2026-09-07 사용자 확정). 기본값 리핑암 → 연암 · 발파암 → 경암. 표준단면과 함께 저장돼 이미 사용자 값 계통임. - 기준값은 서버가 컨텍스트로 내려보냄 — 화면에 상수를 복제하지 않음. 시험 7건 — 별표2 값 · 작업임도 제외 · 매핑이 기본값일 뿐 · 구간별 판정 · **소단이 있어도 위반이 안 사라짐** · 경계값 통과 · 카드에 같은 방식으로 붙는지. ⚠ 실화면 — 사용자 선택 칸은 섰음(리핑암 연암 · 발파암 경암). 다만 이 프로젝트 저장분 63측점에 `cut_slope_segments` 가 아직 없어(오늘 새로 생긴 키) 경고가 뜨는 것은 못 봄. 재계산이 한 번 돌면 채워짐 — 지금 경고 0건은 맞는 동작임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
164 lines
7.5 KiB
TypeScript
164 lines
7.5 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_Card_Chrome.ts
|
|
* 횡단 카드의 **껍데기**(제목행·하단 정보행)만 떼어 낸 조립기 (2026-09-03 · 700줄 제한).
|
|
*
|
|
* 카드 본체(`_UI_Cross_View`)는 SVG 도면과 조작을 맡고, 도면과 무관한 머리·꼬리는 여기서
|
|
* 만든다. 여기 있는 것은 전부 **읽어서 붙이는 값**이라 카드 상태(줌·선택)를 건드리지 않는다.
|
|
* ========================================================================== */
|
|
|
|
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
|
import type { RockBoundaryControl } from "./B06_Section_UI_Cross_Design";
|
|
import {
|
|
buildDesignControls,
|
|
buildRockBoundaryControl,
|
|
sectionModeLabel,
|
|
} from "./B06_Section_UI_Cross_Design";
|
|
import { limitLabel, violationsOf } from "./B06_Section_Cut_Slope_Check";
|
|
import { type FillSlopeLength, fillSlopeLengths } from "./B06_Section_UI_Cross_Fit";
|
|
import {
|
|
type DesignChangeHandler,
|
|
L,
|
|
stationLabel,
|
|
structureDisplayName,
|
|
} from "./B06_Section_UI_Section_Common";
|
|
|
|
/**
|
|
* 성토사면 경사길이 표기 칸 — 성토측이 없으면 null.
|
|
*
|
|
* 값은 **구조물이 끊기 전 본래 사면**(정본 설계선 기준)이라, 기슭막이·집수정이 선 측점도
|
|
* 표준 횡단의 사면 길이를 그대로 보여 준다(2026-09-03 사용자 지시).
|
|
*/
|
|
function fillSlopeInfo(section: CrossSection): HTMLElement | null {
|
|
const lengths = fillSlopeLengths(section);
|
|
const sides = (["left", "right"] as const).filter((side) => lengths[side] !== null);
|
|
if (!sides.length) return null;
|
|
const both = sides.length > 1;
|
|
const info = document.createElement("span");
|
|
info.className = "b06-cross-card__fillslope";
|
|
info.textContent = `${L("B06_Cross_FillSlope")} ${sides
|
|
.map((side) => {
|
|
const length = lengths[side] as FillSlopeLength;
|
|
// 계산 반폭 안에서 원지반을 못 만난 사면은 거기까지만 잰 하한값이라 「≥」로 구분한다.
|
|
const value = `${length.open ? "≥" : ""}${length.lengthM.toFixed(2)}m`;
|
|
if (!both) return value;
|
|
const label = L(side === "left" ? "B06_Design_Ditch_Left" : "B06_Design_Ditch_Right");
|
|
return `${label} ${value}`;
|
|
})
|
|
.join(" · ")}`;
|
|
info.title = L("B06_Cross_FillSlope_Tip");
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* 카드 제목행을 만들어 카드에 붙인다(설계 조작이 있으면 조작 바까지).
|
|
*
|
|
* 1행 구조(E-2/E-3): 측점 위치표기 → 지반유형 → 단면유형 pill → 구조물 → 측점정보.
|
|
*/
|
|
export function appendCardHeader(
|
|
card: HTMLElement,
|
|
section: CrossSection,
|
|
stationInterval: number,
|
|
onDesignChange?: DesignChangeHandler,
|
|
): void {
|
|
const header = document.createElement("header");
|
|
const title = document.createElement("div");
|
|
title.className = "b06-cross-card__title";
|
|
const label = document.createElement("strong");
|
|
label.textContent = stationLabel(section.chainage_m, stationInterval);
|
|
const chainage = document.createElement("span");
|
|
chainage.textContent = `${section.chainage_m.toFixed(1)}m`;
|
|
title.append(label, chainage);
|
|
|
|
// 단면유형 pill(자동 판정, 읽기 전용) — 구조물 pill과 동일 표기, 좌측 배치(E-3).
|
|
const modePill = document.createElement("span");
|
|
modePill.className = "b06-cross-card__mode";
|
|
modePill.textContent = sectionModeLabel(section.design?.section_mode);
|
|
modePill.title = L("B06_Design_Mode_Legend");
|
|
|
|
const meta = document.createElement("div");
|
|
meta.className = "b06-cross-card__meta";
|
|
// 사면이 계산 반폭 끝까지 원지반을 못 만난 측점 — 면적이 거기서 잘려 유토곡선까지
|
|
// 그 값을 쌓는다. 계산은 손대지 않고 사실만 알린다(2026-09-03 사용자 확정).
|
|
if (section.design?.slope_unclosed) {
|
|
const openSlope = document.createElement("span");
|
|
openSlope.className = "b06-cross-card__warning";
|
|
openSlope.textContent = `⚠ ${L("B06_Cross_SlopeUnclosed")}`;
|
|
openSlope.title = L("B06_Cross_SlopeUnclosed_Tip");
|
|
meta.append(openSlope);
|
|
}
|
|
// 절토 비탈 법정 기울기(별표2) 위반 — 성토사면·미폐합 경고와 같은 자리·같은 모양이다.
|
|
// 소단이 서면 실효 경사가 완만해져 위반이 사라진 것처럼 보이므로, 판정은 **구간별**
|
|
// 경사(`cut_slope_segments`)로 한다(2026-09-07).
|
|
const cutViolations = violationsOf(section);
|
|
if (cutViolations.length) {
|
|
const worst = cutViolations[0];
|
|
const badge = document.createElement("span");
|
|
badge.className = "b06-cross-card__warning";
|
|
badge.textContent = `⚠ 절토 1:${worst.ratio.toFixed(2)}`;
|
|
badge.title = cutViolations
|
|
.map(
|
|
(item) =>
|
|
`${item.side === "left" ? "좌" : "우"} 1:${item.ratio.toFixed(2)}` +
|
|
` — ${limitLabel(item.limitKey)} 법정 1:${item.limit.min}~${item.limit.max}` +
|
|
` 범위 밖(${item.kind === "steep" ? "너무 급함" : "너무 완만함"})` +
|
|
` · ${item.startOffsetM.toFixed(2)}~${item.endOffsetM.toFixed(2)}m`,
|
|
)
|
|
.join("\n");
|
|
meta.append(badge);
|
|
}
|
|
const structureName = section.structure;
|
|
if (structureName) {
|
|
const structure = document.createElement("span");
|
|
structure.className = "b06-cross-card__structure";
|
|
// 배수관은 관종·관경(`파형강관 D1200`)이 아니라 표시 이름 하나로 적는다
|
|
// (2026-09-03 사용자 지시). 관종·관경은 툴팁에 남긴다.
|
|
structure.textContent = structureDisplayName(structureName);
|
|
structure.title = `구조물: ${structureName}`;
|
|
meta.append(structure);
|
|
}
|
|
// 측점 종류는 시·종점(BP/EP)만 적는다 — "일반측점"은 대다수라 정보가 없다(2026-08-02 사용자 지시).
|
|
if (section.kind === "bp" || section.kind === "ep") {
|
|
const kind = document.createElement("span");
|
|
kind.textContent = L(
|
|
section.kind === "ep" ? "B06_Profile_View_Kind_EP" : "B06_Profile_View_Kind_BP",
|
|
);
|
|
meta.append(kind);
|
|
}
|
|
// 성토사면 경사길이는 **행 우측 끝**(2026-09-03 사용자 지시) — meta 가 제목행의 오른쪽
|
|
// 끝이므로 그 마지막 칸에 붙인다.
|
|
const fillSlope = fillSlopeInfo(section);
|
|
if (fillSlope) meta.append(fillSlope);
|
|
|
|
// 단면유형 pill은 지반유형 우측·우측 맞춤으로 배치(1번). 좌측 구분선은 지반유형 세그먼트에 준다(3번).
|
|
modePill.classList.add("b06-cross-card__mode--right");
|
|
header.append(title);
|
|
if (onDesignChange) {
|
|
const controls = buildDesignControls(section, onDesignChange);
|
|
controls.groundSegment.classList.add("b06-cross-card__ground");
|
|
header.append(controls.groundSegment, modePill, meta);
|
|
card.append(header, controls.bar);
|
|
return;
|
|
}
|
|
header.append(modePill, meta);
|
|
card.append(header);
|
|
}
|
|
|
|
/** 하단 정보행 — 중심고와 (암 지반일 때) 암 경계선 제어. */
|
|
export function appendCardFooter(
|
|
card: HTMLElement,
|
|
section: CrossSection,
|
|
rockBoundary?: RockBoundaryControl,
|
|
): void {
|
|
const footer = document.createElement("footer");
|
|
const center = document.createElement("span");
|
|
center.textContent = `${L("B06_Profile_View_CenterElevation")} ${section.center_z?.toFixed(2) ?? "-"}m`;
|
|
footer.append(center);
|
|
// 암 경계선 제어는 하단 정보 행 가운데(2026-08-02) — 암 지반만.
|
|
if (rockBoundary && section.design?.geometry_preset === "rock") {
|
|
const rockControl = buildRockBoundaryControl(section, rockBoundary);
|
|
rockControl.classList.add("b06-cross-card__rockb");
|
|
footer.append(rockControl);
|
|
}
|
|
card.append(footer);
|
|
}
|