fix(B04·B05): 지도 경위도 겹 · 위성 그림을 TM 겹 자리로 맞춤 (PLAN 589-1 · 요구 447 · 448)

- 서버 위성 메타 응답에 그림 상자 9×9 TM 격자(tm_grid · pyproj · 응답 때 셈 · 귀퉁이 1 cm 넘게 다르면 안 줌)
- 공용 createTmNormalizer(고를 칸 toNorm · 격자 쌍선형) · normOfLonLat · imageScreenMatrix · placeMapImage
- B04 하단 지도 · B05 배수유역도 · B05 평면 노선 편집이 TM 자리로 · 위성 그림 = 화면 가운데 1차 행렬(contain 뺌)
- Watershed 손 베낌 식 넷 · 줌아웃 한계 = 공용 식 · D03 길(createNormalizer) 바뀜 없음 · TM 길 손 안 댐
- 지킴 시험 test_589_tm_grid(Bessel · GRS80 · 격자 = pyproj 0.02 m · 그림 행렬 0.5 px · 옛 꼴 어긋남)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 20:21:39 +09:00
co-authored by Claude Opus 5.5
parent 7beca06a06
commit ba18b0f96d
14 changed files with 475 additions and 79 deletions
+8 -11
View File
@@ -34,9 +34,10 @@ import {
computeRouteView,
createMetricProjector,
MAP_STATION_INTERVAL_M,
createNormalizer,
createTmNormalizer,
drawPreparedLabels,
drawPreparedLayer,
placeMapImage,
routeLineColor,
ROUTE_LINE_WIDTH,
type MapRect,
@@ -108,6 +109,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
image.className = "b04-map__image";
image.alt = L("B04_Surface_Map_ImageAlt");
image.draggable = false;
image.addEventListener("load", () => scheduleDraw());
backgroundImages.set(layer, image);
});
const canvas = document.createElement("canvas");
@@ -308,13 +310,9 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
// 강도 색띠 범례도 같은 자리에 얹는다(CSS가 우측 세로로 세운다).
viewport.append(detailBasins.menuElement, flowStrength.legendElement);
function updateImageTransform(): void {
backgroundImages.forEach((image) => {
image.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
// 크게 당기면 배경 그림이 뭉개진다 — 흐림 보간을 끄고 픽셀을 그대로 보인다
// (2026-09-04 사용자 지시). 실제 크기는 축척 막대로 읽는다.
image.style.imageRendering = scale > 4 ? "pixelated" : "auto";
});
/** 배경 그림을 TM 겹 자리로 놓음(589-1 · 공용 `placeMapImage`). */
function updateImageTransform(view: ViewState): void {
backgroundImages.forEach((image) => placeMapImage(image, meta, normalizer, view));
}
function syncLayerVisibility(): void {
@@ -346,7 +344,6 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
function resetView(): void {
fitRouteView();
updateImageTransform();
scheduleDraw();
}
@@ -432,7 +429,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
detailBasins.draw(context, normalizer, view);
// 유입 집중점(집중유역)은 맨 마지막 — 켰을 때 무엇에도 가리지 않아야 고를 수 있다.
flowStrength.drawTop(context, normalizer, view);
updateImageTransform();
updateImageTransform(view);
drawScaleBar(mapRect);
}
@@ -489,7 +486,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
if (sequence !== loadSequence) return;
meta = nextMeta;
// 좌표 변환은 여기서 1회만 수행하고, 이후 프레임은 사전 투영 결과만 사용한다.
normalizer = createNormalizer(nextMeta);
normalizer = createTmNormalizer(nextMeta);
routeLayer =
planned.points.length > 1 ? prepareMetricPolyline(planned.points, nextMeta) : null;
routePoints = planned.points;