From 72ead694b19ae9d4095a51ce8c255eaf200ab2c0 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 30 Aug 2026 19:38:24 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05):=20=EA=B0=80=EA=B9=8C=EC=9A=B4=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=EC=A0=90=20=EA=B5=AC=EA=B0=84=EC=9D=80=20?= =?UTF-8?q?=EA=B2=A9=EC=9E=90=20=ED=83=90=EC=83=89=20=EC=97=86=EC=9D=B4=20?= =?UTF-8?q?=EC=A7=81=EA=B2=B0=ED=95=B4=20=EC=9B=90=EC=B2=AD=20=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=EB=85=B8=EC=84=A0=EC=9D=84=20=EB=B3=B4=EC=A1=B4?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 계획노선 CSV는 설계 원청이 지정한 기준선이라 프로그램이 바꾸면 안 되는데, 격자 Dijkstra가 제어점 사이에 셀 중심점을 끼워 넣어 평면이 틀어졌다. 제어점은 원좌표로 되박히지만 끼어든 점은 되박히지 않기 때문이다. 원인은 셀 크기와 정점 간격의 기하 관계다. LAS 프로젝트는 비용면 셀이 2.188m(원본 DTM 해상도, 다운샘플 배수 1)라 정점 간격(평균 2.61m·최대 3.25m)이 전부 8이웃 한 걸음에 들어가 중간점이 안 생겼다. 도엽 등고선 프로젝트는 DTM이 1.0m라 배수 2로 셀이 2.0m가 되고, 20쌍이 두 걸음으로 벌어져 22개 점이 끼어들었다(최대 이탈 2.08m, 평면 +4.72m, 곡선 경고 증가). 솔버에 LAS 전용 보존 코드는 없다 — 셀 2.0m에 LAS CSV를 스냅해도 두 걸음 쌍이 16개 생긴다. 제어점 쌍의 평면 거리가 ROUTE_DIRECT_LINK_CELL_FACTOR×셀 이하면 탐색을 건너뛰고 두 칸을 그대로 잇는다. 조밀 기준선(정점 전체가 제어점)은 평면이 원본과 완전히 같아지고, 간격이 문턱을 넘는 구간만 종전대로 탐색한다. 경사·곡선 제약은 이 구간에서 경고로만 남는다. 검증(프로젝트 0aca395f 실데이터, 원본 미변경 사본에서 재탐색): - 셀 2.0m: 정점 156→135, 끼어든 점 22→0, 이탈 2.076→0.000m, 평면 354.84→350.11m(CSV와 동일), 곡선 경고 9→8 - 셀 3.0m: 111→135 정점(같은 칸에 합쳐지던 24개도 살아남음), 이탈 0.000m - LAS 프로젝트 c1bb453f: 재탐색 결과 xy 차 0.000000m — 결과 불변 - pytest 14 passed (직결·같은 칸 미병합·먼 구간 탐색 유지 3건 추가) --- B05_Profile/B05_Profile_Engine_Solver.py | 51 +++++++++++++++--------- config/config_system.py | 3 ++ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/B05_Profile/B05_Profile_Engine_Solver.py b/B05_Profile/B05_Profile_Engine_Solver.py index 25f113d8..804d9c8d 100644 --- a/B05_Profile/B05_Profile_Engine_Solver.py +++ b/B05_Profile/B05_Profile_Engine_Solver.py @@ -23,6 +23,7 @@ from config.config_system import ( FOREST_ROAD_MAX_GRADE, FOREST_ROAD_MIN_CURVE_R_M, ROUTE_DEFAULT_GRADE_CLASS, + ROUTE_DIRECT_LINK_CELL_FACTOR, ROUTE_GRID_RES_M, ROUTE_MAX_COST_CELLS, ROUTE_MAX_GRADE, @@ -390,31 +391,43 @@ def solve_optimal_route( full_path_grid: list[tuple[int, int]] = [] segment_bounds: list[dict[str, Any]] = [] + direct_link_max_m = ROUTE_DIRECT_LINK_CELL_FACTOR * target_res + for i in range(len(sequence) - 1): pt_start = sequence[i] pt_end = sequence[i + 1] r_s, c_s, _ = get_grid_indices(pt_start) r_e, c_e, _ = get_grid_indices(pt_end) - segment = single_segment_dijkstra( - r_s, - c_s, - r_e, - c_e, - x_coords_sub, - y_coords_sub, - z_grid_sub, - valid_mask_sub, - dz_dx, - dz_dy, - ap_list, - weights, - max_grade, - target_res, - min_curve_radius_m, - max_uphill_grade, - max_downhill_grade, - ) + # 제어점 쌍이 문턱보다 가까우면 격자 탐색 없이 직결한다. 두 끝은 어차피 + # 원좌표로 되박히므로 이 구간 평면은 원청 계획노선 그대로 보존된다. + # 같은 칸에 스냅돼도 두 항목을 유지해 정점이 합쳐져 사라지지 않게 한다. + # 경사·곡선반경 제약은 이 구간에선 경고(curve_warning_segments)로만 남는다. + if ( + math.hypot(pt_end["x"] - pt_start["x"], pt_end["y"] - pt_start["y"]) + <= direct_link_max_m + ): + segment = [(r_s, c_s), (r_e, c_e)] + else: + segment = single_segment_dijkstra( + r_s, + c_s, + r_e, + c_e, + x_coords_sub, + y_coords_sub, + z_grid_sub, + valid_mask_sub, + dz_dx, + dz_dy, + ap_list, + weights, + max_grade, + target_res, + min_curve_radius_m, + max_uphill_grade, + max_downhill_grade, + ) if not segment: fp_note = "·금지구역(FP)" if fp_list else "" raise ValueError( diff --git a/config/config_system.py b/config/config_system.py index 8f5b1289..db445c46 100644 --- a/config/config_system.py +++ b/config/config_system.py @@ -261,6 +261,9 @@ ROUTE_AVOID_DEFAULT_RADIUS_M = float(os.getenv("ROUTE_AVOID_DEFAULT_RADIUS_M", " ROUTE_DEFAULT_GRADE_CLASS = os.getenv("ROUTE_DEFAULT_GRADE_CLASS", "trunk") ROUTE_MAX_COST_CELLS = int(os.getenv("ROUTE_MAX_COST_CELLS", "4000000")) ROUTE_REQUIRED_POINT_TOLERANCE_M = float(os.getenv("ROUTE_REQUIRED_POINT_TOLERANCE_M", "1.0")) +# 제어점 쌍이 이 배수×비용면 셀보다 가까우면 격자 탐색 없이 직결한다 — 원청 계획노선 +# 보존 (2026-08-30 사용자 확정. 조밀 기준선은 격자 중간점이 못 끼어들어 평면 불변). +ROUTE_DIRECT_LINK_CELL_FACTOR = float(os.getenv("ROUTE_DIRECT_LINK_CELL_FACTOR", "2.0")) # 임도 종류 — 현행 규칙(별표2)의 3종. `branch`(지선)는 규칙에서 폐지됐으나 기존 # 저장분이 남아 있어 값으로는 계속 받는다(화면 선택지에서는 뺀다, 2026-08-19). ROUTE_GRADE_CLASSES = ("trunk", "fire", "work", "branch")