Files
Aislo/common_util/common_util_route_follow.py
T
eomsangdonandClaude Opus 5.5 2b7b129566 fix(493-3 화면 ↔ 저장 자리): 추가 측점 = 끌기 동안 땅 위 좌표를 들고 따라감 — 걸음마다 정수 m 에서 다시 놓아 [저장] 자리와 2~4 m 어긋나던 것
- 원인 = 노선 초안 follow 가 추가 측점을 누가거리만 들고 걸음마다 앞 선 위 정수 m 자리에서 다시 투영(반올림 누적) · 서버 [저장]은 정본 좌표에서 한 번 · 관 · 구조물은 좌표를 들고 가 차 0
- followPlusGround(common_util_route_follow) — 같은 누가거리 저장분 · 앞 걸음 좌표에서 따라감 · follow 단계가 걸음 출력마다 좌표 줄을 기억(B05_Profile_Pipeline_Grade)
- 첫 걸음 = 서버 정본 좌표 — GET plus-stations 에 stations(누가거리 · x · y) · 화면 savedPlusRows · 초안 둔 채 열기 = 초안 노선 자리
- 거울 시험 test_493_3_follow_screen_save(직선 · 곡선 교점 · R · 교점 더함 · 지움 · 앞 교점 여러 걸음 오감 = 관 · 구조물 · 추가 측점 화면 자리 = 서버 자리 · 고치기 전 셈 대조 = 2 m 넘게 어긋남)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:45:41 +09:00

160 lines
7.2 KiB
Python

