fix(B05): 노선 원천을 한 곳으로 모으고 도엽 서피스를 기본 하나만 만든다
용화 자동 체인이 끝까지 돌았는데 종단 계획선이 PVI 3개 직선으로 나왔다. 관 22개 측점이 92.6~2044.9m 로 트림 전 원본(2,136m) 기준인데 확정 노선은 1,070.4m 라 21개가 노선 밖이었다. 좌표계도 갈려 있었다 — 관은 EPSG 5179(노선 파일), 노선은 5176(프로젝트)에 저장돼 누가거리만 우연히 맞물려 있었다. 원인은 계획노선을 읽는 곳이 흩어져 있는 것이다. 트림·조밀화를 체인 한 곳에만 넣었더니 배수유역·유입·도엽은 원본을 그대로 읽었다. - load_design_route() 신규 — 읽기·좌표계 변환·트림·조밀화를 한 곳에서 끝낸다. 설계 계통(체인·배수유역)은 전부 이 함수를 지난다. - Router_Watershed 도 이 함수를 쓴다. 좌표계가 프로젝트 기준으로 통일돼 관과 노선이 같은 공간에 놓인다. - 도엽 서피스는 SHEET_SURFACE_AUTO_METHODS(기본 laplace 1종)만 자동 생성한다. 여섯 방식을 매번 만들면 WF1 891초 중 655초를 여기서 쓴다. 나머지는 관리자가 화면에서 고를 때 만든다. Router_Inflow 는 노선을 EPSG 라벨용으로만 쓰고 기하를 안 써서 제외했다. Engine_Extent(배경 지도 범위)도 제외 — 지도는 트림 전 전 구간을 덮어야 사용자가 측량 범위 밖을 볼 수 있다. 실측(용화 route 120): 배수유역 노선 2,136m -> 1,070m 격자 889x1435 -> 545x831 1차 영역 538,574㎡ -> 263,170㎡ 관 좌표계 EPSG 5179 -> 5176 관 22개(10개가 노선 밖) -> 11개 전부 노선 안 종단 PVI 3개 -> 13개, 종곡선 1 -> 11, 불균형 97.0% -> 86.2% PVI 측점이 관 측점과 일치한다(반올림 오차 제외). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,11 @@ from B04_PreProcess.B04_PreProcess_Engine_Pipeline import build_all_terrain_mode
|
||||
from B04_PreProcess.B04_PreProcess_Engine_Structurize import structurize_las
|
||||
from common_util.common_util_atomic import atomic_write_npz
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from config.config_system import SURFACE_GROUND_RATIO_WARN, build_surface_model_config
|
||||
from config.config_system import (
|
||||
SHEET_SURFACE_AUTO_METHODS,
|
||||
SURFACE_GROUND_RATIO_WARN,
|
||||
build_surface_model_config,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -262,7 +266,10 @@ def run_surface_analysis(
|
||||
build_sheet_surface_from_route,
|
||||
)
|
||||
|
||||
sheet_models = build_sheet_surface_from_route(project_root, processed_dir, models_dir)
|
||||
# 자동 전처리는 기본 방식 하나만 만든다 — 나머지는 관리자가 화면에서 고를 때.
|
||||
sheet_models = build_sheet_surface_from_route(
|
||||
project_root, processed_dir, models_dir, list(SHEET_SURFACE_AUTO_METHODS)
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("도엽등고선 서피스 생성 실패: %s", exc)
|
||||
|
||||
|
||||
@@ -446,9 +446,16 @@ def build_sheet_surface_model(
|
||||
|
||||
|
||||
def build_sheet_surface_from_route(
|
||||
project_root: Path, processed_dir: Path, models_dir: Path
|
||||
project_root: Path,
|
||||
processed_dir: Path,
|
||||
models_dir: Path,
|
||||
methods: list[str] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""B03 업로드 계획노선 CSV를 찾아 방식별 도엽 서피스를 만든다. 없으면 빈 목록."""
|
||||
"""B03 업로드 계획노선을 찾아 지정한 방식의 도엽 서피스를 만든다. 없으면 빈 목록.
|
||||
|
||||
`methods`를 주지 않으면 `SHEET_SURFACE_METHODS` 전체를 만든다 — 관리자가 화면에서
|
||||
한 방식을 요청할 때 그 목록만 넘긴다.
|
||||
"""
|
||||
from common_util.common_util_route_geometry import (
|
||||
find_planned_route_file,
|
||||
read_planned_route,
|
||||
@@ -464,7 +471,7 @@ def build_sheet_surface_from_route(
|
||||
return []
|
||||
route_xy = np.array([(v.x, v.y) for v in planned.vertices], dtype=np.float64)
|
||||
return build_sheet_surface_model(
|
||||
project_root, processed_dir, models_dir, route_xy, planned.epsg or 5186
|
||||
project_root, processed_dir, models_dir, route_xy, planned.epsg or 5186, methods
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -39,9 +39,11 @@ from B05_Profile.B05_Profile_Repository import get_surface_crs_epsg
|
||||
from common_util.common_util_route_geometry import (
|
||||
StructureCandidate,
|
||||
find_planned_route_file,
|
||||
load_design_route,
|
||||
read_planned_route,
|
||||
)
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from common_util.common_util_surface_confirmation import get_surface_confirmation_params
|
||||
from common_util.common_util_wamis_rainfall import (
|
||||
build_rainfall_table,
|
||||
ensure_contour_cache,
|
||||
@@ -160,30 +162,35 @@ async def _prepare(project_id: UUID) -> dict[str, Any] | JSONResponse:
|
||||
|
||||
노선은 **B03에 업로드된 계획 노선 파일**에서 읽는다 — B05의 확정 경로가 아니다.
|
||||
배수유역 분석은 노선 설계보다 먼저 끝나 있어야 하기 때문이다(2026-07-31 사용자 지시).
|
||||
다만 설계 계통과 **같은 노선**이어야 한다 — `load_design_route()`가 지표면 밖 구간을
|
||||
잘라 프로젝트 좌표계로 돌려준다. 원본을 그대로 쓰면 유역·관이 확정 노선 밖에도
|
||||
찍히고 좌표계마저 갈린다(2026-09-01 실측: 관은 5179, 노선은 5176이었다).
|
||||
"""
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
stored_path = await get_project_storage_relative_path(connection, project_id)
|
||||
epsg = await get_surface_crs_epsg(connection, project_id, 0)
|
||||
surface_params = await get_surface_confirmation_params(connection, str(project_id))
|
||||
|
||||
project_root = Path(resolve_stored_project_path(stored_path))
|
||||
route_file = find_planned_route_file(_route_input_dir(stored_path))
|
||||
if route_file is None:
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"status": "error", "message": "계획 노선 파일이 업로드되지 않았습니다."},
|
||||
)
|
||||
planned = read_planned_route(route_file)
|
||||
planned = await asyncio.to_thread(load_design_route, project_root, surface_params)
|
||||
if planned is None or len(planned.vertices) < 2:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={
|
||||
"status": "error",
|
||||
"message": f"계획 노선 파일을 읽지 못했습니다: {route_file.name}",
|
||||
"message": f"계획 노선을 읽지 못했거나 지표면과 겹치지 않습니다: {route_file.name}",
|
||||
},
|
||||
)
|
||||
|
||||
# 노선 파일이 CRS를 명시하면 그 값을 따른다. 도엽 재투영도 같은 좌표계로 맞춘다.
|
||||
source_crs = f"EPSG:{planned.epsg or epsg or 5186}"
|
||||
# 설계 계통과 같은 프로젝트 좌표계로 맞춘다. 도엽 재투영도 이 좌표계로 간다.
|
||||
source_crs = planned.crs_input or f"EPSG:{epsg or 5186}"
|
||||
to_lonlat_transformer = Transformer.from_crs(source_crs, "EPSG:4326", always_xy=True)
|
||||
to_metric_transformer = Transformer.from_crs("EPSG:4326", source_crs, always_xy=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user