fix(B05 노선 추천 엔진): 원지반 기울기 참고만 · 삼각 없음 · 측점 필수 · 합치기 멈춤 노선 토공 +10 % · 토공 고르기 위 3 개만 균형 종단 (419-1)
- 못 지킨 곳 수 = R · 계류 · 측점(원지반 기울기는 목록 · grade 수에 참고) · 후보 거름 · 큰 R 판 · 합치기 · 측점 맞춤의 기울기 막기 걷음 · 탐색 링크 상한 남김 - 꺾임만 → 곡선(155° 이상도) 또는 교점 지움 · 측점 맞춤 곡선 끔 · 늦춤 단계 걷음 - 맞추기 전 선(unsnapped) 걷음 · 시종점 기울기 ends_grade_pct · ends_over_limit - 검증_sub4 첫 갈래 122.6 s → 23.8 s · 곡선 33 → 16~18 · 세 갈래 다른 선 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
@@ -9,12 +9,13 @@
|
||||
|
||||
거리 한도(이탈 m) 없음 — 「원래 선」이 없어 한도가 대신하던 것을 직접 잰다(`Guard`):
|
||||
· 탐색 복도(`CORRIDOR_M`) 안 · 계류 버퍼 안 나란히(`along`) 안 늘어남 · 최소 R(별표2) 이상.
|
||||
· 옮긴 조각이 같은 자리 옛 조각보다 지반 기울기 상한을 더 넘지 않음(`GRADE_NOISE`) · 토공
|
||||
가늠이 `EARTH_TOL` 넘게 늘지 않음 · 비중 계산이면 예상노선 이격도(`keeping_offset`).
|
||||
· 옮긴 조각 토공 가늠(띠 · 빠른 거름)이 `EARTH_TOL` 넘게 늘지 않음 · 비중 계산이면 예상노선
|
||||
이격도(`keeping_offset`). 원지반 기울기는 참고만(419 · 막지 않음 · 자리 잣대에도 안 씀).
|
||||
· 합친 곡선은 MC 가 측점 사이 어디에 떨어져도 IP 를 밀어 측점에 놓은 꼴이 잼을 지나야 함
|
||||
(`PLACE_OFFSETS_M` · 측점 맞춤이 합친 곡선을 늘 측점에 · 합치기 풀기 없음).
|
||||
· 합치기는 노선 전체 잣대(`route_metrics`)도 — 넘은 곳 수 · 기울기 넘은 곳 안 늘어남 · 최대
|
||||
기울기 `GRADE_TOL_PCT` · 절성토 `TOTAL_EARTH_TOL` 안(합치기 전 선 기준 · 되풀이해도 누적 안 됨).
|
||||
· 합치기 멈춤 = 노선 전체 잣대 — 못 지킨 곳 수(R · 계류) 안 늘어남 · 노선 토공이 합치기 전 선보다
|
||||
`TOTAL_EARTH_TOL` 넘게 늘지 않음(엔진 상수 · 사용자 옵션 아님 · 되풀이해도 누적 안 됨). 토공 =
|
||||
바뀐 측점 횡단 차이(`cross_earthwork_delta` · 420) · 없으면 노선 잣대 토공(띠 가늠).
|
||||
큰 R(`..._Radius.widen`) · 측점 맞춤(`..._Station`)도 같은 `Guard` 로 잰다(거리 한도 대신).
|
||||
"""
|
||||
|
||||
@@ -27,7 +28,7 @@ from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import shapely
|
||||
from shapely.geometry import LineString
|
||||
from shapely.geometry import LineString, Point
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Metrics import SAMPLE_M, STATION_M, _resample
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Radius import _flat
|
||||
@@ -52,11 +53,8 @@ Route = tuple[Any, dict[str, Any]]
|
||||
# 옮긴 조각 토공 가늠(탐색 몫 · 옆경사 단면)이 옛 조각보다 늘어도 되는 몫 · 바닥(㎥ · 짧은 조각)
|
||||
EARTH_TOL = 0.05
|
||||
EARTH_FLOOR_M3 = 5.0
|
||||
# 옮긴 조각 지반 기울기가 상한을 더 넘어도 「안 늘어남」으로 볼 잴 잡음(비율 · 0.2 %p)
|
||||
GRADE_NOISE = 0.002
|
||||
# 합치기 노선 전체 잣대 — 최대 기울기(%p) · 절성토 늘어도 되는 몫
|
||||
GRADE_TOL_PCT = 0.5
|
||||
TOTAL_EARTH_TOL = 0.02
|
||||
# 합치기 멈춤 — 노선 토공(횡단)이 합치기 전 선보다 늘어도 되는 몫(곡선 수 ↔ 토공 · 요구 254)
|
||||
TOTAL_EARTH_TOL = 0.10
|
||||
# R 후보 수(자리 R ~ 최소 R 사이 · 큰 것부터)
|
||||
RADIUS_STEPS = 12
|
||||
# 합친 곡선 MC 가 앞 측점에서 이만큼(m) 뒤일 때 — 앞 측점으로 당기거나(−t) 뒤 측점으로 미는(20 − t)
|
||||
@@ -150,27 +148,19 @@ class Guard:
|
||||
self.seen[key] = found
|
||||
return found
|
||||
|
||||
def judge(
|
||||
self, local: list[Xy], reference: list[Xy]
|
||||
) -> tuple[bool, tuple[bool, float, float, float]]:
|
||||
"""(지나나, 자리 고르기 잣대) — 지남 = 복도 안 · 계류 나란히 · 기울기 넘음 안 늘어남 · 토공
|
||||
`EARTH_TOL` 안(`offset` 이면 예상노선 이격도 같은 폭). 잣대(작을수록 좋음) = 복도 밖 ·
|
||||
늘어난 계류 나란히(m) · 더 넘은 기울기 · 늘어난 토공 가늠(㎥)."""
|
||||
excess, earth, along, inside, offset = self._look(local)
|
||||
old_excess, old_earth, old_along, _, old_offset = self._look(reference)
|
||||
def judge(self, local: list[Xy], reference: list[Xy]) -> tuple[bool, tuple[bool, float, float]]:
|
||||
"""(지나나, 자리 고르기 잣대) — 지남 = 복도 안 · 계류 나란히 · 토공 `EARTH_TOL` 안(`offset`
|
||||
이면 예상노선 이격도 같은 폭) · 원지반 기울기는 안 봄(419). 잣대(작을수록 좋음) = 복도 밖 ·
|
||||
늘어난 계류 나란히(m) · 늘어난 토공 가늠(㎥)."""
|
||||
_, earth, along, inside, offset = self._look(local)
|
||||
_, old_earth, old_along, _, old_offset = self._look(reference)
|
||||
passed = (
|
||||
inside
|
||||
and excess <= old_excess + GRADE_NOISE
|
||||
and along <= old_along + 1e-6
|
||||
and earth <= old_earth * (1 + EARTH_TOL) + EARTH_FLOOR_M3
|
||||
and (not self.offset or offset <= old_offset * (1 + EARTH_TOL) + EARTH_FLOOR_M3)
|
||||
)
|
||||
cost = (
|
||||
not inside,
|
||||
round(max(0.0, along - old_along), 3),
|
||||
round(max(0.0, excess - old_excess), 4),
|
||||
earth - old_earth,
|
||||
)
|
||||
cost = (not inside, round(max(0.0, along - old_along), 3), earth - old_earth)
|
||||
return passed, cost
|
||||
|
||||
def ok(self, local: list[Xy], reference: list[Xy]) -> bool:
|
||||
@@ -243,15 +233,8 @@ def _earth(metrics: dict[str, Any]) -> float:
|
||||
return metrics["cut_m3"] + metrics["fill_m3"]
|
||||
|
||||
|
||||
def _worse(new: dict[str, Any], base: dict[str, Any]) -> bool:
|
||||
"""노선 전체 잣대가 합치기 전보다 나빠짐(허용 폭 밖)."""
|
||||
over, was = new["over_limit"], base["over_limit"]
|
||||
return (
|
||||
over["total"] > was["total"]
|
||||
or over["grade"] > was["grade"]
|
||||
or new["max_grade_pct"] > base["max_grade_pct"] + GRADE_TOL_PCT
|
||||
or _earth(new) > _earth(base) * (1 + TOTAL_EARTH_TOL)
|
||||
)
|
||||
# (합치기 전 선, 합친 선, 바뀐 자리 누가거리(앞 선 기준 s0, s1)) → 노선 토공 차(㎥ · 횡단)
|
||||
EarthDelta = Callable[[PlannedPolyline, PlannedPolyline, tuple[float, float]], float]
|
||||
|
||||
|
||||
def merge_same_side(
|
||||
@@ -259,14 +242,21 @@ def merge_same_side(
|
||||
guard: Guard,
|
||||
min_radius_m: float,
|
||||
measure: Callable[[PlannedPolyline], dict[str, Any]],
|
||||
cross_earthwork_delta: EarthDelta | None = None,
|
||||
) -> Route:
|
||||
"""같은 쪽 이웃 꺾임 합치기를 되풀이한 선과 잰 값 · 합칠 것 없으면 받은 그대로."""
|
||||
"""같은 쪽 이웃 꺾임 합치기를 되풀이한 선과 잰 값 · 합칠 것 없으면 받은 그대로.
|
||||
|
||||
멈춤 = 못 지킨 곳 수(R · 계류 · 원지반 기울기 뺌) 늘어남 · 노선 토공이 합치기 전 선의
|
||||
1 + `TOTAL_EARTH_TOL` 넘음. `cross_earthwork_delta` 주면 토공 = 바뀐 측점 횡단 차이 누적 ·
|
||||
없으면 노선 잣대 토공(`measure` 절 + 성)."""
|
||||
outline, base = route
|
||||
if len(outline.curves) < 2:
|
||||
return route
|
||||
points, flags, radii = _flat(outline)
|
||||
points = [(round(x, 4), round(y, 4)) for x, y in points]
|
||||
current = route
|
||||
limit = _earth(base) * (1 + TOTAL_EARTH_TOL)
|
||||
earth = _earth(base) # 지금 선 토공 — 차이를 누적
|
||||
blocked: set[tuple[Xy, ...]] = set()
|
||||
while True:
|
||||
reference = _Reference(current[0].vertices)
|
||||
@@ -287,9 +277,9 @@ def merge_same_side(
|
||||
LineString(local), LineString(reference.window(local)), densify=0.1
|
||||
)
|
||||
)
|
||||
found.append((gap, k, cross, radius))
|
||||
found.append((gap, k, cross, radius, local))
|
||||
merged = None
|
||||
for _, k, cross, radius in sorted(found):
|
||||
for _, k, cross, radius, local in sorted(found, key=lambda item: item[:2]):
|
||||
trial_points = points[:k] + [cross] + points[k + 2 :]
|
||||
trial_flags = flags[:k] + [True] + flags[k + 2 :]
|
||||
trial_radii = radii[:k] + [radius] + radii[k + 2 :]
|
||||
@@ -301,11 +291,16 @@ def merge_same_side(
|
||||
radii=trial_radii,
|
||||
)
|
||||
metrics = measure(line)
|
||||
if _worse(metrics, base):
|
||||
if cross_earthwork_delta is None:
|
||||
total = _earth(metrics)
|
||||
else:
|
||||
window = sorted(reference.line.project(Point(xy)) for xy in (local[0], local[-1]))
|
||||
total = earth + cross_earthwork_delta(current[0], line, (window[0], window[1]))
|
||||
if metrics["over_limit"]["total"] > base["over_limit"]["total"] or total > limit:
|
||||
blocked.add(tuple(points[k - 1 : k + 3]))
|
||||
continue
|
||||
merged = (trial_points, trial_flags, trial_radii, (line, metrics))
|
||||
merged = (trial_points, trial_flags, trial_radii, (line, metrics), total)
|
||||
break
|
||||
if merged is None:
|
||||
return current
|
||||
points, flags, radii, current = merged
|
||||
points, flags, radii, current, earth = merged
|
||||
|
||||
Reference in New Issue
Block a user