Files
Aislo/B05_Profile/B05_Profile_UI_Drainage_Spans.ts
eomsangdonandClaude Opus 5 f659aec8a1 feat(B05): 평면(지도)에도 구간형 구조물을 띠로 — 종단과 같은 자료
사용자 지시(2026-09-07). 계획서 3-6 마지막 항목 — 산마루측구·도수로·옹벽처럼 **구간**으로
놓이는 시설이 종단에는 띠로 보이는데 **평면(배수유역도)에는 아무 표시가 없었음**.

**「부품이 없어 새로 짜야 함」이 사실이 아니었음** — 계획선 표본(`strengthSamples`)이 **1m 간격**
이라 **배열 인덱스가 곧 누가거리**임(`resampleRoute`). 구간 → 화면 선은 그 토막을 잘라 굵게
긋기만 하면 됨. 새 파일 하나(88줄)로 끝남.

- `B05_Profile_UI_Drainage_Spans.ts` — 띠 그리기(`drawRouteSpans`)와 자료 만들기
  (`routeSpansFromStructures`). 구간형만 띠가 되고, 색은 **레지스트리 표시색**을 그대로 씀.
- 그리는 자리 — **계획선 바로 위, 강도 색칠·마커 아래**. 굵기 7px·투명도 0.45 라 계획선을
  덮지 않음. 이름은 안 적음(종단에 이미 있고, 지도 글자는 눈금·유역 번호와 겹침).
- 목록이 바뀌면 따라옴 — `setStructures`·`setStructureTypes` 가 띠를 다시 넘김.
- 저장분에 노선보다 긴 구간이 남아 있어도 잘라서 그리고, 시작=끝인 구간도 한 칸은 보임.

⚠ 프로필 패널이 이미 700줄을 넘겨 있어(843) **매핑 로직은 띠 모듈로 뺐음** — 패널에는 3줄만
남김(827줄). 그 파일의 초과는 이전부터임.

시험 5건 — 부품 존재 · 범위 밖 구간 안전 · **쌓는 순서**(계획선 → 띠 → 강도 색칠) ·
종단과 같은 자료에서 뽑는지 · 목록 변화가 따라오는지.

⚠ 실화면 확인은 아직임 — 공용 브라우저가 소단 무한루프(별건, `80db17d4` 로 고침) 로 멈춰 있어
사용자 새로고침 대기 중임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 20:14:09 +09:00

89 lines
3.9 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Drainage_Spans.ts
* 배수유역도(평면)에서 **구간형 구조물이 놓인 자리**를 계획선 위에 띠로 그린다.
*
* 왜 (계획서 3-6) — 산마루측구·도수로·옹벽처럼 **구간**으로 놓이는 시설은 종단에는 띠로
* 보이는데 평면에는 아무 표시가 없었다. 「노선 위에 임의 구간을 얹는 부품이 없다」가
* 그동안의 걸림돌이었는데, 계획선 표본(`strengthSamples`)이 **1m 간격이라 배열 인덱스가
* 곧 누가거리**여서 구간 → 화면 선은 잘라 붙이기만 하면 된다.
*
* 그리는 자리 — **계획선 바로 위, 강도 색칠·마커 아래**. 굵고 반투명해서 계획선을 덮지
* 않는다. 이름은 안 적는다(종단에 이미 있고, 지도에 글자를 얹으면 눈금·유역 번호와 겹친다).
* ========================================================================== */
import type { RoutePoint } from "../B04_PreProcess/B04_PreProcess_UI_RouteSamples";
import type { StructureInstance, StructureType } from "./B05_Profile_Api_Structures";
/** 계획선 위에 얹을 구간 하나 — 누가거리 두 값과 색. */
export interface RouteSpanBand {
startM: number;
endM: number;
/** 구조물 레지스트리의 표시색(`style.color`). */
color: string;
}
/** 띠 굵기(px) — 계획선(2px 안팎)보다 확실히 굵되 유역 채움을 가리지 않는 값. */
const BAND_WIDTH_PX = 7;
/**
* 구간 띠를 그린다. 표본이 없거나 구간이 비면 아무 것도 하지 않는다.
*
* 인덱스는 **누가거리(m)** 다 — 표본이 1m 간격이라 그렇다(`resampleRoute`). 범위를 벗어난
* 값은 잘라 쓰고, 한 점짜리 구간(시작=끝)은 짧은 토막으로라도 보이게 한 칸을 준다.
*/
export function drawRouteSpans(
context: CanvasRenderingContext2D,
samples: ReadonlyArray<RoutePoint>,
spans: ReadonlyArray<RouteSpanBand>,
toScreen: (x: number, y: number) => [number, number],
): void {
if (samples.length < 2 || spans.length === 0) return;
const last = samples.length - 1;
context.save();
context.lineCap = "round";
context.lineJoin = "round";
context.lineWidth = BAND_WIDTH_PX;
for (const span of spans) {
const from = Math.max(0, Math.min(last, Math.floor(Math.min(span.startM, span.endM))));
const to = Math.max(from + 1, Math.min(last, Math.ceil(Math.max(span.startM, span.endM))));
context.beginPath();
for (let index = from; index <= to; index += 1) {
const [x, y] = toScreen(samples[index].x, samples[index].y);
if (index === from) context.moveTo(x, y);
else context.lineTo(x, y);
}
context.strokeStyle = span.color;
context.globalAlpha = 0.45;
context.stroke();
context.globalAlpha = 1;
}
context.restore();
}
/**
* 구조물 정본 + 타입 레지스트리 → 띠 목록. **종단 레인과 같은 자료**를 본다(계획서 3-6).
*
* · 구간형(`interval`)만 띠가 된다 — 점형 시설은 마커로 이미 보인다.
* · 색은 레지스트리 표시색을 그대로 쓴다(여기서 새로 정하지 않는다).
* · 시작·끝이 없으면 기준 측점으로 대신한다. 그것도 없으면 뺀다.
*/
export function routeSpansFromStructures(
structures: ReadonlyArray<StructureInstance>,
types: ReadonlyArray<StructureType>,
): RouteSpanBand[] {
const colorOf = new Map(types.map((type) => [type.type_id, type.style?.color]));
const spans: RouteSpanBand[] = [];
for (const item of structures) {
if (item.placement !== "interval") continue;
const start = item.start_m ?? item.chainage_m ?? null;
const end = item.end_m ?? item.chainage_m ?? null;
if (start === null || end === null) continue;
spans.push({
startM: Math.min(start, end),
endM: Math.max(start, end),
color: colorOf.get(item.type_id) ?? "#8a8a8a",
});
}
return spans;
}