diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts
index 6f728f9da..012187fb4 100644
--- a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts
+++ b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts
@@ -43,6 +43,7 @@ import {
type WeightKey,
} from "./B05_Profile_UI_RouteEdit_Initial_Model";
import { createRecommendMap, RECOMMEND_MAP_HTML } from "./B05_Profile_UI_RouteEdit_Recommend_Map";
+import { recommendHelpHtml, syncRecommendHelp } from "./B05_Profile_UI_RouteEdit_Recommend_Help";
import "./B05_Profile_UI_Style_RouteRecommend.css";
type Vertex = [number, number];
@@ -225,7 +226,8 @@ export function openRecommendModal(params: RecommendParams): void {
- `;
+
+ ${recommendHelpHtml()}`;
const q = (sel: string): T => body.querySelector(sel)!;
map = createRecommendMap(q(".b05-routerec__mapwrap"));
@@ -342,6 +344,7 @@ export function openRecommendModal(params: RecommendParams): void {
? criteriaLine(preview.criteria, params.planMinRadiusM, preview.logic === "gradeline")
: "";
line("criteria").hidden = !preview;
+ syncRecommendHelp(body, logic);
drawMap();
}
diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Help.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Help.ts
new file mode 100644
index 000000000..4f91bb598
--- /dev/null
+++ b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Help.ts
@@ -0,0 +1,188 @@
+/* =============================================================================
+ * B05_Profile_UI_RouteEdit_Recommend_Help.ts
+ * [노선 추천] 모달 오른쪽 설명 칸(계획서 48-2) — 갈래 여덟 · 공통 제약 넷 · 결과 줄 읽는 법.
+ *
+ * 글은 서버 엔진(`B05_Profile_Engine_RouteInitial*`)이 실제로 하는 일을 따라 적는다 — 엔진을
+ * 바꾸면 여기 글도 같이 고친다. 고른 갈래는 강조하고 보이는 자리로 굴린다.
+ * ========================================================================== */
+
+import type { RouteInitialLogic } from "./B05_Profile_Api_RouteInitial";
+import { LOGIC_LABEL } from "./B05_Profile_UI_RouteEdit_Initial_Model";
+
+interface HelpEntry {
+ /** 한 줄 요약. */
+ summary: string;
+ /** 노선을 만드는 법. */
+ how: string[];
+ /** 이럴 때 고름. */
+ when: string;
+ /** 주의할 점. */
+ caution: string;
+}
+
+const LOGIC_HELP: Record = {
+ follow: {
+ summary: "전처리 때 만든 예상노선(점선)을 그대로 따라가는 노선",
+ how: [
+ "새로 길을 찾지 않음 — 예상노선 꺾임점에 최소 곡선반지름으로 곡선만 넣음",
+ "전처리 초기 계획노선과 같은 규칙이라 같은 선이 나옴",
+ "종단기울기 · 계류 이격은 막지 않고, 넘은 곳만 「못 지킨 구간」에 적음",
+ ],
+ when: "현장 답사로 정한 노선을 출발점으로 삼을 때 · 가장 빨리 나옴",
+ caution: "법정 기울기 · 계류 이격을 보장하지 않음 — 못 지킨 구간을 꼭 확인",
+ },
+ gradeline: {
+ summary: "정한 기울기로 등고선을 비스듬히 건너는 등경사선(영선) 노선",
+ how: [
+ "구간마다 기울기가 「목표 기울기」에 가장 가깝게 붙도록 길을 찾음",
+ "목표 칸을 비우면 종단기울기 상한이 목표 · 상한보다 크게 넣으면 상한으로 잘림",
+ "후보 중 목표와의 차(측점 20m 기울기 평균)가 가장 작은 것을 고름",
+ ],
+ when: "일정한 기울기로 오르내리는 노선이 필요할 때(지형도 위 영선 잡기와 같은 일)",
+ caution:
+ "오르막 · 내리막을 가리지 않음 · 목표를 비우면 상한에 붙은 가파른 노선이 되기 쉬움 — 목표를 상한보다 조금 낮게 넣기를 권함",
+ },
+ earthwork: {
+ summary: "깎고 쌓는 흙(절토 + 성토)이 가장 적은 노선",
+ how: [
+ "옆경사가 급한 곳 · 지반이 상한보다 가파른 곳을 비싸게 보고 길을 찾음",
+ "후보 중 절토 + 성토 어림 양이 가장 적은 것을 고름",
+ "흙 양 = 계획고를 상한 안으로 맞춘 선과 옆경사 단면(노폭 4m)으로 어림",
+ ],
+ when: "토공량 · 사토 처리를 줄여야 할 때",
+ caution: "비탈면 흙은 빠진 어림값 · 연장이 길어지는지 비교값으로 함께 볼 것",
+ },
+ gentle: {
+ summary: "종단기울기가 가장 순한 노선",
+ how: [
+ "기울기가 상한에 가까울수록 크게 벌점을 매겨 길을 찾음",
+ "후보 중 가장 가파른 구간의 기울기가 가장 낮은 것을 고름(같으면 평균 기울기)",
+ ],
+ when: "차량 주행 · 노면 침식이 걱정되는 노선",
+ caution: "거리 비용이 작아 돌아가는 긴 노선이 나올 수 있음 — 연장을 확인",
+ },
+ safe: {
+ summary: "급한 옆경사 · 계류 가까운 곳을 피하는 노선",
+ how: [
+ "옆경사 35°(약 70%) 이상 · 계류 이격 거리 안을 위험 구간으로 보고 피함",
+ "후보 중 위험 구간을 지나는 길이가 가장 짧은 것을 고름(같으면 평균 옆경사)",
+ "35° 기준 = 「35° 이상 전폭 절토」 기술 기준",
+ ],
+ when: "붕괴 · 유실 위험이 큰 급경사지 · 계곡 가까운 지형",
+ caution:
+ "계곡을 따라가는 지형에서는 위험 구간이 어디서나 길어 다른 갈래와 차이가 작음 — 계류 이격 값을 줄여 다시 셈해 볼 것",
+ },
+ crossings: {
+ summary: "계류를 건너는 횟수가 가장 적은 노선",
+ how: [
+ "계류 위를 지나는 칸에 벌점을 매겨 길을 찾음",
+ "후보 중 계류 횡단 수가 가장 적은 것을 고름(같으면 계류에서 더 먼 쪽)",
+ "10m 안에서 여러 번 건너는 것은 한 번으로 셈",
+ ],
+ when: "배수관 · 세월교 같은 횡단 구조물을 줄이고 싶을 때",
+ caution: "소하천 · 지방하천 · 국가하천만 셈 — 작은 세류는 빠짐",
+ },
+ cost: {
+ summary: "토공 · 연장 · 계류 횡단을 한 숫자로 묶은 공사비 어림이 가장 낮은 노선",
+ how: [
+ "공사비 어림 = 흙 1㎥ × 1 + 연장 1m × 2 + 계류 횡단 1곳 × 400(흙 ㎥ 상당)",
+ "같은 비율을 1m 당 값으로 바꿔 길을 찾고, 후보 중 어림값이 가장 낮은 것을 고름",
+ ],
+ when: "토공 · 연장 · 구조물 수를 한꺼번에 비교하고 싶을 때",
+ caution: "실제 원가가 아닌 상대값(임시 비율) — 갈래끼리 비교용으로만 볼 것",
+ },
+ weighted: {
+ summary: "예상 · 토공 · 기울기 세 비중(합 100)을 섞어 찾은 노선",
+ how: [
+ "예상 = 예상노선에서 떨어지지 않기 · 토공 = 흙 양 줄이기 · 기울기 = 순한 기울기",
+ "비중대로 섞은 비용 하나로 한 번 길을 찾음 — 한쪽이 100 이면 그 갈래와 같은 노선",
+ "자물쇠로 비중 하나를 고정하면 나머지 둘만 움직임 · 기본 50 / 25 / 25",
+ ],
+ when: "예상노선을 어느 정도 살리면서 토공 · 기울기도 챙기고 싶을 때",
+ caution: "제약은 벌점일 뿐이라 넘는 구간이 남을 수 있음 — 못 지킨 구간을 확인",
+ },
+};
+
+const CRITERIA_HELP: Array<{ name: string; text: string }> = [
+ {
+ name: "종단기울기 상한(%)",
+ text: "이보다 가파른 구간을 만들지 않음. 비우면 임도 종류 · 지형 · 설계속도 법정 값(20km/h 일반 9% · 특수 14%). 곡선을 넣은 뒤 넘으면 상한을 조금씩 낮춰 다시 찾음",
+ },
+ {
+ name: "최소 반지름(m)",
+ text: "꺾는 곳마다 이 반지름의 곡선이 들어갈 만큼 곧게 간 뒤에만 다시 꺾음. 비우면 설계속도 · 지형 법정 값(20km/h 15m · 12m)",
+ },
+ {
+ name: "계류 이격(m)",
+ text: "계류에서 이 거리 안으로는 따라 걷지 않고 건너거나 빠져나가는 방향으로만 움직임. 비우면 100m(타당성평가 「산지계류 100m 이내」 기준)",
+ },
+ {
+ name: "지반 고름 폭(m)",
+ text: "기울기를 잴 때 지형 잔굴곡을 고르는 폭. 클수록 매끈한 지반으로 봄. 비우면 5m",
+ },
+];
+
+const LINES_HELP: Array<{ name: string; text: string }> = [
+ {
+ name: "비교값",
+ text: "연장 · 기울기 최대 · 평균 · 옆경사 · 절토 · 성토 · 계류 횡단 · 공사비 어림 — 갈래를 바꿔 가며 견줌",
+ },
+ {
+ name: "못 지킨 구간",
+ text: "제약을 넘은 측점(5곳까지 · 나머지는 「외 N곳」, 전체는 마우스를 올리면 보임)",
+ },
+ {
+ name: "상한을 풂",
+ text: "지형상 상한 안으로는 시점 · 종점을 이을 수 없어 꼭 필요한 만큼만 상한을 올린 경우",
+ },
+ {
+ name: "거의 같은 노선",
+ text: "제약 안에서 갈 길이 좁은 띠 하나뿐이라 갈래가 달라도 노선이 겹치는 경우 — 제약 값을 바꾸면 벌어질 수 있음",
+ },
+];
+
+const LOGIC_ORDER: RouteInitialLogic[] = [
+ "follow",
+ "gradeline",
+ "earthwork",
+ "gentle",
+ "safe",
+ "crossings",
+ "cost",
+ "weighted",
+];
+
+/** 설명 칸 HTML — 모달 본문 끝에 붙는다(세로 구분선 오른쪽). */
+export function recommendHelpHtml(): string {
+ const logics = LOGIC_ORDER.map((key) => {
+ const entry = LOGIC_HELP[key];
+ return `
+