- 겹 표(map_layer_list) = 그룹1 22 줄 표 차례 · title = 호버제목 · group(수계망도 · 도로 · 토지·건물 · 제한·재해) · pages · 숨김 표 걷음 · 도로300m 한 줄(「도로」 묶음 · 기능 남김) - 겹 줄(map_layer_bar) = 단독 넷 | 묶음 넷(여럿 고르기 메뉴 · 켠 수 배지 · 안 닫힘) | 그래픽 카드 · sync 가 단추 · 메뉴 줄 막힘 · 글을 맞춤(why = 페이지 까닭) - 도구 표(map_toolbar) = 그룹2 17 줄 title · mapToolGroups 가 글을 표로 맞춤 · 기본 단추 생성 · 지형 칩 막힘 「수치지형도/라이다 — 까닭」 - 띠 = 한 자료 · 겹 둘(GL fillEdge 거름 · 옛 캔버스 · 범례 줄 따로) · B05 띠 받기(D03 과 같은 서버 길 · bandCollection 공용) - D03 = 겹 줄 · 도구줄 한 행(createMapToolRow) · 222px 기댐 · 줄바꿈 CSS 걷음 · 측점 · 기울기 호버 덮어쓰기 걷음 · LAS 올리기 호버 걷음 - B05 = 세로 두 줄 → 한 행 · 호버 덮어쓰기 걷음(도구줄 · 1m 등고선 · 3D · 추천) · 겹 막힘 querySelector 덮어쓰기 → 공용 sync · 임도(업로드) 보임 + 「받은 임도 자료 없음」 막힘 · 스냅 간격 날 select → 고르기 메뉴(하나) - 켬끔 기억 열쇠 · 측점 · 기울기 처음 값 · 그래픽 카드 취향 열쇠 둘 그대로 - 낡은 시험 고침(568_1 · 622_2 · 624_2 · 624_3 · 553_4 · 625_draw · b05_routeedit_initial · 626_3 · 390_4 · 346_2 · 353_1 · 581_2 · 584_1 · 584_3) — 표 꼴 row() · 호버 글 = 표 · 한 행 · 고르기 메뉴로 바뀐 글을 잡음 · 새 지킴 634_2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_RecPreview.ts
|
|
* 평면 노선 추천 미리보기(PLAN 626-1 · 요구 530 · 결정 D1 · D7 · D8) — 평면 도구줄 아이콘 하나 + 아래 판
|
|
* (영선 · 토공 최소 · 기울기 최소 · 비중 계산 · 끔) · 하나만 보임 · 선만(위 캔버스 · 노선 아래 그림 · 집기 없음).
|
|
* 선 = 노선 추천 저장본(`B05_Profile/route_recommend/` · 지금 설계 지형 · 체인이 미리 셈) 그대로 — 다시 셈 · 쓰기 없음 ·
|
|
* 저장본 없으면(409) 알림 · 끔. 옛 추천 모달 · 좌측 「노선 추천」 칸 · [적용](`as_initial`) 은 걷음(서버 길 · 시험은 남김).
|
|
* 부르는 곳 = `_Chrome`(도구줄 `recommend` 칸) · `_RouteEdit`(그리기 `overStations` 앞).
|
|
* ========================================================================== */
|
|
|
|
import { createPickMenu } from "@ui/ui_template_context_menu";
|
|
import { showToast } from "@ui/ui_template_elements";
|
|
import { themeColor } from "@ui/ui_template_palette";
|
|
import type { AnalysisTerrain } from "./B05_Profile_Api_Replan";
|
|
import { fetchRouteInitial, type RouteInitialLogic } from "./B05_Profile_Api_RouteInitial";
|
|
import { LOGIC_LABEL } from "./B05_Profile_UI_RouteEdit_Initial_Model";
|
|
|
|
type Vertex = [number, number];
|
|
|
|
/** 드롭다운 넷(요구 530 · 「예상노선 추적」 = 초기값이라 뺌). */
|
|
const MODES = [
|
|
"gradeline",
|
|
"earthwork",
|
|
"gentle",
|
|
"weighted",
|
|
] as const satisfies readonly RouteInitialLogic[];
|
|
|
|
export interface RecPreview {
|
|
root: HTMLElement;
|
|
/** 노선 그리기 안(돌림 안) — 고른 선 하나. */
|
|
draw: (context: CanvasRenderingContext2D, toScreen: (vertex: Vertex) => [number, number]) => void;
|
|
/** 검증 창구 — 고른 갈래 · 선 점 수. */
|
|
state: () => { mode: string | null; points: number };
|
|
}
|
|
|
|
export function createRecPreview(opts: {
|
|
projectId: string;
|
|
/** 지금 설계 지형(저장본 고름 · D7). */
|
|
terrain: () => AnalysisTerrain;
|
|
redraw: () => void;
|
|
}): RecPreview {
|
|
const lines = new Map<string, Vertex[]>();
|
|
let shown: Vertex[] | null = null;
|
|
let turn = 0;
|
|
const menu = createPickMenu({
|
|
icon: "compass",
|
|
title: "추천노선", // 마우스 글 = 공용 표 호버제목(634-2 · `mapToolGroups` 가 맞춤)
|
|
items: MODES.map((mode) => ({ value: mode, label: LOGIC_LABEL[mode] })),
|
|
offLabel: "끔",
|
|
onPick: (mode) => void pick(mode as RouteInitialLogic | null),
|
|
});
|
|
menu.root.dataset.act = "recommend";
|
|
|
|
async function pick(mode: RouteInitialLogic | null): Promise<void> {
|
|
const mine = ++turn;
|
|
shown = null;
|
|
opts.redraw();
|
|
if (!mode) return;
|
|
const key = `${mode}|${opts.terrain()}`;
|
|
try {
|
|
if (!lines.has(key)) {
|
|
const body = (await fetchRouteInitial(opts.projectId, {
|
|
mode,
|
|
with_profile: true,
|
|
terrain: opts.terrain(),
|
|
})) as { planned?: number[][] };
|
|
const planned = (body.planned ?? []).map(([x, y]) => [x, y] as Vertex);
|
|
if (planned.length < 2) throw new Error("추천 선이 비었음");
|
|
lines.set(key, planned);
|
|
}
|
|
if (mine !== turn) return;
|
|
shown = lines.get(key)!;
|
|
opts.redraw();
|
|
} catch (error) {
|
|
if (mine !== turn) return;
|
|
menu.set(null);
|
|
const text = error instanceof Error ? error.message : String(error);
|
|
showToast(`추천 노선 「${LOGIC_LABEL[mode]}」 못 봄 — ${text}`, "info");
|
|
}
|
|
}
|
|
|
|
return {
|
|
root: menu.root,
|
|
draw(context, toScreen) {
|
|
if (!shown) return;
|
|
context.save();
|
|
context.setLineDash([10, 6]);
|
|
context.lineJoin = "round";
|
|
context.beginPath();
|
|
shown.forEach((vertex, index) => {
|
|
const [x, y] = toScreen(vertex);
|
|
if (index) context.lineTo(x, y);
|
|
else context.moveTo(x, y);
|
|
});
|
|
// 지도 위 그림 색(테마 안 따름) + 검은 테 — 위성 · 어두운 바탕 모두 보임
|
|
context.strokeStyle = "rgba(0, 0, 0, 0.6)";
|
|
context.lineWidth = 5;
|
|
context.stroke();
|
|
context.strokeStyle = themeColor("--draw-map-sketch", "#ffe94d");
|
|
context.lineWidth = 3;
|
|
context.stroke();
|
|
context.restore();
|
|
},
|
|
state: () => ({ mode: menu.value(), points: shown?.length ?? 0 }),
|
|
};
|
|
}
|