Merge remote-tracking branch 'origin/sub_laptop_7' into sub_laptop_1

This commit is contained in:
2026-10-18 09:30:40 +09:00
20 changed files with 674 additions and 50 deletions
+23 -4
View File
@@ -5,12 +5,12 @@
새 노선에서 `STRUCTURE_ROUTE_DRIFT_MAX_M` 넘게 멀면 「노선에서 멀어짐」 표시만(버리지 않음).
- `fill_structure_coordinates` — 저장 때 노선 위 기준 자리로 x · y 채움(자리가 그대로면 옛 좌표
지킴) · 옛 자료는 읽을 때 없는 것만 채움.
지킴 · 보낸 좌표가 그 누가거리를 낳으면 살림 · 410) · 옛 자료는 읽을 때 없는 것만 채움.
- `follow_structures` — 좌표 → 새 노선 누가거리 · 구간 끝은 같은 만큼 밂 · 노선과의 거리.
짝 = TS `common_util/common_util_route_follow.ts` `followStructures`(구조물) · `projectPipePoints`
(관 — 파이썬 본체 = `common_util_drainage_pipes.project_pipe_points`) · 거울 시험 =
`resources/tester/test_344_2_route_pairs.py`.
(관 — 파이썬 본체 = `common_util_drainage_pipes.project_pipe_points`) · `placeOnLine`(좌표 낡음
판정) · 거울 시험 = `resources/tester/test_344_2_route_pairs.py` · `test_410_follow_xy.py`.
"""
from __future__ import annotations
@@ -20,6 +20,7 @@ from typing import Any, Iterable, Sequence
from shapely.geometry import LineString, Point
from common_util.common_util_structure_station import (
SAME_STATION_M,
route_general_stations,
snap_existing_station,
snap_structure_station,
@@ -41,6 +42,20 @@ 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]],
@@ -51,7 +66,9 @@ def fill_structure_coordinates(
"""구조물 x · y 를 노선 위 기준 자리(`chainage_m`)로 채운다(제자리) · 채운 수.
`previous` = 저장 전 정본 {structure_id: 항목} — 자리(누가거리 셋)가 그대로면 옛 좌표를 지킨다
(노선이 바뀌어 멀어진 땅 자리를 다음 저장이 노선 위로 덮지 않게). `only_missing` = 읽을 때.
(노선이 바뀌어 멀어진 땅 자리를 다음 저장이 노선 위로 덮지 않게). 보낸 좌표가 그 누가거리를
낳으면(지금 노선 따라감 = 제자리) 그 좌표를 살린다(410 · 옮긴 자리 · 따라간 땅 자리) — 낡은
좌표(누가거리만 옮김)는 노선 위로. TS 짝 = `placeOnLine`. `only_missing` = 읽을 때.
"""
line = _line(xy)
if line is None:
@@ -68,6 +85,8 @@ def fill_structure_coordinates(
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
+28 -7
View File
@@ -9,8 +9,10 @@
* - `followStructures` ↔ 파이썬 `common_util_route_follow.follow_structures`(+ 0 ~ 연장 자름 ·
* 노선과 거리 · `STRUCTURE_ROUTE_DRIFT_MAX_M` 넘으면 「노선에서 멀어짐」 · 버리지 않음).
* - `projectOnPolyline` = shapely `LineString.project`(가장 가까운 첫 선분) · `distance`.
* - `placeOnLine` · `rebasePipeDraft` 는 TS 만 — 좌표 없는 세션 관 초안(정본 PUT 꼴)을 따라가기 전 자리로 ·
* 노선을 저장한 [저장]이 서버가 따라 옮긴 관 위에 초안을 얹음(349-0u).
* - `placeOnLine` 좌표 낡음 판정 ↔ 파이썬 `fill_structure_coordinates`(좌표가 그 누가거리를 낳으면 살림 ·
* 아니면 누가거리의 노선 위 자리 · 410) · 거울 시험 = resources/tester/test_410_follow_xy.py.
* - `placeOnLine` 의 좌표 없는 관 · `rebasePipeDraft` 는 TS 만 — 좌표 없는 세션 관 초안(정본 PUT 꼴)을 따라가기
* 전 자리로 · 노선을 저장한 [저장]이 서버가 따라 옮긴 관 위에 초안을 얹음(349-0u).
* 거울 시험 = resources/tester/test_344_2_route_pairs.py.
* ========================================================================== */
@@ -53,18 +55,35 @@ export function projectOnPolyline(
return best && walked > 0 ? { ...best, length_m: walked } : null;
}
/** 좌표 없는 관 · 구조물(세션 초안 = 정본 PUT 꼴 · 좌표 뺌)에 자리를 붙인 사본 — 같은 id · 같은 누가거리인
* `saved`(정본 · 좌표 있음)가 있으면 그 좌표(안 옮긴 것 = 서버 따라감과 같은 자리) · 없으면 그 누가거리의
* `line` 위 자리(연장 밖은 끝 선분 위로 자름). 좌표가 있거나 누가거리가 없으면 그대로(같은 객체). */
/** 좌표가 그 누가거리를 낳는가 — `line` 에서 따라감(같은 선 = 제자리)이 누가거리를 안 옮김(410 · 파이썬
* `fill_structure_coordinates` 같은 규칙). 아니면 좌표가 낡음(마커 · 방향키 · 폼으로 누가거리만 옮김). */
const holdsStation = (
item: FollowingStructure,
line: ReadonlyArray<XY>,
intervalM: number | null,
): boolean => {
const [moved] = followStructures([item], line, Infinity, intervalM);
return (
moved.drift_m === null || Math.abs(moved.item.chainage_m! - item.chainage_m!) <= SAME_STATION_M
);
};
/** 관 · 구조물에 자리를 붙인 사본 — 좌표가 그 누가거리를 낳으면 그대로(같은 객체 · 410) · 좌표 없음(세션 관
* 초안 = 정본 PUT 꼴 · 좌표 뺌)이면 같은 id · 같은 누가거리인 `saved`(정본 · 앞 따라감 · 좌표 있음)의 좌표(안
* 옮긴 것 = 서버 따라감과 같은 자리) · 그 밖(옮김 · 좌표 낡음)은 그 누가거리의 `line` 위 자리(연장 밖은 끝 선분
* 위로 자름 = 옮긴 자리가 새 기준). 누가거리가 없으면 그대로. `intervalM` = site 맞춤 간격(`followStructures`). */
export function placeOnLine<S extends FollowingStructure & { id?: string | null }>(
items: ReadonlyArray<S>,
line: ReadonlyArray<XY>,
saved: ReadonlyArray<FollowingStructure & { id?: string | null }> = [],
intervalM: number | null = null,
): S[] {
const byId = new Map(saved.map((item) => [item.id, item]));
return items.map((item) => {
if ((item.x != null && item.y != null) || item.chainage_m == null) return item;
const was = item.id ? byId.get(item.id) : undefined;
if (item.chainage_m == null) return item;
const placed = item.x != null && item.y != null;
if (placed && holdsStation(item, line, intervalM)) return item;
const was = !placed && item.id ? byId.get(item.id) : undefined;
if (
was?.x != null &&
was.y != null &&
@@ -196,11 +215,13 @@ export const placedOnLine = (
list: unknown,
line: ReadonlyArray<[number, number]>,
saved: FollowingStructure[],
intervalM: number | null = null,
): unknown =>
Array.isArray(list)
? placeOnLine(
list as FollowingStructure[],
line.map(([x, y]) => ({ x, y })),
saved,
intervalM,
)
: list;