Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1 — 582 두 장 캔버스 · UI_Map 충돌(sub1 누름 잡기 · 놓음 + sub2 위만 다시 그림) 양쪽 살림
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { themeColor } from "@ui/ui_template_palette";
|
||||
import type { VWorldMeta } from "./B04_PreProcess_Api_Fetch";
|
||||
import { pickLodList } from "./logic/B04_PreProcess_UI_MapRender_Prepare";
|
||||
|
||||
// 2D 지도 벡터 레이어 렌더 엔진.
|
||||
// GeoJSON 좌표를 로드 시 1회만 정규화 맵 좌표(0~1)로 사전 투영해 두고,
|
||||
@@ -46,6 +47,14 @@ export type PreparedPart = {
|
||||
coords: Float64Array;
|
||||
closed: boolean;
|
||||
weights: Float64Array | null;
|
||||
/** 줌 단계 색인(582 · `indexLayerLod` 가 붙임 · 없으면 정점 전부 훑음) — 그리는 정점은 같음. */
|
||||
lod?: PartLod;
|
||||
};
|
||||
|
||||
/** 단마다 「무게 ≥ 그 단」 정점 번호(원래 차례). thresholds 는 큰 → 작은 차례. */
|
||||
export type PartLod = {
|
||||
thresholds: number[];
|
||||
lists: Uint32Array[];
|
||||
};
|
||||
|
||||
/** 사전 투영된 피처 1개. bbox는 정규화 좌표 기준이며 컬링에 사용한다. */
|
||||
@@ -375,6 +384,9 @@ function drawLineParts(
|
||||
const coords = part.coords;
|
||||
if (coords.length < 4) continue;
|
||||
const weights = part.weights;
|
||||
// 줌 단계 색인(582) = 「무게 ≥ 그 단」 정점만 훑음 — 아래 거름이 그대로라 그리는 정점 같음.
|
||||
const list = part.lod ? pickLodList(part.lod, tolerance) : null;
|
||||
const count = list ? list.length : coords.length / 2;
|
||||
// 현재 줌에서 화면 오차 LOD_PX 미만인 정점만 생략 (끝점은 weight=∞라 항상 유지).
|
||||
// 정점 사이 보간은 하지 않는다 — 원본 데이터의 형상 그대로 표시 (2026-07-28 사용자 지시).
|
||||
context.beginPath();
|
||||
@@ -388,8 +400,10 @@ function drawLineParts(
|
||||
let prevX = 0;
|
||||
let prevY = 0;
|
||||
let prevSide = 0;
|
||||
for (let i = 0; i < coords.length; i += 2) {
|
||||
if (weights && weights[i / 2] < tolerance) continue;
|
||||
for (let k = 0; k < count; k += 1) {
|
||||
const v = list ? list[k] : k;
|
||||
if (weights && weights[v] < tolerance) continue;
|
||||
const i = v * 2;
|
||||
const x = coords[i] * affine.ax + affine.bx;
|
||||
const y = coords[i + 1] * affine.ay + affine.by;
|
||||
const side = sideOf(x, y);
|
||||
@@ -584,6 +598,8 @@ export function drawPreparedLabels(
|
||||
uprightRad = 0,
|
||||
/** 글자 테두리 색 — 지도 바탕색과 같아야 글자가 선을 끊어 보인다. 기본 = `--map-halo`. */
|
||||
halo = haloColor(),
|
||||
/** 글 폭 기억(582 · 글 → 폭 px) — 부르는 쪽이 글꼴마다 하나 둠 · 없으면 그때마다 잼. */
|
||||
widths?: Map<string, number>,
|
||||
): void {
|
||||
const affine = affineOf(view);
|
||||
const margin = CULL_MARGIN;
|
||||
@@ -600,7 +616,12 @@ export function drawPreparedLabels(
|
||||
const y = feature.labelAnchorY * affine.ay + affine.by;
|
||||
if (x < -margin || x > view.width + margin) continue;
|
||||
if (y < -margin || y > view.height + margin) continue;
|
||||
const half = context.measureText(feature.labelText).width / 2 + LABEL_GAP_PX;
|
||||
let width = widths?.get(feature.labelText);
|
||||
if (width === undefined) {
|
||||
width = context.measureText(feature.labelText).width;
|
||||
widths?.set(feature.labelText, width);
|
||||
}
|
||||
const half = width / 2 + LABEL_GAP_PX;
|
||||
if (
|
||||
drawn.some(
|
||||
(item) => Math.abs(item.x - x) < item.half + half && Math.abs(item.y - y) < LABEL_ROW_PX,
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user