fix(B05): 가까운 제어점 구간은 격자 탐색 없이 직결해 원청 계획노선을 보존한다
계획노선 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건 추가)
This commit is contained in:
@@ -23,6 +23,7 @@ from config.config_system import (
|
|||||||
FOREST_ROAD_MAX_GRADE,
|
FOREST_ROAD_MAX_GRADE,
|
||||||
FOREST_ROAD_MIN_CURVE_R_M,
|
FOREST_ROAD_MIN_CURVE_R_M,
|
||||||
ROUTE_DEFAULT_GRADE_CLASS,
|
ROUTE_DEFAULT_GRADE_CLASS,
|
||||||
|
ROUTE_DIRECT_LINK_CELL_FACTOR,
|
||||||
ROUTE_GRID_RES_M,
|
ROUTE_GRID_RES_M,
|
||||||
ROUTE_MAX_COST_CELLS,
|
ROUTE_MAX_COST_CELLS,
|
||||||
ROUTE_MAX_GRADE,
|
ROUTE_MAX_GRADE,
|
||||||
@@ -390,31 +391,43 @@ def solve_optimal_route(
|
|||||||
full_path_grid: list[tuple[int, int]] = []
|
full_path_grid: list[tuple[int, int]] = []
|
||||||
segment_bounds: list[dict[str, Any]] = []
|
segment_bounds: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
direct_link_max_m = ROUTE_DIRECT_LINK_CELL_FACTOR * target_res
|
||||||
|
|
||||||
for i in range(len(sequence) - 1):
|
for i in range(len(sequence) - 1):
|
||||||
pt_start = sequence[i]
|
pt_start = sequence[i]
|
||||||
pt_end = sequence[i + 1]
|
pt_end = sequence[i + 1]
|
||||||
r_s, c_s, _ = get_grid_indices(pt_start)
|
r_s, c_s, _ = get_grid_indices(pt_start)
|
||||||
r_e, c_e, _ = get_grid_indices(pt_end)
|
r_e, c_e, _ = get_grid_indices(pt_end)
|
||||||
|
|
||||||
segment = single_segment_dijkstra(
|
# 제어점 쌍이 문턱보다 가까우면 격자 탐색 없이 직결한다. 두 끝은 어차피
|
||||||
r_s,
|
# 원좌표로 되박히므로 이 구간 평면은 원청 계획노선 그대로 보존된다.
|
||||||
c_s,
|
# 같은 칸에 스냅돼도 두 항목을 유지해 정점이 합쳐져 사라지지 않게 한다.
|
||||||
r_e,
|
# 경사·곡선반경 제약은 이 구간에선 경고(curve_warning_segments)로만 남는다.
|
||||||
c_e,
|
if (
|
||||||
x_coords_sub,
|
math.hypot(pt_end["x"] - pt_start["x"], pt_end["y"] - pt_start["y"])
|
||||||
y_coords_sub,
|
<= direct_link_max_m
|
||||||
z_grid_sub,
|
):
|
||||||
valid_mask_sub,
|
segment = [(r_s, c_s), (r_e, c_e)]
|
||||||
dz_dx,
|
else:
|
||||||
dz_dy,
|
segment = single_segment_dijkstra(
|
||||||
ap_list,
|
r_s,
|
||||||
weights,
|
c_s,
|
||||||
max_grade,
|
r_e,
|
||||||
target_res,
|
c_e,
|
||||||
min_curve_radius_m,
|
x_coords_sub,
|
||||||
max_uphill_grade,
|
y_coords_sub,
|
||||||
max_downhill_grade,
|
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:
|
if not segment:
|
||||||
fp_note = "·금지구역(FP)" if fp_list else ""
|
fp_note = "·금지구역(FP)" if fp_list else ""
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
@@ -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_DEFAULT_GRADE_CLASS = os.getenv("ROUTE_DEFAULT_GRADE_CLASS", "trunk")
|
||||||
ROUTE_MAX_COST_CELLS = int(os.getenv("ROUTE_MAX_COST_CELLS", "4000000"))
|
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"))
|
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`(지선)는 규칙에서 폐지됐으나 기존
|
# 임도 종류 — 현행 규칙(별표2)의 3종. `branch`(지선)는 규칙에서 폐지됐으나 기존
|
||||||
# 저장분이 남아 있어 값으로는 계속 받는다(화면 선택지에서는 뺀다, 2026-08-19).
|
# 저장분이 남아 있어 값으로는 계속 받는다(화면 선택지에서는 뺀다, 2026-08-19).
|
||||||
ROUTE_GRADE_CLASSES = ("trunk", "fire", "work", "branch")
|
ROUTE_GRADE_CLASSES = ("trunk", "fire", "work", "branch")
|
||||||
|
|||||||
Reference in New Issue
Block a user