사용자 피드백 반영(2026-08-03 2차).
유토곡선 일원화
- B05 유토곡선을 B06과 같은 계산(computeMassHaulSeries: 횡단 정식 + 종단 개략
비교)으로 전환. 상세 조회가 미지정 측점에 기본 설계를 즉석 계산해 얹으므로
B05 진입 시점에 이미 정식 곡선 재료가 있다. 표시 토글 sessionStorage 키와
balloon 위치 scope를 B06과 공유해 두 화면이 같은 그림을 유지한다.
- B05 전용 개략 엔진(computeLongitudinalMassHaul) 삭제.
- 유토곡선 펼침 시 종단 그래프:곡선 세로 1:1 분할.
사토장·토취장 자동 선정 삭제
- 임도에는 토취장이 없고 부족분은 설계자가 계획선을 고쳐 맞춘다는 실무 판단에
따라 4옵션(비용/지형안정성/계곡부/임내공간) 엔진·API·UI·config·저장 훅 전부
제거. massHaulPayload/createMassHaulLegend 시그니처 원복.
기본 지반 변경
- 기본 설계 프리뷰·확정 기본값을 토사에서 리핑암 + 예상 암반 경계 0.5m로 변경.
산지 절토는 표토 아래 암이 일반적이라 전량 토사 가정은 물량이 낙관적이다.
발파암은 B06에서 측점별 수정.
계획선 R·선 연결 (배관 구조물 자리)
- 계획선 샘플이 고정 격자로만 평가되어 격자 사이 변화점(배관 측점 승격분)의
모서리를 잘라먹던 결함 수정 — 샘플 집합에 PVI·BVC·EVC를 합집합으로 포함
(프론트 buildAlignment + 서버 build_alignment 동일 규칙). 면적 가중치도
합쳐진 격자로 재계산. 37.3m 변화점 + R=150 수치 검증 통과.
표시 개선
- EP(종점) 잔량 라벨: 곡선 끝점에 "EP {누가토량}㎥" 불투명 판(B05·B06 공통).
- 유토곡선 0선을 붉은 굵은 실선(2.5px)으로, 0 눈금값도 적색.
- 3D 뷰 [측점 가로선] 우측 [측점 라벨] 토글 신설(기본 꺼짐) — 구조물 측점만
"측점번호 구조물명" 스프라이트 표시.
typecheck·vite build·ruff·B03 테스트 통과. 실서버 스모크로 리핑암 기본 프리뷰와
disposal-sites 제거 확인.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
131 lines
5.6 KiB
Python
131 lines
5.6 KiB
Python
"""B05 경로 확정 시 종단 정본 파일에 비정규 측점·상단측 변경을 병합하는 헬퍼.
|
|
|
|
라우터(`B05_wf2_Route_Router.py`)의 confirm 엔드포인트가 호출한다. 파일 기반 병합이라
|
|
재확정해도 중복이 생기지 않으며, 모든 호출은 비치명적(실패해도 경로 확정은 진행)이다.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
import aiomysql
|
|
|
|
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
|
from B05_wf2_Route.B05_wf2_Route_Engine_Sections import generate_irregular_sections
|
|
from B05_wf2_Route.B05_wf2_Route_Engine_Sections_Core import SectionGenerationOptions
|
|
from B05_wf2_Route.B05_wf2_Route_Repository import get_surface_crs_epsg
|
|
from B05_wf2_Route.B05_wf2_Route_Schema import RouteConfirmRequest
|
|
from B06_wf3_ProfileCross.B06_wf3_ProfileCross_Repository import (
|
|
get_latest_section_options,
|
|
get_longitudinal_section,
|
|
)
|
|
from common_util.common_util_json import atomic_write_json
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
|
|
|
|
def _section_options_from_stored(stored: dict[str, Any] | None) -> SectionGenerationOptions:
|
|
"""저장된 종횡단 옵션(단일 소스)만으로 옵션을 재구성한다(확정 시 비정규 측점 샘플링용)."""
|
|
defaults = SectionGenerationOptions()
|
|
stored = stored or {}
|
|
return SectionGenerationOptions(
|
|
station_interval_m=stored.get("station_interval_m") or defaults.station_interval_m,
|
|
cross_half_width_m=stored.get("cross_half_width_m") or defaults.cross_half_width_m,
|
|
cross_sample_interval_m=stored.get("cross_sample_interval_m")
|
|
or defaults.cross_sample_interval_m,
|
|
long_sample_interval_m=stored.get("long_sample_interval_m")
|
|
or defaults.long_sample_interval_m,
|
|
include_endpoint=defaults.include_endpoint,
|
|
)
|
|
|
|
|
|
def _merge_uphill_overrides_into_longitudinal(
|
|
project_root: Path, longitudinal_file_path: str, overrides: list[dict[str, Any]]
|
|
) -> None:
|
|
"""종단 정본 파일 stations의 uphill_side를 사용자 변경값으로 덮어쓴다.
|
|
|
|
solve가 자동 판정한 상단측(=측구 방향)을 3D 램프 클릭으로 바꾼 경우, 확정 시점에
|
|
정본에 반영해 B06이 값만 읽으면 되게 한다. 사용자 지정임을 소스 필드로 남긴다.
|
|
"""
|
|
if not overrides:
|
|
return
|
|
root = project_root.resolve()
|
|
path = (root / longitudinal_file_path).resolve()
|
|
if root not in path.parents or not path.is_file():
|
|
return
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
stations = data.get("stations")
|
|
if not isinstance(stations, list):
|
|
return
|
|
by_chainage = {round(float(item["chainage_m"]), 3): str(item["side"]) for item in overrides}
|
|
changed = False
|
|
for station in stations:
|
|
side = by_chainage.get(round(float(station.get("chainage_m", -1.0)), 3))
|
|
if side is None:
|
|
continue
|
|
station["uphill_side"] = side
|
|
station["uphill_side_source"] = "user"
|
|
changed = True
|
|
if changed:
|
|
data["stations"] = stations
|
|
atomic_write_json(path, data)
|
|
|
|
|
|
def _merge_irregular_into_longitudinal(
|
|
project_root: Path, longitudinal_file_path: str, irregular_stations: list[dict[str, Any]]
|
|
) -> None:
|
|
"""종단 정본 파일의 stations에 비정규 측점을 병합한다(기존 비정규는 교체·정렬).
|
|
|
|
B06 상세는 종단 파일의 stations를 읽고 그에 맞는 cross 파일만 노출하므로, 이 병합과
|
|
(엔진이 이미 쓴) cross 파일만으로 다음 페이지에 횡단이 나타난다. 계획선·규칙 측점·표고
|
|
샘플은 손대지 않는다. 파일 기반이라 재확정해도 중복이 생기지 않는다.
|
|
"""
|
|
root = project_root.resolve()
|
|
path = (root / longitudinal_file_path).resolve()
|
|
if root not in path.parents or not path.is_file():
|
|
return
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
stations = data.get("stations")
|
|
if not isinstance(stations, list):
|
|
return
|
|
regular = [station for station in stations if station.get("kind") != "irregular"]
|
|
merged = regular + list(irregular_stations)
|
|
merged.sort(key=lambda station: float(station.get("chainage_m", 0.0)))
|
|
data["stations"] = merged
|
|
atomic_write_json(path, data)
|
|
|
|
|
|
async def _append_irregular_cross_sections(
|
|
connection: aiomysql.Connection,
|
|
project_id: UUID,
|
|
route: dict[str, Any],
|
|
request: RouteConfirmRequest,
|
|
) -> None:
|
|
"""확정 시 비정규 측점의 횡단을 생성해 종단 파일에 병합한다(파일 기반, 비치명적 호출용)."""
|
|
stored_path = await get_project_storage_relative_path(connection, project_id)
|
|
project_root = Path(resolve_stored_project_path(stored_path))
|
|
stored_options = await get_latest_section_options(connection, project_id)
|
|
crs_epsg = await get_surface_crs_epsg(connection, project_id, request.surface_model_id)
|
|
irregular_stations = await asyncio.to_thread(
|
|
generate_irregular_sections,
|
|
project_root,
|
|
str(route["route_data_path"]),
|
|
request.filter_key,
|
|
request.method,
|
|
request.smooth,
|
|
extra_stations=request.extra_stations(),
|
|
options=_section_options_from_stored(stored_options),
|
|
crs=f"EPSG:{crs_epsg}" if crs_epsg is not None else None,
|
|
)
|
|
if not irregular_stations:
|
|
return
|
|
longitudinal = await get_longitudinal_section(connection, project_id, route["id"])
|
|
if longitudinal:
|
|
await asyncio.to_thread(
|
|
_merge_irregular_into_longitudinal,
|
|
project_root,
|
|
str(longitudinal["longitudinal_file_path"]),
|
|
irregular_stations,
|
|
)
|