- 새 logic/B05_Profile_PlanContext.ts — 흐릴 세트(내 것 빼고) · 이음점(남 세트 끝) · 받을 때 이어진 이음점 · 벗어남 안내 · 이름표 자리 - 새 B05_Profile_UI_PlanContext.ts — plan-context 실시간 읽기 · 옅은 선 + 세트 이름표 · 누름 칸 없음(보기만) - 키트 · 긋기 · 고르기 snaps 덧칸 — 긋기 · 노드 끌기가 이음점에 붙기만 - 기능 표 others 줄(B05 보기만) - 시험 test_635_4(6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_PlanContext.ts
|
|
* B05 흐린 다른 세트 셈(PLAN 635-4 · 요구 546 · 547 · 결정 5 · 6 · 7) — 순수 함수.
|
|
* 입력 = `GET /api/projects/{B}/plan-context`(같은 예정노선 세트 모두 · B 좌표 · 업체 이름 없음).
|
|
* 흐릴 세트 = 내 세트 아님 · 이음점 = 남 세트 꺾은선 끝 · 받을 때 이어진 이음점 = 내 받은 선 끝 1 m 안.
|
|
* 이음은 고정 안 함 — 벗어나면 안내 글 한 줄(판단 = 설계자).
|
|
* ========================================================================== */
|
|
|
|
import { lineLength, pointAtDistance } from "../../D03_PlanAnalysis/logic/D03_PlanAnalysis_Draw";
|
|
|
|
export type Vertex = [number, number];
|
|
|
|
/** plan-context 세트 한 줄(635-2 꼴). */
|
|
export interface ContextSet {
|
|
key: string;
|
|
year: number;
|
|
no: number;
|
|
mine: boolean;
|
|
parts: number;
|
|
branch: boolean;
|
|
length_m: number;
|
|
lines: Vertex[][];
|
|
}
|
|
|
|
export interface PlanContext {
|
|
linked: boolean;
|
|
plan_name?: string;
|
|
mine?: string | null;
|
|
sets?: ContextSet[];
|
|
}
|
|
|
|
/** 이음점 · 끝 맞음 거리(m). */
|
|
export const JOINT_TOL_M = 1;
|
|
|
|
export interface Joint {
|
|
key: string;
|
|
point: Vertex;
|
|
}
|
|
|
|
const dist = (a: Vertex, b: Vertex): number => Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
const drawn = (line: Vertex[]): boolean => line.length > 1;
|
|
const ends = (line: Vertex[]): Vertex[] => [line[0], line[line.length - 1]];
|
|
|
|
/** 흐릴 세트 — 내 세트 아님 · 그릴 선 있음. */
|
|
export function otherSets(context: PlanContext | null): ContextSet[] {
|
|
if (!context?.linked) return [];
|
|
return (context.sets ?? []).filter((set) => !set.mine && set.lines.some(drawn));
|
|
}
|
|
|
|
/** 남 세트 꺾은선 끝 모두(붙을 자리). */
|
|
export function jointPoints(context: PlanContext | null): Joint[] {
|
|
return otherSets(context).flatMap((set) =>
|
|
set.lines.filter(drawn).flatMap((line) => ends(line).map((point) => ({ key: set.key, point }))),
|
|
);
|
|
}
|
|
|
|
/** 받을 때 이어진 이음점 — 남 세트 끝 중 내 받은 선 끝 1 m 안. */
|
|
export function linkedJoints(context: PlanContext | null): Joint[] {
|
|
const mine = (context?.sets ?? []).find((set) => set.mine);
|
|
const tips = (mine?.lines ?? []).filter(drawn).flatMap(ends);
|
|
return jointPoints(context).filter((joint) =>
|
|
tips.some((tip) => dist(tip, joint.point) <= JOINT_TOL_M),
|
|
);
|
|
}
|
|
|
|
/** 이어졌던 이음점마다 지금 선 끝까지 거리 — 1 m 밖만 · 선 없음 = 빈. */
|
|
export function jointGaps(
|
|
line: ReadonlyArray<Vertex>,
|
|
joints: ReadonlyArray<Joint>,
|
|
): Array<{ key: string; gapM: number }> {
|
|
if (line.length < 2) return [];
|
|
const tips = ends(line as Vertex[]);
|
|
return joints
|
|
.map((joint) => ({
|
|
key: joint.key,
|
|
gapM: Math.min(...tips.map((tip) => dist(tip, joint.point))),
|
|
}))
|
|
.filter((gap) => gap.gapM > JOINT_TOL_M);
|
|
}
|
|
|
|
/** 벗어남 안내 한 줄(없음 = null). */
|
|
export function jointNote(gaps: ReadonlyArray<{ key: string; gapM: number }>): string | null {
|
|
if (!gaps.length) return null;
|
|
const list = gaps.map((gap) => `${gap.key} ${gap.gapM.toFixed(1)} m`).join(" · ");
|
|
return `이웃 세트 이음점에서 벗어남(${list}) — 이음은 고정이 아님 · 끝을 이음점에 놓으면 붙습니다.`;
|
|
}
|
|
|
|
/** 세트 이름표 자리 — 가장 긴 꺾은선 가운데. */
|
|
export function labelPoint(set: ContextSet): Vertex | null {
|
|
const line = set.lines.filter(drawn).sort((a, b) => lineLength(b) - lineLength(a))[0];
|
|
return line ? pointAtDistance(line, lineLength(line) / 2) : null;
|
|
}
|