main_laptop_1 -> main byeonghap (4 hwangyeong 585 commits) #12
@@ -13,8 +13,36 @@ import {
|
||||
buildRockBoundaryControl,
|
||||
sectionModeLabel,
|
||||
} from "./B06_Section_UI_Cross_Design";
|
||||
import { type FillSlopeLength, fillSlopeLengths } from "./B06_Section_UI_Cross_Fit";
|
||||
import { type DesignChangeHandler, L, stationLabel } 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 카드 제목행을 만들어 카드에 붙인다(설계 조작이 있으면 조작 바까지).
|
||||
*
|
||||
@@ -67,6 +95,10 @@ export function appendCardHeader(
|
||||
);
|
||||
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");
|
||||
|
||||
@@ -58,6 +58,58 @@ export function toeFitHalfWidth(section: CrossSection): number | null {
|
||||
return Math.min(Math.ceil(extent + 1), MAX_FIT_HALF_WIDTH_M);
|
||||
}
|
||||
|
||||
/**
|
||||
* 성토측 사면의 **본래** 경사길이(m) — 성토가 아닌 측은 null (2026-09-03 사용자 지시).
|
||||
*
|
||||
* 「본래」 = 구조물(기슭막이·집수정)이 사면을 끊기 전 표준 횡단 기준. 구조물은 프론트
|
||||
* 오버레이라 백엔드 `design_line`을 바꾸지 않으므로, 그 선으로 재면 곧 본래 길이다.
|
||||
* 구간은 노견 끝(그 측에 측구가 있으면 측구 바깥)부터 설계선이 원지반과 처음 만나는
|
||||
* 점까지이고, 그 사이 성토선은 1:n 직선이라 수평 성분만으로 경사길이가 나온다.
|
||||
*/
|
||||
export interface FillSlopeLength {
|
||||
/** 사면 경사길이(m). 미교차(`open`)면 계산 반폭까지의 **하한값**이다. */
|
||||
lengthM: number;
|
||||
/** 설계선 끝(계산 반폭)까지 원지반과 만나지 못한 사면 — 길이를 다 재지 못했다. */
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export function fillSlopeLengths(section: CrossSection): {
|
||||
left: FillSlopeLength | null;
|
||||
right: FillSlopeLength | null;
|
||||
} {
|
||||
const lengths: { left: FillSlopeLength | null; right: FillSlopeLength | null } = {
|
||||
left: null,
|
||||
right: null,
|
||||
};
|
||||
const design = section.design;
|
||||
if (!design) return lengths;
|
||||
const groundAt = groundInterpolator(section.samples);
|
||||
const designAt = designInterpolator(design.design_line);
|
||||
const edges = design.road_edges;
|
||||
if (!groundAt || !designAt || !edges) return lengths;
|
||||
const lineOffsets = design.design_line.map((point) => point.offset_m);
|
||||
if (lineOffsets.length < 2) return lengths;
|
||||
const { protectMax, protectMin } = protectedSpan(design, edges);
|
||||
const slant = Math.hypot(1, 1 / Math.max(design.fill_slope_ratio, 1e-6));
|
||||
for (const side of ["left", "right"] as const) {
|
||||
const mode = design.section_mode;
|
||||
if (mode === "both_cut" || mode === `${side}_cut`) continue;
|
||||
const outward = side === "left" ? 1 : -1;
|
||||
const start = side === "left" ? protectMax : protectMin;
|
||||
const limit = side === "left" ? Math.max(...lineOffsets) : Math.min(...lineOffsets);
|
||||
// 미교차 측점은 **재지 못한 것**이라 계산 반폭까지의 하한값만 준다 — `toeFitHalfWidth`가
|
||||
// 쓰는 추세 외삽은 지반이 사면과 거의 나란한 자리에서 수십 m씩 튀어 길이 표기에는 못 쓴다
|
||||
// (2026-09-03 실측: 740m 측점 좌측 외삽 101.53m, 실제 반폭 20m까지 고저차 7.16m 유지).
|
||||
const meet = meetOffset(designAt, groundAt, start, limit, outward);
|
||||
const end = Math.abs(meet) > Math.abs(limit) ? limit : meet;
|
||||
lengths[side] = {
|
||||
lengthM: Math.abs(end - start) * slant,
|
||||
open: Math.abs(designAt(end) - groundAt(end)) > MEET_TOLERANCE_M,
|
||||
};
|
||||
}
|
||||
return lengths;
|
||||
}
|
||||
|
||||
/** 노면·노견(+측구) 구간의 바깥 경계 — 교차점 탐색은 여기서부터 바깥으로 간다. */
|
||||
function protectedSpan(
|
||||
design: CrossDesign,
|
||||
@@ -98,7 +150,12 @@ function meetOffset(
|
||||
const offset = startOffset + outward * Math.min(SCAN_STEP_M * index, span);
|
||||
const diff = designAt(offset) - groundAt(offset);
|
||||
if (Math.abs(diff) <= MEET_TOLERANCE_M) return offset;
|
||||
if (previousDiff !== 0 && Math.sign(diff) !== Math.sign(previousDiff)) return offset;
|
||||
if (previousDiff !== 0 && Math.sign(diff) !== Math.sign(previousDiff)) {
|
||||
// 두 걸음 사이를 선형보간한다 — 반폭 맞춤에는 영향이 없지만(1m 올림), 같은 교차점을
|
||||
// 길이로 읽는 성토사면 표기가 탐색 간격(0.1m)만큼 튀는 것을 막는다(2026-09-03).
|
||||
const previous = offset - outward * SCAN_STEP_M;
|
||||
return previous + (offset - previous) * (previousDiff / (previousDiff - diff));
|
||||
}
|
||||
previousDiff = diff;
|
||||
}
|
||||
return extrapolateMeet(designAt, groundAt, limitOffset, outward);
|
||||
|
||||
@@ -238,6 +238,12 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 성토사면 경사길이 — 제목행 우측 끝(2026-09-03). meta 의 마지막 칸이라 우측 맞춤이다. */
|
||||
.b06-cross-card__fillslope {
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 지반유형 세그먼트(D-6): 제목행 1행 내 인라인 배치. 좌측 구분선(3번). */
|
||||
.b06-design__seg--header {
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -348,6 +348,12 @@ export const ui_locales_b2 = {
|
||||
"사면이 계산 반폭 끝까지 원지반과 만나지 않아 절·성토 면적이 그 자리에서 잘렸습니다. 유토곡선·수량도 잘린 값을 씁니다.",
|
||||
"The slope never meets existing ground within the computed half-width, so the cut/fill area is truncated there. The mass haul curve and quantities use the truncated value.",
|
||||
],
|
||||
/* 성토사면 경사길이 — 구조물이 사면을 끊기 전 **본래** 길이(2026-09-03 사용자 지시). */
|
||||
B06_Cross_FillSlope: ["성토사면", "Fill slope"],
|
||||
B06_Cross_FillSlope_Tip: [
|
||||
"노견 끝(그 측에 측구가 있으면 측구 바깥)부터 성토 사면이 원지반과 만나는 곳까지의 경사길이입니다. 기슭막이·집수정이 사면을 끊기 전 본래 길이이며, 양측 성토면 좌·우를 함께 적습니다. 5m를 넘으면 옹벽·석축 등이 필요합니다(성토_비탈면 §2). 「≥」는 사면이 계산 반폭 안에서 원지반과 만나지 못해 거기까지만 잰 하한값입니다.",
|
||||
"Slant length of the fill slope, from the shoulder edge (outside the ditch if that side has one) to where it meets existing ground. It is the original slope before any revetment or catch basin cuts it short, and both sides are shown when the section is filled on both. Over 5 m a retaining structure is required. A leading ≥ marks a lower bound: the slope never meets ground within the computed half-width, so only that much could be measured.",
|
||||
],
|
||||
B06_MassHaul_CutNatural: ["절토(자연)", "Cut (natural)"],
|
||||
B06_MassHaul_CutCompacted: ["절토(다짐환산)", "Cut (compacted)"],
|
||||
B06_MassHaul_Fill: ["성토", "Fill"],
|
||||
|
||||
Reference in New Issue
Block a user