feat(B05): 곡선·직선 지우고 더하기 + 반지름 못박기
사용자 지시(2026-09-07) — 「r과 직선 삭제나 추가가 있어야 하지 않을까?」
편집을 **꺾임점 목록 + 자리마다 곡선 켬끔 + 반지름** 셋으로 표현함.
· 직선 삭제·추가 = 목록에서 점을 빼거나 더하기(빼면 앞뒤 직선이 하나로
합쳐지고, 직선 위에 더하면 둘로 갈리며 그 자리에 곡선이 생김)
· 곡선 삭제·추가 = `curve` 를 끄고 켜기(끄면 직선이 그대로 꺾임)
· 반지름 변경 = `radius_m` 못박기(없으면 서버가 고름)
`POST /route/replan` 의 정점마다 `curve`·`radius_m` 을 받게 함.
편집값이 오면 **묶지 않음** — 사용자가 곡선 하나로 본 것을 임의로 합치면
손잡이가 사라지기 때문. 편집값은 점과 짝이라 중복 제거도 함께 함.
시험 tmp/tests/test_route_polyline_edit.py 7건 — 곡선 삭제·추가, 반지름
못박기, **반지름을 바꿔도 교각점은 안 움직임**(「주변 직선 각도 구속」),
직선 삭제·추가. pytest 427 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -582,6 +582,8 @@ def build_planned_polyline(
|
||||
min_radius_m: float,
|
||||
hairpin_min_radius_m: float = 10.0,
|
||||
simplify: bool = True,
|
||||
curve_flags: list[bool] | None = None,
|
||||
radii: list[float | None] | None = None,
|
||||
) -> PlannedPolyline:
|
||||
"""점 묶음을 계획노선 폴리라인으로 바꾼다.
|
||||
|
||||
@@ -595,8 +597,33 @@ def build_planned_polyline(
|
||||
⚠ 이 갈래가 없으면 [확인]을 누를 때마다 선이 깎인다(2026-09-07 실측: 정점 136 → 134 →
|
||||
119, 노드 22 → 21). 원호 점이 섞인 폴리라인을 다시 단순화하면 꺾임점이 조금씩 지워지고,
|
||||
그 결과로 만든 폴리라인을 또 단순화하기 때문이다. **단순화는 원본 점군에서 한 번만.**
|
||||
|
||||
`curve_flags`·`radii` 는 **사용자 편집을 그대로 받는 자리**다(2026-09-07 사용자 지시:
|
||||
「r과 직선 삭제나 추가가 있어야 하지 않을까」). 점마다 짝을 이룬다.
|
||||
· `curve_flags[i] = False` → 그 자리에 **곡선을 두지 않는다**(곡선 삭제). 직선이 그대로
|
||||
꺾인다. 다시 True 로 주면 곡선이 돌아온다(곡선 추가).
|
||||
· `radii[i]` → 그 곡선의 반지름을 **그 값으로 못박는다**(R 변경). 없으면 예정노선에
|
||||
맞춰 고르거나 법정 하한을 쓴다.
|
||||
· 직선을 지우거나 더하는 것은 **점 목록 자체**로 표현된다 — 점을 빼면 앞뒤 직선이 하나로
|
||||
합쳐지고, 직선 위에 점을 더하면 둘로 갈리며 그 자리에 곡선이 생긴다.
|
||||
편집값이 주어지면 **묶지 않는다** — 사용자가 곡선 하나로 본 것을 임의로 합치면 손잡이가
|
||||
사라지기 때문이다.
|
||||
"""
|
||||
deduped = dedupe_points(points)
|
||||
# 편집값은 점과 짝이므로 **함께** 중복을 걸러야 어긋나지 않는다.
|
||||
edited = curve_flags is not None or radii is not None
|
||||
flags = list(curve_flags) if curve_flags is not None else [True] * len(points)
|
||||
given = list(radii) if radii is not None else [None] * len(points)
|
||||
flags += [True] * (len(points) - len(flags))
|
||||
given += [None] * (len(points) - len(given))
|
||||
deduped: list[tuple[float, float]] = []
|
||||
kept_flags: list[bool] = []
|
||||
kept_radii: list[float | None] = []
|
||||
for point, flag, radius_value in zip(points, flags, given):
|
||||
if deduped and _distance(deduped[-1], point) <= DUPLICATE_TOLERANCE_M:
|
||||
continue
|
||||
deduped.append(point)
|
||||
kept_flags.append(bool(flag))
|
||||
kept_radii.append(radius_value)
|
||||
cleaned = simplify_to_nodes(deduped) if simplify else deduped
|
||||
if len(cleaned) < 3:
|
||||
nodes = [RouteNode(x=x, y=y) for x, y in cleaned]
|
||||
@@ -612,9 +639,13 @@ def build_planned_polyline(
|
||||
cleaned[index - 1], cleaned[index], cleaned[index + 1]
|
||||
)
|
||||
|
||||
runs = _curve_runs(cleaned)
|
||||
if node_indices is not None:
|
||||
runs = _split_wide_runs(runs, cleaned, deduped, node_indices, min_radius_m)
|
||||
if edited:
|
||||
# 사용자가 손댄 목록 — 묶지 않고 자리마다 하나씩 본다. 곡선을 지운 자리는 뺀다.
|
||||
runs = [(index, index) for index in range(1, len(cleaned) - 1) if kept_flags[index]]
|
||||
else:
|
||||
runs = _curve_runs(cleaned)
|
||||
if node_indices is not None:
|
||||
runs = _split_wide_runs(runs, cleaned, deduped, node_indices, min_radius_m)
|
||||
vertices: list[tuple[float, float]] = [cleaned[0]]
|
||||
curves: list[RouteCurve] = []
|
||||
cursor = 0 # 아직 선에 안 실은 첫 꺾임점
|
||||
@@ -649,7 +680,10 @@ def build_planned_polyline(
|
||||
# 반지름은 **법정 하한 이상에서 예정노선에 가장 가까운 값**(2026-09-07 사용자 확정).
|
||||
# 자리가 모자라면 하한 아래로 줄이되 **막지 않고 위반으로 표시**한다(기존 규칙).
|
||||
radius = min_radius_m
|
||||
if node_indices is not None:
|
||||
forced = kept_radii[first] if edited and first == last else None
|
||||
if forced is not None and forced > 0:
|
||||
radius = float(forced) # 사용자가 못박은 반지름 — 맞추지 않고 그대로 쓴다.
|
||||
elif node_indices is not None:
|
||||
samples = deduped[node_indices[first - 1] : node_indices[last + 1] + 1]
|
||||
radius = _fit_radius_m(
|
||||
samples,
|
||||
|
||||
Reference in New Issue
Block a user