# 계획노선 shapefile 입력 + PRJ 2개 분리 원본: `docs/raw/PLAN.md`의 「계획노선 shapefile 입력 + PRJ 2개 분리」. 검증 대기 상태로 이관했다. 남은 좌표계 기본값 일원화는 PLAN.md 「프로젝트 작업 좌표계 일원화」에 있다. **배경**: 원청 정식 계획노선이 shapefile(UTM-K)로, 지형 자료가 별도 PRJ(동부원점 Bessel)로 들어왔다. 실측 — 노선 bbox를 지형 계로 옮기면 TFW 원점(208,288.84 / 368,092.28) 안쪽에 정확히 앉는다(노선 1,241m × 454m). 기존 입력 경로는 ① shapefile 확장자를 아예 막고 ② PRJ를 프로젝트당 1개로 전제했다. **설계 원칙** (2026-08-31 사용자 확정): - **EPSG 코드로 구분하지 않는다.** 모르는 좌표계가 들어와도 돌아야 한다 — 판별은 라벨일 뿐이고 변환은 `crs_input_from_prj()`가 주는 값(EPSG 확정 시 `"EPSG:n"`, 아니면 **원문 WKT**)으로 한다. 실측: 이번 PRJ 2종 모두 `to_epsg()`가 None인데 WKT↔WKT 변환은 정확했다. - **PRJ 짝은 폴더로 가른다.** shapefile은 규격상 형제 파일이 같은 basename·같은 폴더에 있어야 GDAL이 연다. 세트를 `input/shp/`에 두면 노선 PRJ가 그 안에, 지형 PRJ는 `input/prj/`에 남아 **파일명 정렬 운에 기대지 않고** 갈린다. - **노선은 2D로 받는다.** 업로드 shp는 PolyLine(type 3), Z 없음, 121정점. 지반고는 이미 `build_surface_sampler()`+`sample_xy()`가 확정 서피스에서 뽑는다 (`B05_Profile_Engine_Sections_Core.py:135`). CSV의 z는 참조값이라 z=0으로 채운다. - **파일 하나에 카드 한 장.** 노선 파일이 5개면 카드도 5개다(2026-08-31 사용자 지시). **구현** (커밋 `fe72ea04` · `36e4decf` + 2열 개편, 브랜치 `feat/b03-route-shapefile`): - [X] A. 업로드 허용에 `.shp .shx .dbf .cpg` 추가(py·ts). 한 번에 보낼 **파일 수** 5 → 10 (화면의 카드 수 한도도 10으로). - [X] B. `B03_FileInput_Engine_Shapefile.py` 신설 — ESRI 규격 직접 파싱(GDAL 미사용). 형제 파일이 아직 안 와도 `.shp` 하나로 기하를 읽는다. `.cpg` 내용이 `949`뿐인 실물을 CP949로 정규화. `analyze_input_metadata()`에 `.shp` 분기. - [X] C. `read_planned_route()`로 CSV·shapefile 일원화, `find_planned_route_file()`은 shapefile 우선. `PlannedRoute.crs_input` 추가(변환 입력), `epsg`는 라벨로 유지. - [X] D. shapefile 세트를 `input/shp/`에 모으고(`resolve_upload_destination` + `reconcile_shapefile_members`), `find_project_prj()`가 지형 PRJ를 프로젝트 좌표계로 고른다. `project_epsg_from_prj()`·`B04_PreProcess_Engine.py`의 `glob()[0]` 제거. - [X] E. 필수 세트를 "노선 1종(csv|shp) + prj + tfw"로 완화, shp면 `shx`·`dbf`· 노선 PRJ 동반 필수. 재접속 현황에 `relative_path`를 실어 노선 PRJ를 `route_prj`로 따로 세게 했다 — **지형 PRJ 없이 통과하던 구멍**을 막았다. - [X] F. UI — **컨테이너 2개 · 파일마다 카드 한 장** - 왼쪽 「계획노선 자료」: `csv`(.csv·.shp) · `shx` · `dbf` · `cpg` · `route_prj` - 오른쪽 「지형 자료 (LAS)」: `las_laz` · `prj`(지형 좌표계) · `tfw` · `tif` + LAS 없는 설계 토글 - `.prj`만 확장자로 안 갈린다 — **노선 도형과 basename이 같으면** 노선 좌표계 카드, 아니면 지형 카드(`planSlotAssignments`). 재접속 현황은 저장 경로로 가린다. - 형제 카드는 노선 도형이 shapefile일 때만 필수 — "· 선택" 꼬리표가 실시간으로 붙고 떨어진다. - 처음엔 슬롯 하나에 세트를 담았으나(`companions`) 사용자 지시로 갈아엎었고, 슬롯=파일 하나인 기존 구조가 그대로 살아 업로드 루프도 원래대로 돌아왔다. **자체검증** — `tmp/tests/test_route_shapefile_input.py` **9개 전부 통과** (`./venv/Scripts/python.exe -m pytest tmp/tests/ -q`, 2026-08-31): - 실물 파서: PolyLine 1파트·121정점·전 정점 z=0 ✓ - `.cpg`가 `949`뿐인 실물 → CP949 정규화, 속성 `대상지=일월.용화.2.2`·`길이=2.200` ✓ - 노선 PRJ는 `to_epsg()`가 None → `crs_input`이 **원문 WKT**, 라벨만 5179 ✓ - 노선(UTM-K) → 지형 PRJ 변환이 TFW 원점 안쪽, 연장 2,000~2,300m(속성 2.2km와 일치) ✓ - CSV·shapefile 공존 시 shapefile 우선, 노선명은 dbf 속성에서 ✓ - 노선 PRJ 파일명이 정렬상 앞서도(`a_route.prj` < `z_terrain.prj`) 지형 PRJ 선택 ✓ - PRJ가 SHP보다 먼저 도착한 순서도 세트 폴더로 끌어오고 지형 PRJ는 그대로 ✓ - 필수 세트: shp면 `shx`·`dbf`·`route_prj` 요구, 지형 PRJ 없으면 차단, **CSV 기존 흐름은 그대로 통과**(회귀) ✓ - `npx tsc --noEmit` 통과, `ruff check`·`ruff format` 통과 - **전체 스위트 잔여 실패 11건은 기존 실패** — `git archive HEAD` 사본에서 같은 11건이 동일하게 실패한다(B05 구조물·B06 포장·B07 토적도·PRJ 실물경로). 실패 테스트 중 이번에 고친 모듈을 import하는 것은 없다. - **화면 검증**(공용 브라우저, 백엔드 재시작 + 캐시 비우기 + reload): 실물 7파일 (노선 5 + 지형 prj·tfw)을 한 번에 떨어뜨려 배정 실측 — `route.prj`는 노선 좌표계 카드, `terrain.prj`는 지형 카드로 정확히 갈렸다. shx·dbf·route_prj의 "· 선택"이 사라지고 cpg만 남았다. 업로드 버튼은 LAS 미선택이라 비활성(정상). 조작한 화면은 새로고침으로 되돌렸다 — 업로드 버튼은 누르지 않았다. - 카드 제목이 한글 글자 단위로 3줄까지 접히던 것을 `word-break: keep-all` + 헤더 flex-wrap으로 고쳤다 — 카드 9개 전부 1줄로 실측. **계획 대비 차이·남긴 것**: - 임시 보관함(`is_batch_required_complete`)은 확장자만 알아 노선/지형 PRJ를 못 가린다 — 묶음 완료 판정은 `shx`·`dbf`까지만 본다. 프로젝트 업로드 경로는 경로로 갈라 정확. - `B04_PreProcess_Engine_SheetSurface.py`의 `planned.epsg or 5186`은 그대로 뒀다 — 좌표계 기본값 산재는 아래 「프로젝트 작업 좌표계 일원화」 몫이다. - **미실행**: 실제 프로젝트에 자료를 올려 끝까지 도는 종단 확인은 사용자 프로젝트 데이터를 건드려서 하지 않았다 — 지시가 있으면 시험 프로젝트로 수행한다. - **미실행**: `prettier`는 돌리지 않았다. 대상 파일이 원래 prettier 형식이 아니라 파일 전체가 재포맷돼(로케일 200줄 이상) 변경분이 묻힌다. 새 코드는 주변 서식에 맞췄다.