260719_0
This commit is contained in:
@@ -60,8 +60,8 @@ export interface RouteSolveResponse {
|
||||
metrics: Record<string, unknown>;
|
||||
required_points_ok: boolean;
|
||||
route_data_path: string;
|
||||
longitudinal_length_m: number;
|
||||
cross_section_count: number;
|
||||
longitudinal_length_m: number | null;
|
||||
cross_section_count: number | null;
|
||||
}
|
||||
|
||||
/** 경로 확정 결과 (RouteConfirmResponse) */
|
||||
|
||||
@@ -206,3 +206,29 @@ async def confirm_route(connection: aiomysql.Connection, route_id: int) -> None:
|
||||
"UPDATE routes SET status = 'CONFIRMED' WHERE id = %s",
|
||||
(route_id,),
|
||||
)
|
||||
|
||||
|
||||
async def get_surface_crs_epsg(
|
||||
connection: aiomysql.Connection, project_id: UUID, surface_model_id: int
|
||||
) -> int | None:
|
||||
"""종횡단 메타데이터용 좌표계를 조회한다.
|
||||
|
||||
지표면 모델 crs_epsg가 NULL이면 같은 프로젝트 input_files의 감지된
|
||||
좌표계로 폴백한다 (B06 get_confirmed_route_context와 동일 규칙).
|
||||
"""
|
||||
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
||||
await cursor.execute(
|
||||
"""
|
||||
SELECT COALESCE(
|
||||
(SELECT sm.crs_epsg FROM surface_models sm WHERE sm.id = %s),
|
||||
(SELECT f.crs_epsg
|
||||
FROM input_files f
|
||||
WHERE f.project_id = %s AND f.crs_epsg IS NOT NULL
|
||||
ORDER BY f.id DESC
|
||||
LIMIT 1)
|
||||
) AS crs_epsg
|
||||
""",
|
||||
(surface_model_id, str(project_id)),
|
||||
)
|
||||
row = await cursor.fetchone()
|
||||
return int(row["crs_epsg"]) if row and row["crs_epsg"] is not None else None
|
||||
|
||||
@@ -20,6 +20,7 @@ from B05_wf2_Route.B05_wf2_Route_Repository import (
|
||||
create_route_statistics,
|
||||
get_latest_route,
|
||||
get_route_points,
|
||||
get_surface_crs_epsg,
|
||||
insert_route_points,
|
||||
)
|
||||
from B05_wf2_Route.B05_wf2_Route_Schema import (
|
||||
@@ -198,35 +199,51 @@ async def solve_route(
|
||||
)
|
||||
raise
|
||||
|
||||
sections = await asyncio.to_thread(
|
||||
run_section_generation,
|
||||
project_root,
|
||||
design["route_data_path"],
|
||||
request.filter_key,
|
||||
request.method,
|
||||
request.smooth,
|
||||
options=_section_options(request),
|
||||
)
|
||||
await connection.begin()
|
||||
# 종횡단 생성 실패는 저장된 경로를 무효화하지 않으므로 비치명적으로 처리한다.
|
||||
longitudinal_length_m: float | None = None
|
||||
cross_section_count: int | None = None
|
||||
try:
|
||||
await delete_sections_for_route(connection, route_id)
|
||||
await create_longitudinal_section(
|
||||
connection,
|
||||
project_id=project_id,
|
||||
route_id=route_id,
|
||||
data=sections["longitudinal"]["data"],
|
||||
longitudinal_file_path=sections["longitudinal"]["file_path"],
|
||||
crs_epsg = await get_surface_crs_epsg(
|
||||
connection, project_id, request.surface_model_id
|
||||
)
|
||||
await insert_cross_sections(
|
||||
connection,
|
||||
project_id=project_id,
|
||||
route_id=route_id,
|
||||
sections=sections["cross_sections"],
|
||||
sections = await asyncio.to_thread(
|
||||
run_section_generation,
|
||||
project_root,
|
||||
design["route_data_path"],
|
||||
request.filter_key,
|
||||
request.method,
|
||||
request.smooth,
|
||||
options=_section_options(request),
|
||||
crs=f"EPSG:{crs_epsg}" if crs_epsg is not None else None,
|
||||
)
|
||||
await connection.commit()
|
||||
await connection.begin()
|
||||
try:
|
||||
await delete_sections_for_route(connection, route_id)
|
||||
await create_longitudinal_section(
|
||||
connection,
|
||||
project_id=project_id,
|
||||
route_id=route_id,
|
||||
data=sections["longitudinal"]["data"],
|
||||
longitudinal_file_path=sections["longitudinal"]["file_path"],
|
||||
)
|
||||
await insert_cross_sections(
|
||||
connection,
|
||||
project_id=project_id,
|
||||
route_id=route_id,
|
||||
sections=sections["cross_sections"],
|
||||
)
|
||||
await connection.commit()
|
||||
except Exception:
|
||||
await connection.rollback()
|
||||
raise
|
||||
longitudinal_length_m = sections["longitudinal"]["data"]["length_m"]
|
||||
cross_section_count = len(sections["cross_sections"])
|
||||
except Exception:
|
||||
await connection.rollback()
|
||||
raise
|
||||
logger.exception(
|
||||
"B05 종횡단 생성 실패 (경로는 저장됨): project_id=%s route_id=%s",
|
||||
project_id,
|
||||
route_id,
|
||||
)
|
||||
|
||||
return RouteSolveResponse(
|
||||
project_id=str(project_id),
|
||||
@@ -235,8 +252,8 @@ async def solve_route(
|
||||
metrics=metrics,
|
||||
required_points_ok=solver["required_points_ok"],
|
||||
route_data_path=design["route_data_path"],
|
||||
longitudinal_length_m=sections["longitudinal"]["data"]["length_m"],
|
||||
cross_section_count=len(sections["cross_sections"]),
|
||||
longitudinal_length_m=longitudinal_length_m,
|
||||
cross_section_count=cross_section_count,
|
||||
)
|
||||
except LookupError as exc:
|
||||
async with pool.acquire() as connection, connection.cursor() as cursor:
|
||||
|
||||
@@ -100,8 +100,9 @@ class RouteSolveResponse(BaseModel):
|
||||
metrics: dict[str, Any]
|
||||
required_points_ok: bool
|
||||
route_data_path: str
|
||||
longitudinal_length_m: float
|
||||
cross_section_count: int
|
||||
# 종횡단 생성 실패 시 None (경로 자체는 저장됨)
|
||||
longitudinal_length_m: float | None = None
|
||||
cross_section_count: int | None = None
|
||||
|
||||
|
||||
class RouteConfirmResponse(BaseModel):
|
||||
|
||||
@@ -97,6 +97,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
const profilePanel = createRouteProfilePanel();
|
||||
let confirmedSurface: SurfaceModelSummary | null = null;
|
||||
let latest: RouteLatestResponse | null = null;
|
||||
let defaultCrossHalfWidth = 0;
|
||||
let routeReady = false;
|
||||
let stale = false;
|
||||
let restoring = true;
|
||||
@@ -154,7 +155,10 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
|
||||
function renderSections(detail: SectionDetailResponse): void {
|
||||
profilePanel.render(detail);
|
||||
viewer.renderStationLines(detail.longitudinal.stations, panel.values().crossHalfWidth);
|
||||
viewer.renderStationLines(
|
||||
detail.longitudinal.stations,
|
||||
panel.values().crossHalfWidth ?? defaultCrossHalfWidth,
|
||||
);
|
||||
}
|
||||
|
||||
async function restoreSections(routeId: number): Promise<void> {
|
||||
@@ -239,8 +243,12 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
long_sample_interval_m: values.longSampleInterval,
|
||||
});
|
||||
renderLatest(await fetchLatestRoute(activeProjectId));
|
||||
renderSections(await fetchSectionDetail(activeProjectId, solved.route_id));
|
||||
showToast("최적 경로 계산이 완료되었습니다.", "success");
|
||||
await restoreSections(solved.route_id);
|
||||
if (solved.cross_section_count === null) {
|
||||
showToast("경로는 저장되었지만 종횡단 생성에 실패했습니다.", "error");
|
||||
} else {
|
||||
showToast("최적 경로 계산이 완료되었습니다.", "success");
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : "경로 계산에 실패했습니다.", "error");
|
||||
} finally {
|
||||
@@ -270,6 +278,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
|
||||
fetchSectionContext(activeProjectId),
|
||||
]);
|
||||
confirmedSurface = models.models.find((model) => model.status === "CONFIRMED") ?? null;
|
||||
defaultCrossHalfWidth = sectionContext.defaults.cross_half_width_m;
|
||||
if (!confirmedSurface) {
|
||||
showToast("확정된 지표면 모델이 없습니다.", "error");
|
||||
} else {
|
||||
|
||||
@@ -12,10 +12,10 @@ export interface RoutePanelValues {
|
||||
minUphillGrade: number | null;
|
||||
minDownhillGrade: number | null;
|
||||
allowAvoidPassThrough: boolean;
|
||||
stationInterval: number;
|
||||
crossHalfWidth: number;
|
||||
crossSampleInterval: number;
|
||||
longSampleInterval: number;
|
||||
stationInterval: number | null;
|
||||
crossHalfWidth: number | null;
|
||||
crossSampleInterval: number | null;
|
||||
longSampleInterval: number | null;
|
||||
}
|
||||
|
||||
interface PanelCallbacks {
|
||||
@@ -275,10 +275,10 @@ export function createRoutePanel(callbacks: PanelCallbacks) {
|
||||
minUphillGrade: parseOptional(minUphillGrade),
|
||||
minDownhillGrade: parseOptional(minDownhillGrade),
|
||||
allowAvoidPassThrough: avoidPass.checked,
|
||||
stationInterval: Number(stationInterval.value),
|
||||
crossHalfWidth: Number(crossHalfWidth.value),
|
||||
crossSampleInterval: Number(crossSampleInterval.value),
|
||||
longSampleInterval: Number(longSampleInterval.value),
|
||||
stationInterval: parseOptional(stationInterval),
|
||||
crossHalfWidth: parseOptional(crossHalfWidth),
|
||||
crossSampleInterval: parseOptional(crossSampleInterval),
|
||||
longSampleInterval: parseOptional(longSampleInterval),
|
||||
};
|
||||
},
|
||||
restore(values: Partial<RoutePanelValues>) {
|
||||
|
||||
Reference in New Issue
Block a user