refactor(346-3): 노선 저장 횡단 이월 측점 열쇠 = common_util_station_key 한 벌

- station_keys 가 station_key(sub2 짝)를 부름 · 관 pipe:<id>(id 없으면 at:<0.01 m>) · 구조물 structure:<id> · 같은 열쇠 둘째는 at:(B06 stationKeysOf 와 같음)
- 파일 차례 관 열쇠 없앰 · 시험 test_346_3_route_save 고쳐 씀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
2026-10-16 11:59:38 +09:00
co-authored by Claude Opus 5.5
parent 6757d1dcb8
commit 3995e91a1c
3 changed files with 59 additions and 47 deletions
+3 -3
View File
@@ -284,10 +284,10 @@ async def _carry_cross(
new_long = _read_json(project_root / relative) new_long = _read_json(project_root / relative)
_, new_structures = load_structures(str(project_root)) _, new_structures = load_structures(str(project_root))
old_keys = station_keys( old_keys = station_keys(old_long.get("stations") or [], old_pipes, old_structures, interval_m)
old_long.get("stations") or [], list(enumerate(old_pipes)), old_structures, interval_m new_keys = station_keys(
new_long.get("stations") or [], [pipe for _, pipe in pairs], new_structures, interval_m
) )
new_keys = station_keys(new_long.get("stations") or [], pairs, new_structures, interval_m)
designs = { designs = {
round(float(row["chainage_m"]), 3): row["design"] round(float(row["chainage_m"]), 3): row["design"]
for row in old_designs for row in old_designs
+42 -35
View File
@@ -8,9 +8,9 @@ DB · 파일 없이 값만 받아 값을 돌려준다(시험이 그대로 부름
(사용자 관 편집 지킴). (사용자 관 편집 지킴).
- 새 세류선 교차점(옛 노선 위에 없던 자리)에 관이 없으면 기본 관 하나(`_base_pipes` 결과를 받음). - 새 세류선 교차점(옛 노선 위에 없던 자리)에 관이 없으면 기본 관 하나(`_base_pipes` 결과를 받음).
- 관 최대 간격 보충 = 옛 노선에서 벗어난 자리의 **오목 지점**(지반 종단 낮은 곳)에만 — 격자 없음. - 관 최대 간격 보충 = 옛 노선에서 벗어난 자리의 **오목 지점**(지반 종단 낮은 곳)에만 — 격자 없음.
- 측점 열쇠(계약 `design_pipeline_types.ts` 머리) = 일반 `grid:k`(누가거리 ÷ 측점 간격 반올림) · - 측점 열쇠 = `common_util_station_key.station_key` 한 벌(sub2 짝) — 일반 `grid:k` ·
관 `pipe:<id>` · 구조물 `structure:<id>`(구간 시작 · 끝은 `:start` · `:end`) · 시종점 관 `pipe:<id>` · 구조물 `structure:<id>` · 시종점 `end:BP` · `end:EP` ·
`end:BP` · `end:EP`. 관 id = 저장 파일 차례(노선 저장 한 번 안에서 옛 · 새가 같음). id 없으면 `at:<누가거리 0.01 m>`.
- 횡단 이월 = 같은 열쇠 측점에 `CARRY_INPUT_KEYS` 일곱 · 관 · 구조물 열쇠면 - 횡단 이월 = 같은 열쇠 측점에 `CARRY_INPUT_KEYS` 일곱 · 관 · 구조물 열쇠면
`STRUCTURE_ADJUST_KEYS` 도(구조물 조정은 구조물 따라) · 열쇠가 사라진 측점 수 = `STRUCTURE_ADJUST_KEYS` 도(구조물 조정은 구조물 따라) · 열쇠가 사라진 측점 수 =
「노선 밖 고침」. 「노선 밖 고침」.
@@ -32,6 +32,8 @@ from common_util.common_util_drainage_pipes import (
project_pipe_points, project_pipe_points,
) )
from common_util.common_util_route_geometry import RouteVertex from common_util.common_util_route_geometry import RouteVertex
from common_util.common_util_station_key import chainage_key as station_chainage_key
from common_util.common_util_station_key import station_key
from common_util.common_util_structure_station import plan_stations, snap_structure_station from common_util.common_util_structure_station import plan_stations, snap_structure_station
from config.config_system import DRAINAGE_PIPE_MAX_SPACING_M, DRAINAGE_PIPE_MIN_SPACING_M from config.config_system import DRAINAGE_PIPE_MAX_SPACING_M, DRAINAGE_PIPE_MIN_SPACING_M
@@ -59,7 +61,7 @@ def vertices_of(xy: Sequence[Sequence[float]]) -> list[RouteVertex]:
def follow_pipes( def follow_pipes(
old_pipes: list[PipePoint], vertices: list[RouteVertex] old_pipes: list[PipePoint], vertices: list[RouteVertex]
) -> list[tuple[int, PipePoint]]: ) -> list[tuple[int, PipePoint]]:
"""관이 새 노선 좌표를 따라감 → [(관 id = 옛 차례, 새 관)] 누가거리 순 · 옛 목록 그대로.""" """관이 새 노선 좌표를 따라감 → [(옛 차례 · 더한 관 -1, 새 관)] 누가거리 순 · 옛 목록 그대로."""
pairs = [(index, copy.deepcopy(pipe)) for index, pipe in enumerate(old_pipes)] pairs = [(index, copy.deepcopy(pipe)) for index, pipe in enumerate(old_pipes)]
for _, pipe in pairs: for _, pipe in pairs:
project_pipe_points([pipe], vertices) project_pipe_points([pipe], vertices)
@@ -145,49 +147,54 @@ def fill_concave_pipes(
added += 1 added += 1
def _facility_keys( def _facility_owners(
pipes: Sequence[tuple[int, PipePoint]], structures: Sequence[Any] pipes: Sequence[PipePoint], structures: Sequence[Any]
) -> dict[float, str]: ) -> dict[float, tuple[str, str | None]]:
"""측점 자리(0.01 m) → 관 · 구조물 열쇠. 같은 자리면 관이 먼저(`plan_stations` 이름 규칙).""" """측점 자리(0.01 m) → (관 · 구조물, id). 같은 자리면 관이 먼저(`plan_stations` 이름 규칙)."""
keys: dict[float, str] = {} owners: dict[float, tuple[str, str | None]] = {}
for pipe in pipes:
def put(marks: list[dict[str, object]], head: str) -> None: for mark in plan_stations([pipe], []):
for mark in marks: owners.setdefault(
role = str(mark.get("role") or "anchor") round(float(mark["chainage_m"]), 2), ("pipe", getattr(pipe, "id", None))
keys.setdefault(
round(float(mark["chainage_m"]), 2), head if role == "anchor" else f"{head}:{role}"
) )
for pipe_id, pipe in pipes:
if pipe_id >= 0:
put(plan_stations([pipe], []), f"pipe:{pipe_id}")
for item in structures: for item in structures:
if getattr(item, "structure_id", None): for mark in plan_stations([], [item]):
put(plan_stations([], [item]), f"structure:{item.structure_id}") owners.setdefault(
return keys round(float(mark["chainage_m"]), 2),
("structure", getattr(item, "structure_id", None)),
)
return owners
def station_keys( def station_keys(
stations: Sequence[dict[str, Any]], stations: Sequence[dict[str, Any]],
pipes: Sequence[tuple[int, PipePoint]], pipes: Sequence[PipePoint],
structures: Sequence[Any], structures: Sequence[Any],
interval_m: float, interval_m: float,
) -> dict[str, float]: ) -> dict[str, float]:
"""측점 열쇠 → 누가거리. 관 · 구조물 자리 > 시종점 > 일반 격자(간격 배수인 것만).""" """측점 열쇠(`common_util_station_key.station_key` 한 벌) → 누가거리.
facility = _facility_keys(pipes, structures)
관 · 구조물 자리 > 시종점 > 일반 격자(간격 배수인 것만) · id 없는 관 · 구조물은 `at:` ·
같은 열쇠가 둘이면 뒤엣것은 `at:`(B06 `stationKeysOf` 와 같음).
"""
owners = _facility_owners(pipes, structures)
values = [float(row["chainage_m"]) for row in stations if row.get("chainage_m") is not None] values = [float(row["chainage_m"]) for row in stations if row.get("chainage_m") is not None]
out: dict[str, float] = {} out: dict[str, float] = {}
for index, chainage in enumerate(values): for index, chainage in enumerate(values):
key = facility.get(round(chainage, 2)) owner = owners.get(round(chainage, 2))
if key is None and index == 0: if owner is not None:
key = "end:BP" key = station_key(owner[0], chainage, interval_m, owner[1])
elif key is None and index == len(values) - 1: elif index == 0:
key = "end:EP" key = station_key("bp", chainage, interval_m)
elif key is None and interval_m > 0: elif index == len(values) - 1:
k = round(chainage / interval_m) key = station_key("ep", chainage, interval_m)
key = f"grid:{k}" if abs(chainage - k * interval_m) < _SAME_M else None elif interval_m > 0 and abs(chainage - round(chainage / interval_m) * interval_m) < _SAME_M:
if key is not None: key = station_key("regular", chainage, interval_m)
out.setdefault(key, chainage) else:
continue
if key in out:
key = f"at:{station_chainage_key(chainage)}"
out.setdefault(key, chainage)
return out return out
+14 -9
View File
@@ -131,31 +131,36 @@ def _structure(sid, type_id, anchor, start=None, end=None):
def test_측점_열쇠_규칙(): def test_측점_열쇠_규칙():
stations = [{"chainage_m": s} for s in (0.0, 20.0, 40.0, 50.0, 60.0, 77.0, 80.0, 95.5)] """`common_util_station_key` 한 벌 — 관 · 구조물 id · id 없으면 at: · 같은 열쇠 둘째는 at:."""
pipes = [(3, PipePoint(chainage_m=50.0))] stations = [{"chainage_m": s} for s in (0.0, 20.0, 33.3, 40.0, 50.0, 60.0, 77.0, 80.0, 95.5)]
with_id = PipePoint(chainage_m=50.0)
with_id.id = "p-7" # sub3 이 넣는 관 id
no_id = PipePoint(chainage_m=33.3)
structures = [_structure("w1", "retaining_wall", 77.0, 60.0, 80.0)] structures = [_structure("w1", "retaining_wall", 77.0, 60.0, 80.0)]
keys = station_keys(stations, pipes, structures, 20.0) keys = station_keys(stations, [with_id, no_id], structures, 20.0)
assert keys == { assert keys == {
"end:BP": 0.0, "end:BP": 0.0,
"grid:1": 20.0, "grid:1": 20.0,
"at:33.30": 33.3, # id 없는 관
"grid:2": 40.0, "grid:2": 40.0,
"pipe:3": 50.0, "pipe:p-7": 50.0,
"structure:w1:start": 60.0, "structure:w1": 60.0, # 구간 시작 측점이 먼저 열쇠를 가짐
"structure:w1": 77.0, "at:77.00": 77.0, # 같은 구조물 열쇠 둘째 — at:
"structure:w1:end": 80.0, "at:80.00": 80.0,
"end:EP": 95.5, "end:EP": 95.5,
} }
assert "pipe:0" not in keys and "pipe:1" not in keys # 파일 차례 열쇠 없음
def test_이월은_열쇠로_누가거리가_밀려도(): def test_이월은_열쇠로_누가거리가_밀려도():
old_keys = { old_keys = {
"end:BP": 0.0, "end:BP": 0.0,
"grid:1": 20.0, "grid:1": 20.0,
"pipe:0": 50.0, "pipe:p-7": 50.0,
"structure:w1": 77.0, "structure:w1": 77.0,
"grid:5": 100.0, "grid:5": 100.0,
} }
new_keys = {"end:BP": 0.0, "grid:1": 20.0, "pipe:0": 57.0, "structure:w1": 84.0} new_keys = {"end:BP": 0.0, "grid:1": 20.0, "pipe:p-7": 57.0, "structure:w1": 84.0}
designs = { designs = {
0.0: {"ground_type": "soil"}, 0.0: {"ground_type": "soil"},
20.0: {"ground_type": "rock", "berm": {"on": True}, "cut_area_m2": 9.0}, 20.0: {"ground_type": "rock", "berm": {"on": True}, "cut_area_m2": 9.0},