Files
Aislo/B04_PreProcess/B04_PreProcess_UI_MapOverlays.ts
T
eomsangdon fdd9c77cb3 feat(B05): 배수유역도 필터 버튼을 지도 오버레이로 · 유역 번호 배지를 라운드 사각으로
- 표시 필터 버튼 묶음을 헤더에서 지도 좌측 상단(8px 여백)으로 옮겼다. 버튼 줄과
  상태 문구를 한 오버레이가 세로로 쌓아, 문구가 버튼 아래에서 시작한다
- 버튼 줄은 한 줄 유지. 오른쪽 진행단계 오버레이에 끝이 가려도 창을 넓히거나 그
  오버레이를 접으면 드러난다(사용자 확정) — 두 줄로 접는 쪽이 지도를 더 가린다
- 오버레이 빈 자리는 pointer-events를 흘려 지도 팬·유역 고르기를 막지 않고,
  버튼 위 pointerdown만 지도로 내려가지 않게 끊는다
- 유역 번호 배지를 원에서 라운드 사각으로. 지도 위 다른 숫자가 전부 동그라미라
  크기로만 갈렸다. 크기(지름 22px)는 그대로. B04 지도 배지도 같은 함수라 함께 바뀐다
2026-08-17 20:17:05 +09:00

154 lines
5.2 KiB
TypeScript

/* =============================================================================
* 2D 지도 오버레이 도형 (B04 지도 · B05 배수유역도 공용)
*
* 유역 채움·번호 배지·분수령 파선·상류 세류망처럼 **배경 레이어 위에 얹는** 도형만 모았다.
* 좌표 변환과 레이어 렌더는 `B04_PreProcess_UI_MapRender.ts`가 맡는다 — 그 파일이 700줄
* 한계에 닿아 분리했다.
* ========================================================================== */
import { themeColor } from "@ui/ui_template_palette";
/** 상류 세류망 강조선 색. 정의처는 `ui_template_theme.css`(`--map-upstream`). */
const upstreamLineColor = (): string => themeColor("--map-upstream", "rgba(29, 78, 216, 0.95)");
import {
haloColor,
lonLatToScreen,
type Normalizer,
type ViewState,
} from "./B04_PreProcess_UI_MapRender";
export type FilledRing = {
ring: ReadonlyArray<readonly [number, number]>;
/** 면적 중심에 얹을 번호. 없으면 라벨을 그리지 않는다. */
label?: string;
};
/**
* lon/lat 폴리곤 링을 파스텔 채움 + 테두리 + 중심 번호로 그린다.
* 사전 투영 캐시를 쓰지 않는 소량(유역 수 개) 오버레이 전용이라 매 프레임 변환해도 부담이 없다.
*/
export function drawFilledRing(
context: CanvasRenderingContext2D,
entry: FilledRing,
normalizer: Normalizer,
view: ViewState,
color: string,
): void {
if (entry.ring.length < 3) return;
let sumX = 0;
let sumY = 0;
context.beginPath();
entry.ring.forEach(([lon, lat], index) => {
const [x, y] = lonLatToScreen(normalizer, view, lon, lat);
sumX += x;
sumY += y;
if (index === 0) context.moveTo(x, y);
else context.lineTo(x, y);
});
context.closePath();
context.fillStyle = color;
context.fill();
context.strokeStyle = color;
context.lineWidth = 1.6;
context.stroke();
if (!entry.label) return;
drawRingBadge(context, [sumX / entry.ring.length, sumY / entry.ring.length], color, entry.label);
}
/** 유역 번호 배지 한 개(라운드 사각 + 숫자). 화면 좌표를 그대로 받는다.
*
* 채움과 따로 떼어 둔 이유: 번호는 **맨 위에** 있어야 한다. 채움과 함께 그리면 그 위에
* 얹히는 등고선·화살표·관 마커에 가려 읽을 수 없다(2026-08-01 사용자 지시).
*
* 원이 아니라 사각인 이유: 지도 위 다른 숫자들도 전부 동그라미라 크기로만 갈렸다.
* 형태를 바꿔 한눈에 구분되게 했다 — 크기는 그대로다(2026-08-17 사용자 지시). */
export function drawRingBadge(
context: CanvasRenderingContext2D,
center: readonly [number, number],
color: string,
label: string,
): void {
const [centerX, centerY] = center;
const half = 11;
context.save();
context.beginPath();
context.roundRect(centerX - half, centerY - half, half * 2, half * 2, 6);
context.fillStyle = color;
context.fill();
context.strokeStyle = haloColor();
context.lineWidth = 1.5;
context.stroke();
context.font = "600 12px sans-serif";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = themeColor("--map-label-text", "#1f2937");
context.fillText(label, centerX, centerY);
context.restore();
}
/** 폴리곤 정점 평균의 화면 좌표 — 배지를 얹을 자리. */
export function ringCenterOnScreen(
ring: ReadonlyArray<readonly [number, number]>,
normalizer: Normalizer,
view: ViewState,
): [number, number] {
let sumX = 0;
let sumY = 0;
ring.forEach(([lon, lat]) => {
const [x, y] = lonLatToScreen(normalizer, view, lon, lat);
sumX += x;
sumY += y;
});
return [sumX / ring.length, sumY / ring.length];
}
/** 상류 세류망 강조 — B04 분석 오버레이와 B05 배수유역도가 같은 굵기·색으로 그린다. */
export function drawUpstreamLines(
context: CanvasRenderingContext2D,
lines: ReadonlyArray<ReadonlyArray<readonly [number, number]>>,
normalizer: Normalizer,
view: ViewState,
): void {
if (lines.length === 0) return;
context.save();
context.setLineDash([]);
context.lineWidth = 4;
context.lineCap = "round";
context.lineJoin = "round";
context.strokeStyle = upstreamLineColor();
lines.forEach((line) => {
if (line.length < 2) return;
context.beginPath();
line.forEach(([lon, lat], index) => {
const [x, y] = lonLatToScreen(normalizer, view, lon, lat);
if (index === 0) context.moveTo(x, y);
else context.lineTo(x, y);
});
context.stroke();
});
context.restore();
}
/** 유역 경계(분수령=능선)를 능선 스타일(갈색 파선)로 강조해 그린다. */
export function drawRidgeRing(
context: CanvasRenderingContext2D,
ring: ReadonlyArray<readonly [number, number]>,
normalizer: Normalizer,
view: ViewState,
): void {
if (ring.length < 3) return;
context.beginPath();
ring.forEach(([lon, lat], index) => {
const [x, y] = lonLatToScreen(normalizer, view, lon, lat);
if (index === 0) context.moveTo(x, y);
else context.lineTo(x, y);
});
context.closePath();
context.save();
context.strokeStyle = themeColor("--map-basin-outline", "#92400e");
context.lineWidth = 1.8;
context.setLineDash([7, 4]);
context.stroke();
context.restore();
}