"""노선이 바뀔 때 구조물이 땅 위 자리(좌표)를 따라감 — 누가거리를 다시 매김(344-2).
사용자 확정(10-16 · 341 결정 2): 관 · 구조물의 자리 정본 = 좌표(x · y) · 누가거리는 그 자리를 지금
노선에서 읽은 값. 노선이 바뀌면 좌표를 새 노선에 투영해 누가거리를 다시 매기고(정수 m · 234-1),
새 노선에서 `STRUCTURE_ROUTE_DRIFT_MAX_M` 넘게 멀면 「노선에서 멀어짐」 표시만(버리지 않음).
- `fill_structure_coordinates` — 저장 때 노선 위 기준 자리로 x · y 채움(자리가 그대로면 옛 좌표
지킴 · 보낸 좌표가 그 누가거리를 낳으면 살림 · 410) · 옛 자료는 읽을 때 없는 것만 채움.
- `follow_structures` — 좌표 → 새 노선 누가거리 · 구간 끝은 같은 만큼 밂 · 노선과의 거리
(셈 = TS `followStructures` · Node 입구 `facility.follow` · 472-4 ①).
- `route_outside` — 노선 밖(시점 앞 · 종점 뒤 0.51 m 넘음) · 측점 세울 때 셈(459 · 정본에 안 씀).
짝 = TS `common_util/common_util_route_follow.ts` `followStructures`(구조물) · `projectPipePoints`
(관 — 파이썬 본체 = `common_util_drainage_pipes.project_pipe_points`) · `placeOnLine`(좌표 낡음
판정) · 거울 시험 = `resources/tester/test_344_2_route_pairs.py` · `test_410_follow_xy.py` ·
끌기 여러 걸음 화면 자리 = [저장] 한 번 자리(관 · 구조물 · 추가 측점) = `test_493_3_follow_screen_save.py`.
"""
from __future__ import annotations
import math
from typing import Any, Iterable, Sequence
from shapely.geometry import LineString, Point
from common_util.common_util_calc_runner import run_calc
from common_util.common_util_structure_station import (
SAME_STATION_M,
route_general_stations,
snap_existing_station,
snap_structure_station,
)
from config.config_system import SECTION_STATION_INTERVAL_M, STRUCTURE_ROUTE_DRIFT_MAX_M
_POSITION_KEYS = ("chainage_m", "start_m", "end_m")
_FOLLOW_KEYS = ("placement", *_POSITION_KEYS, "x", "y")
#: 노선 밖 판정(m) — 투영이 시점 앞 · 종점 뒤로 이만큼 넘으면 측점 안 세움(459 · TS 짝).
ROUTE_OUTSIDE_M = 0.51
def _line(xy: Sequence[Sequence[float]]) -> LineString | None:
points = [(float(point[0]), float(point[1])) for point in xy]
if len(points) < 2:
return None
line = LineString(points)
return line if line.length > 0 else None
def route_outside(
x: float | None, y: float | None, xy: Sequence[Sequence[float]], tol_m: float = ROUTE_OUTSIDE_M
) -> bool:
"""노선 밖인가(459) — 가장 가까운 자리가 시점 · 종점이고 그 끝 선분 연장으로 `tol_m` 넘게 나감
(옆으로 먼 것은 아님) · 좌표 없으면 거짓. TS `routeOutside` 짝 · 측점 계획에서 뺌."""
line = _line(xy)
if line is None or x is None or y is None:
return False
at = float(line.project(Point(float(x), float(y))))
if 0.0 < at < float(line.length):
return False
coords = list(line.coords)
(ax, ay), (bx, by) = (coords[0], coords[1]) if at <= 0.0 else (coords[-1], coords[-2])
span = math.hypot(bx - ax, by - ay)
if span <= 0.0:
return False
return -((float(x) - ax) * (bx - ax) + (float(y) - ay) * (by - ay)) / span > tol_m
def _positions(item: Any) -> tuple[Any, ...]:
return tuple(getattr(item, key, None) for key in _POSITION_KEYS)
def _holds_station(item: Any, line: LineString) -> bool:
"""좌표가 그 기준 측점을 낳는가 — 같은 노선 따라감이 누가거리를 안 옮김(TS `holdsStation`)."""
at = float(line.project(Point(float(item.x), float(item.y))))
length = float(line.length)
target = (
snap_existing_station(at, route_general_stations(length))
if getattr(item, "placement", None) == "site"
else snap_structure_station(at, [length])
)
anchor = float(item.chainage_m)
shift = target - anchor
return abs(min(max(anchor + shift, 0.0), length) - anchor) <= SAME_STATION_M
def fill_structure_coordinates(
items: Iterable[Any],
xy: Sequence[Sequence[float]],
*,
previous: dict[str, Any] | None = None,
only_missing: bool = False,
) -> int:
"""구조물 x · y 를 노선 위 기준 자리(`chainage_m`)로 채운다(제자리) · 채운 수.
`previous` = 저장 전 정본 {structure_id: 항목} — 자리(누가거리 셋)가 그대로면 옛 좌표를 지킨다
(노선이 바뀌어 멀어진 땅 자리를 다음 저장이 노선 위로 덮지 않게). 보낸 좌표가 그 누가거리를
낳으면(지금 노선 따라감 = 제자리) 그 좌표를 살린다(410 · 옮긴 자리 · 따라간 땅 자리) — 낡은
좌표(누가거리만 옮김)는 노선 위로. TS 짝 = `placeOnLine`. `only_missing` = 읽을 때.
"""
line = _line(xy)
if line is None:
return 0
filled = 0
for item in items:
anchor = getattr(item, "chainage_m", None)
if anchor is None:
continue
has_xy = getattr(item, "x", None) is not None and getattr(item, "y", None) is not None
if only_missing and has_xy:
continue
old = (previous or {}).get(getattr(item, "structure_id", None) or "")
if old is not None and _positions(old) == _positions(item) and old.x is not None:
item.x, item.y = old.x, old.y
continue
if has_xy and _holds_station(item, line):
continue
point = line.interpolate(float(anchor))
item.x, item.y = round(float(point.x), 4), round(float(point.y), 4)
filled += 1
return filled
def follow_structures(
items: Iterable[Any],
xy: Sequence[Sequence[float]],
max_drift_m: float = STRUCTURE_ROUTE_DRIFT_MAX_M,
) -> list[tuple[float | None, bool]]:
"""좌표를 새 노선에 투영해 누가거리를 다시 매긴다(제자리) → 항목마다 (노선과 거리, 멀어짐).
기준 자리 = 투영 → 정수 m(노선 끝 안) · 구간 끝(`start_m` · `end_m`)은 같은 만큼 밀고 0 ~ 연장
안으로 자름. 좌표가 없는 옛 자료는 그대로(거리 None) · site 꼴(사토장 · 토취장) = 새 노선 일반
측점 중 가장 가까운 곳(380-3). 셈 = TS `followStructures`(Node 입구 `facility.follow` · 472-4).
"""
items = list(items)
line = _line(xy)
if line is None or not items:
return [(None, False)] * len(items)
rows = [{key: getattr(item, key, None) for key in _FOLLOW_KEYS} for item in items]
results = run_calc(
["facility.follow"],
{
"follow": {
"items": rows,
"line": [[x, y] for x, y in line.coords],
"max_drift_m": max_drift_m,
"interval_m": SECTION_STATION_INTERVAL_M,
}
},
)
if results is None:
raise RuntimeError("구조물 따라감 실패 — 계산 Node 입구(facility.follow)")
out: list[tuple[float | None, bool]] = []
for item, row in zip(items, results["facility.follow"], strict=True):
for key in _POSITION_KEYS:
if getattr(item, key, None) is not None:
setattr(item, key, float(row["item"][key]))
out.append((row["drift_m"], bool(row["far"])))
return out