feat(B03): 업로드 자료 카드 그래픽 미리보기 — 범위 사각형·노선 형상·범위 대조

- 계획노선 shapefile 분석에 `preview_path`(200점 안팎 솎은 좌표열) 추가 — 이미
  메모리에 있는 정점을 쓰므로 파일 재열람 0회(솎기 0.136ms)
- LAS·GeoTIFF 는 사용자 확정대로 **bbox 사각형만** (점구름·래스터 렌더 안 함)
- `upload-overview` 응답에 분석 `metadata` 동봉 — 재접속해도 같은 그림
- 카드에 SVG 미리보기 렌더(신규 `B03_FileInput_UI_Preview.ts`), 값 없으면 미표시
- 계획노선 카드는 지형 자료 범위와 대조 — 벗어나면 경고색·안내, 좌표계가 다르면
  대조 생략(재투영 안 함). shapefile 은 좌표계가 없어 같은 세트 `.prj` 값을 씀

검증: 공용 브라우저 실측(노선 선 121점·범위 안/밖·좌표계 상이 3갈래),
tmp/tests/test_b03_preview_path.py 2건, tsc --noEmit 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 07:04:33 +09:00
co-authored by Claude Opus 5
parent af85dea714
commit ab16361125
10 changed files with 337 additions and 2 deletions
@@ -213,6 +213,33 @@ def _route_name_from_attributes(attributes: dict[str, str], fallback: str) -> st
return fallback
_PREVIEW_MAX_POINTS = 200
def _thin_preview_path(
parts: list[list[tuple]], limit: int = _PREVIEW_MAX_POINTS
) -> list[list[list[float]]]:
"""카드 미리보기용으로 노선 정점을 솎는다 — 파일을 다시 읽지 않는다.
전 정점은 이미 메모리에 있고(`read_shapefile_parts`), 화면에 그릴 선은 60~80px
높이라 200점이면 모양이 충분히 산다. 파트별로 시작·끝점은 반드시 남긴다.
"""
total = sum(len(part) for part in parts)
if total == 0:
return []
step = max(1, total // limit)
preview: list[list[list[float]]] = []
for part in parts:
if not part:
continue
thinned = [[float(point[0]), float(point[1])] for point in part[::step]]
last = [float(part[-1][0]), float(part[-1][1])]
if thinned[-1] != last:
thinned.append(last)
preview.append(thinned)
return preview
def analyze_shapefile_metadata(path: str | Path) -> dict[str, Any]:
"""계획노선 shapefile의 B03 메타데이터를 만든다."""
source = Path(path)
@@ -241,4 +268,6 @@ def analyze_shapefile_metadata(path: str | Path) -> dict[str, Any]:
"bounds": header["bounds"],
"start_point": list(parts[0][0]) if parts else None,
"end_point": list(parts[-1][-1]) if parts else None,
# 카드 미리보기용 솎은 좌표열 — 파일 재열람 없음(2026-09-04 사용자 지시).
"preview_path": _thin_preview_path(parts),
}