808줄 한 파일을 셋으로 나눔 (동작 불변, 순수 이동). - `B07_DesignDetail_Router_Support.py` 532줄 — 도면 목록·장 배치·확정 저장·재계산 - `B07_DesignDetail_Router_Support_Basin.py` 279줄 — 유역도 배경(GeoJSON·좌표 환산·도곽 클리핑) - `B07_DesignDetail_Router_Support_Io.py` 88줄 — 원본 JSON·매니페스트 입출력, 측점 대응표 떼어낸 `watershed_source`·`clip_line_to_box` 는 본체에서 `as` 로 다시 내보내 호출부(`B07_DesignDetail_Router.py`)의 import 경로가 그대로임. 검증: 라우트 7개 그대로, 공용 브라우저 `design-drawings` 200 · `frame-template` 200, ruff check 통과, tmp/tests 378 passed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""B07 도면 조립 지원 — 원본 JSON·매니페스트 읽기/쓰기와 측점 대응표.
|
|
|
|
지원 모듈이 700줄을 넘어 떼어냈다(2026-09-04). 유역도 조각과 본체가 함께 쓴다.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from B05_Profile.B05_Profile_Engine_Sections import prune_stale_cross_files
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
_STAGE_DIR = "B07_DesignDetail"
|
|
|
|
_CROSS_ID = re.compile(r"^cross_(\d+)m$")
|
|
_LONG_ID = re.compile(r"^longitudinal(?:_(\d+))?$")
|
|
# 노선 전장에 한 장씩만 나오는 도면 — 라우터가 원본 자료를 따로 실어 넘긴다.
|
|
MASS_HAUL_ID = "mass_haul"
|
|
WATERSHED_ID = "watershed"
|
|
COVER_ID = "cover"
|
|
|
|
# 아직 내용을 만들지 않은 도면 — 도각만 실어 연다(2026-09-01 사용자 지시).
|
|
# 화면 순서(DRAWING_GROUPS)와 같은 이름을 쓴다.
|
|
BLANK_DRAWINGS: tuple[tuple[str, str], ...] = (
|
|
("blank_plan_terrain", "계획평면도(지형)"),
|
|
("blank_plan_route", "계획평면도(노선배치도)"),
|
|
("blank_plan_layout", "계획평면도(배치도)"),
|
|
("blank_plan_lidar", "계획평면도(라이다)"),
|
|
("blank_cross_standard", "표준 횡단면도"),
|
|
("blank_standard", "표준도"),
|
|
("blank_landuse", "용지도"),
|
|
)
|
|
BLANK_LABELS: dict[str, str] = dict(BLANK_DRAWINGS)
|
|
|
|
|
|
def _read_json(path: Path) -> dict[str, Any]:
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
if not isinstance(payload, dict):
|
|
raise ValueError("도면 원본 JSON 형식이 올바르지 않습니다.")
|
|
return payload
|
|
|
|
|
|
def _cross_files(longitudinal_path: Path, longitudinal: dict[str, Any]) -> list[Path]:
|
|
cross_dir = longitudinal_path.parent.parent / "cross_sections"
|
|
if not cross_dir.is_dir():
|
|
raise FileNotFoundError("B06 횡단면 파일을 찾을 수 없습니다.")
|
|
stations = longitudinal.get("stations")
|
|
valid_names = prune_stale_cross_files(cross_dir, stations if isinstance(stations, list) else [])
|
|
files = sorted(cross_dir.glob("cross_*.json"))
|
|
if valid_names:
|
|
files = [path for path in files if path.name in valid_names]
|
|
return files
|
|
|
|
|
|
def _station_map(longitudinal: dict[str, Any]) -> dict[int, dict[str, Any]]:
|
|
stations = longitudinal.get("stations", [])
|
|
if not isinstance(stations, list):
|
|
return {}
|
|
return {
|
|
round(float(station.get("chainage_m", 0))): station
|
|
for station in stations
|
|
if isinstance(station, dict)
|
|
}
|
|
|
|
|
|
def _design_root(project_root: Path) -> Path:
|
|
return project_root / _STAGE_DIR
|
|
|
|
|
|
def _read_manifest(project_root: Path) -> dict[str, Any]:
|
|
path = _design_root(project_root) / "manifest.json"
|
|
if not path.is_file():
|
|
return {"drawings": {}}
|
|
payload = _read_json(path)
|
|
return payload if isinstance(payload.get("drawings"), dict) else {"drawings": {}}
|
|
|
|
|
|
def _write_manifest(project_root: Path, manifest: dict[str, Any]) -> None:
|
|
stage_root = _design_root(project_root)
|
|
stage_root.mkdir(parents=True, exist_ok=True)
|
|
path = stage_root / "manifest.json"
|
|
temporary = path.with_suffix(".tmp")
|
|
temporary.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
temporary.replace(path)
|