- `docs/` 를 .gitignore 에서 빼 git 추적 대상으로 전환 (위키·완료 이력·검증 기록) - `docs/raw/PLAN.md` · `docs/raw/OWNERS.md` 를 저장소 최상위로 이동 후 .gitignore 에 등록 — 창끼리 공유하되 저장소에는 안 올리는 장부 - 살아 있는 경로 참조 7개 파일 정정 (아카이브 107건은 그때 사실이라 그대로 둠) - graphify 날짜별 산출물(`docs/wiki/graphify-out/20*/`) 제외 — `graphify update` 가 다시 만듦 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5.7 KiB
5.7 KiB
[완료 계획서] B04 하단 2D 지도 — 도엽 등고선 렌더링 성능 개선
- 완료 일시: 2026-07-28
- 이관 일시: 2026-07-28
- 검증 문서:
file:///D:/02_Software_Prog/%EC%9E%84%EB%8F%84%EC%84%A4%EA%B3%84%20%EB%B0%8F%20%EA%B2%AC%EC%A0%81%EC%9E%90%EB%8F%99%ED%99%94%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8%20%EA%B0%9C%EB%B0%9C/docs/raw/verification/2026-07-28_verify_B04_Map_Performance.md
B04 하단 2D 지도 — 도엽 등고선 렌더링 성능 개선 (2026-07-28 협의)
배경
- B04 하단 2D 지도(
B04_wf1_Surface_UI_MapViewer.ts)에서 도엽 등고선 켜면 팬/줌 버벅임 심각. - 사용자 조건: ① 화면 해상도 저하 절대 불가(해상도 민감), ② 등고선·세류 원본 데이터는 차기 배수유역도 기능에서 사용 예정이므로 원본 GeoJSON·DB 무가공 — 모든 최적화는 프론트 렌더링 계층에서만.
- 진행 방식: 단계별 적용 → 각 단계마다 사용자 육안 확인 후 다음 단계 진행. B04 먼저, 결과 보고 타 페이지 확산 검토.
원인 진단 (코드 확인 완료)
| 문제 | 위치 | 내용 |
|---|---|---|
| 정점당 DOMRect 생성 | B04_wf1_Surface_UI_MapViewer.ts:265-283 |
toCanvasPoint가 정점마다 getMapRect() 호출, 매 프레임 정점 수만큼 객체 할당 |
| 무스로틀 전체 재드로잉 | B04_wf1_Surface_UI_MapViewer.ts:506-524 |
wheel/pointermove 이벤트마다 전 레이어 전 정점 재드로잉, rAF 없음 |
| 캔버스 버퍼 재할당 | B04_wf1_Surface_UI_MapViewer.ts:430-441 |
drawVectorLayer 호출마다 canvas.width/height 재설정 |
| 컬링 없음 | drawVectorLayer 전체 |
화면 밖 피처도 전부 그림 |
| 사전 투영 없음 | drawRing/drawPoint |
매 프레임 lon/lat→픽셀 변환 반복, GeoJSON 중첩 배열 순회 |
단계 1 — 무손실 최적화 (렌더 결과 픽셀 단위 동일 보장)
- 1-1. 좌표 사전 투영: GeoJSON 로드 시 1회만 lon/lat→**정규화 맵 좌표(0~1, Float64Array)**로 변환해 캐시. 매 프레임엔 뷰포트·scale·offset 합성 어파인만 적용. 피처별 bbox도 이때 사전 계산. (계획 대비 변경: 기준 픽셀 좌표 대신 정규화 좌표 채택 — 리사이즈 시 재투영 불필요)
- 1-2.
toCanvasPoint의 정점당getMapRect()제거: 프레임당 1회 어파인 계수(affineOf) 계산 후 재사용. 원 변환식과 대수적으로 동일하므로 렌더 결과 불변. - 1-3. rAF 스로틀: wheel/pointermove/resize는
scheduleDraw()로 상태만 갱신, 프레임당 1회만 드로잉.dispose시 대기 프레임 취소. - 1-4. 캔버스 버퍼 재할당은 width/height/DPR이 실제로 변할 때만 수행.
- 1-5. 뷰포트 컬링: 사전 계산된 피처 bbox로 화면 밖 피처 스킵 (라벨 앵커 포함, 마진 32px).
- 1-6. 렌더 로직 분리:
B04_wf1_Surface_UI_MapRender.ts(신규, 359줄) 렌더 엔진 /B04_wf1_Surface_UI_MapViewer.ts(423줄) UI·이벤트. 둘 다 700줄 이내. - 1-7.
npm run typecheck통과,prettier포맷팅,.claude/git-autopush.ps1실행. - 완료 조건: 도엽 등고선 켠 상태 팬/줌 체감 개선, 렌더 결과 기존과 픽셀 단위 동일(해상도·선 굵기·라벨 위치 불변). 사용자 육안 확인.
단계 2 — 비트맵 레이어 캐시 (구현 후 롤백, 2026-07-28 사용자 검증)
- 2-1~2-2. 제스처 중 CSS transform + 오버스캔 + settle 재렌더 방식 구현했으나, 사용자 검증 결과 롤백: 팬 종료마다 재렌더가 체감되고, 오버스캔 범위 밖 데이터가 다시 채워질 때 딜레이 발생. 단계 3 LOD로 프레임 렌더 비용이 충분히 낮아져 매 프레임 직접 렌더(단계 1 방식)가 더 나은 것으로 확정 — 항상 완전한 화면 보장, 재생성 딜레이·가장자리 공백 원천 제거.
- 결론: 단계 1(rAF 직접 렌더) + 단계 3(LOD) 조합 채택. 비트맵 캐시 코드는 제거됨.
단계 3 — 줌별 LOD + 곡선 스무딩 (2026-07-28 사용자 지시로 방향 확정: 줌아웃=경향, 줌인=디테일)
- 3-1. 연속 LOD: 로드 시 파트별 Douglas-Peucker 가중치 1회 계산(반복형, 종횡비 보정), 렌더마다 현재 줌 기준 화면 오차 0.75px 미만 정점만 생략. 줌인할수록 자동으로 원본 정점 복원. (계획 당시 "기본 꺼짐+토글" 의도였으나 사용자가 줌아웃 시 축소를 직접 지시하여 기본 적용으로 변경)
- 3-2.
줌인 곡선 스무딩(quadratic 스플라인)— 구현 후 원복 (2026-07-28 사용자 지시: "원래 데이터가 그런 거라면 보간 불필요"). 원본 정점 형상 그대로 표시. - 3-3.
순수 팬 settle 재렌더 생략— 단계 2 롤백과 함께 제거 (매 프레임 직접 렌더 구조에선 불필요). - 완료 조건: 사용자 검증 — LOD 렌더링 감소 확인(승인), 스무딩 불필요(원복), 최종 구조 = 단계 1 + LOD.
추가 작업 (2026-07-28 사용자 지시)
- 4-1. 줌인/아웃 중심을 뷰포트 중심 → 마우스 커서 위치로 변경. wheel 핸들러에서 커서 아래 지점이 고정되도록 offset 보정(
offset' = (cursor − center)·(1 − r) + offset·r). 배경지도는 동일 offset/scale을 쓰는 CSS transform이라 자동 정합. - 4-2. 지도 영역 중간 버튼 드래그 시 브라우저 기본 자동 스크롤이 페이지 스크롤과 간섭 — pointerdown에서
button === 1이면preventDefault()로 기본 동작 차단, 지도 팬 전용으로 사용.
제약
- 원본 GeoJSON·백엔드·DB 절대 무가공 (배수유역도 대비).
- 해상도 저하 감지 시 해당 단계 롤백.
- Surgical Edit — MapViewer 외 파일 수정 금지 (신규 MapRender 파일 제외).