perf(D03): 노선분석 평면 두 장 캔버스 · 한 화면 한 번 그리기 · 등고선 줌 단계 색인 · 글 폭 기억 — 그림 그대로 (PLAN 582-0 · 582-1 · 요구 440-9 · 441)

- D03 평면 = 바탕(위성 · 바탕 겹 다섯) / 위(그은 선 · 재기) 두 장 · 마우스 움직임 · 누름 = 위만 · 화면 바뀜 = 둘 다 · rAF 한 화면 한 번
- PlanMap.redraw = 위 겹만 · redrawBase = 바탕까지(겹 켬끔 · 받음)
- 공용 그리기에 선택 칸만 더함 — 조각 색인(part.lod · indexLayerLod 를 부른 레이어만) · 글 폭 기억(widths 인자) · B04 · B05 는 안 켬
- 한 판 한 번 긋기(d)는 뺌 — 반투명 선 겹침 색이 바뀌고(픽셀 14만 차) 래스터가 더 느림(36 → 60 ms)
- 지킴 시험 test_582_contour_lod — 색인 그린 명령 = 지금 명령(27 줌 · 자리 · 닫힌 고리 · 짧은 조각) · 글 폭 기억 같은 글

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 14:41:59 +09:00
co-authored by Claude Opus 5.5
parent c4b37023c7
commit 5e906ceb44
7 changed files with 420 additions and 29 deletions
@@ -13,11 +13,44 @@ import type {
GeoJsonCollection,
GeoJsonGeometry,
Normalizer,
PartLod,
PreparedFeature,
PreparedLayer,
PreparedPart,
} from "../B04_PreProcess_UI_MapRender";
/** 줌 단계 무게(정규화 좌표 · 큰 → 작은 · 4 배 간격) — 화면 1 폭 ≈ 1,400 px 이면 다 줌아웃 ~ 2,000 배. */
const LOD_THRESHOLDS = [4 ** -5, 4 ** -6, 4 ** -7, 4 ** -8, 4 ** -9, 4 ** -10];
/** 이보다 짧은 조각은 색인 없이 전부 훑음(훑기가 이미 짧음). */
const LOD_MIN_VERTICES = 64;
/** 조각 하나의 줌 단계 색인. 짧거나 무게가 없으면 undefined. */
export function buildPartLod(weights: Float64Array | null): PartLod | undefined {
if (!weights || weights.length < LOD_MIN_VERTICES) return undefined;
const lists = LOD_THRESHOLDS.map((threshold) => {
const kept: number[] = [];
for (let v = 0; v < weights.length; v += 1) if (weights[v] >= threshold) kept.push(v);
return Uint32Array.from(kept);
});
return { thresholds: LOD_THRESHOLDS, lists };
}
/** 허용 오차에 맞는 가장 성긴 단 — 「무게 ≥ 오차」 정점을 모두 담음. 맞는 단이 없으면 null(전부 훑음). */
export function pickLodList(lod: PartLod, tolerance: number): Uint32Array | null {
for (let k = 0; k < lod.thresholds.length; k += 1)
if (lod.thresholds[k] <= tolerance) return lod.lists[k];
return null;
}
/** 레이어 선 조각마다 줌 단계 색인을 붙임(제자리 · 582 — 지금 D03 만 부름). */
export function indexLayerLod(layer: PreparedLayer): PreparedLayer {
for (const feature of layer.features) {
if (feature.kind !== "line") continue;
for (const part of feature.parts) part.lod = buildPartLod(part.weights);
}
return layer;
}
function isPoint(value: unknown): value is [number, number] {
return Array.isArray(value) && typeof value[0] === "number" && typeof value[1] === "number";
}