chore(계획): 요구 447 분석 — B04 · B05 지도 겹 어긋남(수렴각 회전) · PLAN 589-1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 20:13:20 +09:00
co-authored by Claude Opus 5.5
parent b08f78cf8e
commit 41392b7e1c
@@ -0,0 +1,149 @@
# 447 분석 — B04 · B05 지도 겹 어긋남(「같은 직사각형」 가정)
조사만 · 코드 · git · storage · DB 손대지 않음. 셈 = `tmp/brain_447/calc_447.py`(겹마다 화면 식 그대로 · pyproj 정답) · `calc_447_fix.py`(고친 뒤 나머지) · `calc_447_open.py`(처음 화면 · 크게 당김) · 결과 글 `calc_447_out.txt`.
맞춤 확인: 같은 식으로 검증_sub2_D 상자 귀퉁이 = **63.62 m** → 586-0 실측 63.6 m 와 같음(셈 틀 맞음).
## 0. 한눈에
| 화면 | 겹(좌표계) | 「같은 직사각형」 가정 | 어긋남(B 검증 다섯 = 용화) | D 지역 크기였다면 |
| --- | --- | --- | --- | --- |
| B04 하단 지도 | 위성 · 혼합 · 백지도 그림(웹 메르카토르 타일) · 지적 · 행정 · 도엽(경위도) · 계획선 · 측점 · 흐름 강도 · 관(TM) · 유역 · 분수령(서버가 TM→경위도) | 있음 — 경위도 ↔ TM · 그림 ↔ 경위도(contain 비율) | 노선↔도엽 귀퉁이 3.9 m · 위성↔도엽 7.5 m · 노선 자리 위성↔노선 5.2 m | 64 m · 71 m |
| B05 평면 노선 편집 | 도엽 하천중심선 등 · 도엽 등고선(LAS 없을 때)(경위도) · 노선 · LAS 등고선 · 구조물(TM) · 위성 없음 | 있음 — 경위도 ↔ TM | 귀퉁이 3.9 m · 노선 자리 1.6 m | 64 m |
| B05 배수유역도 | 위성 그림(기본 꺼짐) · 도엽 · 유역 채움 · 분수령 · 상류 세류(경위도) · 노선 · 관 · 강도 · 화살표 · 구간 띠 · 측점(TM) | 있음 — B04 와 같음 | B04 와 같음 | B04 와 같음 |
- 「어긋남 0」 자리 = 그림 상자 왼위 ↔ 오른아래 대각선(경위도 ↔ TM) · 그림 상자 세로 가운데 줄(위성 ↔ 경위도). 둘 다 가운데를 지남 → 「가운데는 맞고 가장자리만 어긋남」.
- 저장 좌표 영향 없음(지금도) — 누름 → TM · 노선 · 관 · 구조물 그리기가 모두 TM 한 길(어긋나 보이는 것은 경위도 · 위성 쪽).
## 1. 화면별 겹 · 좌표계 · 화면 식
공용 식(B04_PreProcess_UI_MapRender.ts):
- `computeMapRect` 117~131 — 뷰포트 안에 **TM 상자 비율**(`width_meters/height_meters`)로 맞춘 네모.
- `normalizedToScreen` 324~332 · `affineOf` 313 — 0~1 정규화 → 화면(배율 · 옮김).
- 경위도 길 G — `createNormalizer` 107~115 · `lonLatToScreen` 335~346 · `_Prepare.ts` `projectRing` 59~67 · `labelAnchorOf` 157~172: `n = ((lon−lon_min)/lonR, 1−(lat−lat_min)/latR)`.
- TM 길 T — `metricToScreen` 352~361 · `createMetricProjector` 141~159 · `_Prepare.ts` `prepareMetricPolyline(s)` 252 · 298: `n = ((x−x_min)/W, 1−(y−y_min)/H)` — 주석 그대로 「meta의 x/y 범위와 lon/lat 범위가 같은 사각형」.
- 상자 메타 S·G·T 공통 — 서버 `B04_PreProcess_Engine_VWorld.py` 167~191: 타일 상자의 **왼위(lon_left, lat_top) · 오른아래(lon_right, lat_bottom) 두 귀퉁이만** pyproj 로 TM → `x_min · y_max · x_max · y_min`. 나머지 두 귀퉁이는 맞추지 않음.
- 위성 그림 S — 같은 파일 117~160: VWorld WMTS(EPSG:3857 타일) 이어 붙임 → 그림 가로 = 경도 선형 · 세로 = **메르카토르 y 선형**(위도 선형 아님).
### B04 하단 지도(`B04_PreProcess_UI_MapViewer.ts`)
- 위성 · 혼합 · 백지도: `<img>` 105~111 · 주소 511 · `updateImageTransform` 311~318(CSS translate · scale) · CSS `B04_PreProcess_UI_Style_Map.css` 80~83 `object-fit: contain` → 그림은 **그림 px 비율**(2560×3328)로 놓임 · 캔버스 겹은 **TM 상자 비율** 네모(mapRect) — 두 네모 크기가 다름.
- 지적 · 행정 · 등고선 · 도엽_*(CRS84 경위도 GeoJSON): 454 · 507 `prepareLayer(data, normalizer)` → G.
- 계획선(TM): 493~494 `prepareMetricPolyline` → T · 측점 눈금 421~428 `createMetricProjector` → T.
- 흐름 강도 `B04_PreProcess_UI_FlowStrength.ts` 157 · 329 → T · 고른 고리 207 → G.
- 세부 유역 `B04_PreProcess_UI_Basins.ts` 관 242 · 266 → T · 유역 채움 287~290 · 501~507 → G(`Router_Basins.py` 399 서버가 TM 경계를 pyproj 로 경위도로 바꿔 줌).
- 배수유역 관리 겹 `B04_PreProcess_UI_Watershed.ts` 165~181 · 200~217 · 385~406 · 419~436 — G 식을 손으로 네 번 베낌 · TM 1 m 격자를 `bbox_lonlat` 의 경위도 최소 · 최대 직사각형에 폄(격자도 회전분만큼 어긋남).
- 「같은 직사각형」: G ↔ T(회전 · 사다리꼴) · S ↔ G(contain 비율 · 메르카토르 세로).
### B05 평면 노선 편집(`B05_Profile_UI_RouteEdit*.ts`)
- 메타 = 위성 메타(`_Drainage_Parts.ts` 464) · 위성 그림 없음.
- 도엽 겹(하천중심선 등) `_RouteEdit_Contour.ts` 195~199 · 도엽 등고선(LAS 없을 때) 111 → G.
- LAS 등고선 101 · 104 `prepareMetricPolylines` → T · 노선 · 구조물 `_RouteEdit.ts` 306~310 `metricToScreen` · `screenToMetric` → T · 추천 노선 `_Recommend_Map.ts` 143 → T.
- `sheetCoversBand` `_RouteEdit_Contour.ts` 117~ — TM → 경위도를 같은 직사각형으로(받을 범위 판정만 · 띠 여유 안).
- 「같은 직사각형」: G(도엽) ↔ T(노선 · LAS 등고선). 주석(09-12 ⑥) 「도엽 등고선과 LAS 등고선은 서로 어긋난다」 중 일부가 이 흠일 수 있음(아래 5).
### B05 배수유역도(`B05_Profile_UI_Drainage*`)
- 위성: `_Drainage_Chrome.ts` 91~93 · `_Drainage_Panel.ts` 188~194 · 485 · CSS `B05_Profile_UI_Style_Drainage.css` 122~127 `object-fit: contain` → S(B04 와 같은 흠).
- 도엽 `_Drainage_Panel.ts` 477~482 → G · 유역 채움 · 분수령 · 상류 세류 `_Drainage_Render.ts` 85~131 · 고르기 `_Drainage_Interact.ts` 143~152 → G.
- 노선 426 · 486 `prepareMetricPolyline` → T · 관 `_Drainage_Pipes.ts` 96~130(제 식 · 같은 직사각형) → T · 강도 · 구간 띠 · 화살표 `_Drainage_Render.ts` 139~ → T · 확대 한도 `_Drainage_ZoomRange.ts` 53~57 G 식 베낌.
- 「같은 직사각형」: B04 와 같음.
## 2. 어긋남 크기(셈)
검증 프로젝트는 땅이 둘뿐 — B 다섯(랩탑메인 · sub1~4) = 용화 한 상자 · D 다섯 = 같은 D 상자. 큰 상자 둘(가이드_예정노선 · 4250c7bc)도 함께.
| 땅 | 좌표계 | 중앙 자오선과 경도 차 | 수렴각 γ | 상자 | 그림 px |
| --- | --- | --- | --- | --- | --- |
| B 용화(B 다섯) | Korean 1985 Modified East Belt(Bessel · TOWGS84 · EPSG 번호 없음 = 5176 꼴) | 129.0866 − 129.0029 = 0.084° | 0.050° | 2454 × 3170 m | 2560×3328 |
| D(D 다섯) | EPSG:5186 | 127.9495 − 127 = 0.950° | 0.562° | 5462 × 3386 m | 2816×1792 |
| 가이드_예정노선 | EPSG:5186 | 0.958° | 0.567° | 7956 × 5819 m | 4096×3072 |
상자 통째(같은 땅 한 점이 두 겹에서 벌어진 거리 · m):
| 땅 | 노선↔도엽 T-G 귀퉁이(오른위 · 왼아래) | 위성↔도엽 S-G 좌우 가장자리 | 위성↔노선 S-T 최대 |
| --- | --- | --- | --- |
| B 용화 | 3.94 · 3.22 (왼위 · 오른아래 0 · 가운데 0.24) | 7.52 | 7.52 |
| D | 63.62 · 62.46 (가운데 0.68) | 70.54 | 70.54 |
| 가이드_예정노선 | 99.13 · 95.89 | 98.46 | 98.46 |
화면 px(뷰포트 가정 B04 1400×560 · B05 1200×700 — 화면을 띄우지 않아 가정값):
- B 용화 처음 화면(설계 노선 + 200 m · 배율 2.33 · B04 0.41 px/m): 노선 가운데 T-G 1.6 m / 0.6 px · S-G 6.4 m / 2.6 px · S-T 5.2 m / 2.1 px · 화면 안(그림 있는 곳) 최대 T-G 2.7 m / 1.1 px · S-G 7.4 m / 3.1 px.
- B 용화 크게 당김(화면 폭 20 m · 70 px/m): 노선 자리 T-G 109 px · S-G 447 px · S-T 360 px · 상자 가장자리 T-G 276 px · S-G 527 px.
- D 크기(배율 1 · 0.165 px/m): T-G 10.5 px · S-G 11.7 px · 크게 당기면 4,000 px 넘음.
### 까닭 나눔
노선↔도엽(T-G):
| 까닭 | B 용화 | D | 큰 상자 |
| --- | --- | --- | --- |
| 도북 · 진북 회전(수렴각 γ = 중앙 자오선과 경도 차 × sin 위도) — 귀퉁이 ≈ γ × 대각선 | 3.5 m | 63.0 m | 97.5 m |
| 축척 차 — 위도 따라 경도 1° 길이(cos 위도)가 달라 상자가 사다리꼴(상자 안 수렴각 변화) | ≤ 0.46 m | ≤ 1.07 m | ≤ 2.68 m |
| 축척 차 — TM 축척계수 k 변화(상자 안) | 0.001 m(k 1.0000005~1.0000009) | 0.06 m(k 1.0000840~1.0000954) | 0.1 m |
| 축척 차 — k 고른 몫 · 원점 차(가산 원점 · Bessel 데이텀 이동) | 0 m — 두 귀퉁이를 pyproj 로 맞춰 흡수 | 0 m | 0 m |
위성↔도엽(S-G):
| 까닭 | B 용화 | D |
| --- | --- | --- |
| contain 비율 차 — TM 상자 비율 대 그림 px 비율(+0.62 % · +2.65 %) | 7.52 m | 70.54 m |
| └ 두 귀퉁이 맞춤 기울기 γ(H/W + W/H)(회전 탓) | +0.18 % ≈ 2.2 m | +2.19 % ≈ 59.8 m |
| └ 웹 메르카토르 px 가로세로가 땅과 다름(1 − M/N · 회전 없어도 남음) | +0.43 % ≈ 5.3 m | +0.44 % ≈ 12.0 m |
| 메르카토르 세로 비선형(그림 = 메르카토르 y 선형 · 도엽 = 위도 선형) | 0.15 m | 0.17 m |
위성↔노선(S-T) = 둘이 겹친 것(방향에 따라 더해지거나 빠짐).
## 3. 고칠 길
### 권장 — TM 을 주인으로(B 안 · D03 과 방향만 반대)
D03 은 바탕이 MapLibre(웹 메르카토르 GL)라 TM 겹을 경위도 쪽으로 옮김(586-0). B04 · B05 는 바탕이 캔버스 + `<img>` 한 장이고 설계값 · 누름 · 재기가 모두 TM → **경위도 겹과 그림만 TM 으로 옮기는 쪽이 작음**. TM 길(`metricToScreen` · `screenToMetric` · `createMetricProjector` · `prepareMetricPolyline(s)` · `_Drainage_Pipes` 제 식)은 손대지 않음 → 저장 좌표 영향 없음이 구조로 보장.
데이텀 문제: B 검증 다섯이 모두 Bessel(TOWGS84 WKT) → D03 TS 변환(`D03_PlanAnalysis_Tm.ts` · GRS80 5185~5188 · 5179 만)은 `tmParams` = null → **D03 틀을 그대로 공용으로 올리면 B 검증 프로젝트는 하나도 안 고쳐짐**. 그래서 변환은 서버 pyproj 에 맡김.
1. 서버 `B04_PreProcess_Router_GIS.py` `get_vworld_meta` 33~60 — 응답에 `tm_grid`(그림 상자 경위도 9×9 마디의 TM 좌표 · `resolve_project_crs` · pyproj)를 붙임. 응답 때 셈 · storage 안 씀 · D03 은 모르는 칸이라 그대로.
- 쌍선형 나머지(상자 안 최대): 9×9 = 용화 0.001 m · D 0.006 m · 큰 상자 0.014 m(2×2 네 귀퉁이만이면 0.09 · 0.43 · 0.90 m — 크게 당기면 보임).
2. 공용 `B04_PreProcess_UI_MapRender.ts` · `logic/B04_PreProcess_UI_MapRender_Prepare.ts`
- `Normalizer` 에 고를 칸 `toNorm?(lon, lat)` — 새 `createTmNormalizer(meta)`(또는 옵션)일 때만 채움: 격자 쌍선형 → TM → `((x−x_min)/W, 1−(y−y_min)/H)`. `createNormalizer` 는 그대로(D03 · 옛 시험 보호).
- `projectRing` 59~67 · `labelAnchorOf` 157~172 · `lonLatToScreen` 335~346 이 `toNorm` 있으면 씀 → 도엽 · 지적 · 행정 · 유역 채움 · 분수령 · 상류 세류 · 고른 고리(MapOverlays · Basins · FlowStrength · Drainage_Render · Drainage_Interact)가 한 번에 TM 자리로.
- 손으로 베낀 G 식 — `B04_PreProcess_UI_Watershed.ts` 네 곳 → `lonLatToScreen` · 격자 네모는 `bbox_lonlat` 네 귀퉁이를 `toNorm`(TM 격자는 TM 에서 곧은 네모) · `_Drainage_ZoomRange.ts` 53~57 → `toNorm`.
- 부르는 곳 셋만 `createTmNormalizer` 로: B04 `MapViewer.ts` 492 · B05 `_Drainage_Panel.ts` 477 · `_RouteEdit_Contour.ts` 195. (`VWorldMeta` 타입에 `tm_grid?` 한 줄.)
3. 위성 그림 — `B04_PreProcess_UI_MapViewer.ts` `updateImageTransform` 311~318 · `_Drainage_Panel.ts` 188~194 · CSS 두 곳(`object-fit: contain` · `transform-origin: center` 뺌 → 원점 0 0 · 그림 제 크기 · `matrix(…)`).
- 행렬 = 586-0 `localTmFrame` 생각 그대로: 그림 px → (경도 선형 · 메르카토르 y) → 경위도 → `toNorm` → 화면, 이 길을 **화면 가운데에서 1차로** 맞춤(옮김 · 확대 때마다 다시 · 이미 그때마다 부름).
- 나머지: 화면 폭 2 km 0.07 m(0.05 px) · 200 m 0.0007 m · 20 m 0 — 상자 통째(배율 1)도 0.24 m(용화) · 0.6 m(D) · 1.4 m(큰 상자) = 0.1 px 안팎.
- 세 귀퉁이 고정 행렬(더 짧음)이면 상자 안 0.24 · 0.6 · 1.4 m 가 그대로 → 크게 당기면 수십 px · 권하지 않음.
4. 그대로 둠 — `sheetCoversBand`(받을 범위 판정 · 띠 여유 안 · 영향 ≤ 귀퉁이 T-G) · 축척 막대(TM 기준이라 맞음).
다른 화면 영향:
- D03 — `createNormalizer` · `prepareLayer` · `lonLatToScreen` 을 쓰지만 `toNorm` 없는 길 → 바뀜 없음(586-0 · 586-1 그대로).
- B05 · B06 한 페이지 · A00 설계 화면이 띄우는 편집 · 배수유역도 → 같은 부품이라 함께 고쳐짐.
- 시각만 바뀜: 도엽 · 위성 · 유역 채움이 노선 쪽으로 옮겨 앉음(용화 노선 자리 위성 5~6 m · 도엽 1.6 m).
값(저장 좌표) 영향 없음 확인:
- 쓰는 길 = 누름 → `screenToMetric`(`_RouteEdit_View.ts` 41~52) · 관 = 노선 누가거리(`_Drainage_Pipes.ts` 92~113) · 구조물 · 측점 = TM → 모두 안 고침.
- 경위도 겹에서 TM 으로 되돌려 저장하는 길 없음(G 역변환은 D03 `_UI_Map.ts` 에만) · 등고선 고르기 · 자물쇠 = 높이값만(`_RouteEdit_ContourLock.ts`).
- 지난 저장값(사용자가 어긋난 위성 · 도엽을 보고 놓은 관 등)은 그대로 — 고쳐도 옮기지 않음.
시험 자리:
- 새 `resources/tester/test_447_tm_grid.py` + `helper_447_tm_grid.cjs`(진짜 TS 돌림 · 586 꼴): 서버 `tm_grid` 왼위 · 오른아래 마디 = 메타 `x_min · y_max · x_max · y_min` 1 mm 안 · 안 마디 = pyproj(용화 Bessel WKT · 5186) · TS `toNorm` 대 pyproj ≤ 0.02 m · `tm_grid` 없으면 옛 식과 비트 같음 · 그림 행렬 — 그림 귀퉁이 px 의 화면 자리 = `toNorm` 자리 0.5 px 안(당긴 화면) · 옛 꼴 어긋남 지킴(용화 귀퉁이 ≥ 3 m · D ≥ 60 m).
- 함께 돌릴 옛 시험(고친 대상): `test_582_contour_lod` · `test_509_1_sheet_clip_route` · `test_509_6_sheet_clip_drainage` · `test_281_1_drainage_zoom_range` · `test_b05_routeedit_view` · `test_586_tm_frame`(D03 안 바뀜 확인).
### 다른 길 — D03 과 같은 방향(A 안 · 비권장)
- TM 겹을 경위도 바탕으로(`localTmFrame` 공용화 · `D03_PlanAnalysis_Tm.ts` → `common_util/` 옮김).
- 고칠 곳이 많음 — `metricToScreen` · `createMetricProjector` · `screenToMetric`(D03 옛 꼴도 씀) · `_Drainage_Pipes` 제 식 · `prepareMetricPolyline(s)` · 편집 `toScreen` · `toMetric` · 추천 노선 · 보던 화면 기억 · 맞춤 — 누름 → 저장 좌표 길까지 건드림.
- Bessel 이라 B 검증 다섯은 「모르는 좌표계 = 옛 꼴」로 남음(TS 에 Bessel + 7 변수 이동을 더해야 함).
- 위성 contain 흠은 따로 고쳐야 함(그림을 mapRect 에 늘려 채움 · 메르카토르 세로 0.15~0.5 m 남음).
- 나머지(처음 화면 귀퉁이): 용화 0.22 m(B04) · 0.12 m(B05).
## 4. 쉬운 말 세 줄
- 지도는 투명지 석 장(위성 사진 · 도엽 · 노선)을 「똑같은 네모」라 여기고 **왼쪽 위 · 오른쪽 아래 두 귀퉁이에만 핀을 꽂아** 겹쳐 둔 꼴.
- 그런데 노선 장은 살짝 돌아 있고(용화 0.05° · D 지역 0.56° — 지구가 둥글어 설계 좌표의 「위쪽」과 경도선의 「위쪽」이 다름), 위성 장은 가로가 살짝 좁음(0.6 % · D 2.6 % — 웹 지도 사진의 가로세로 비율이 땅과 조금 다름).
- 그래서 핀 두 개를 잇는 대각선(가운데 포함) 위는 꼭 맞고 다른 귀퉁이 · 좌우 가장자리로 갈수록 벌어짐 — 용화 귀퉁이 노선↔도엽 4 m · 위성↔도엽 7.5 m(노선 자리 위성↔노선 5 m) · D 지역 크기였다면 64 m · 71 m.
## 5. 확인 못 함
- 실제 화면 px — 화면을 띄우지 않음 · 뷰포트(B04 1400×560 · B05 1200×700)는 가정 · m 값은 뷰포트와 무관.
- B04 처음 화면이 쓰는 `confirmed.route_bounds` 가 B05 설계 노선 범위와 같은지 — 설계 노선(`planned_route_nodes.csv`) 범위로 셈.
- 곁 발견: 용화 설계 노선 동쪽 끝(x 209,038)이 위성 상자(x_max 208,900) 밖 138 m — 위성을 받은 범위 · 노선이 바뀐 때 다시 받는지 확인 못 함.
- 도엽 등고선 ↔ LAS 등고선 어긋남(`_RouteEdit_Contour.ts` 머리 주석) 중 이 흠 몫 — 화면 몫은 용화에서 노선 자리 1.6 m · 귀퉁이 3.9 m 까지 · 자료 자체 차는 확인 못 함.
- VWorld 타일이 정확히 EPSG:3857 격자인지 — 서버 코드 식(OSM 타일식) 그대로 가정.
- D03 Bessel 프로젝트(지금 D 검증은 모두 5186) — 586-0 은 옛 꼴로 남음 · 위 `tm_grid` 를 D03 이 써도 되는지는 따로.