- 맞춤 규칙 한 벌 — common_util_structure_station(파이썬 · TS 짝 · 거울 시험) - 입력 — 좌측 폼 칸(1 m 단계 · 친 값만) · 끌기 · 우클릭 · 방향키(1 m 걸음) · 배수유역도 관 · B06 관 옮김 · 추가 - 구간형 시작 · 끝도 정수 m · 일반 측점 = 횡단 상세를 읽을 때 등록 - 서버 자동 배치 · 노선 재투영 자리도 정수 m(확정본은 단추로만) - [정수 맞춤] — 구조물 목록 머리 · 확인 창 · 관은 하나씩 폼 [수정] 길 · 계획고 편집 따라감 · 결과 n건 알림 - 찾는 폭 한 값 — 손으로 적은 0.51 을 PIPE_MATCH_M 으로 - 시험 — test_234_1 새로 · 1 m 걸음 · 정수 입력에 맞춘 기대값 고침(219_1 · station_roundtrip) · 헬퍼 대역 보탬 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
81 lines
3.1 KiB
Python
81 lines
3.1 KiB
Python
"""구조물 · 관 기준 측점 = 정수 m · 일반 측점 1 m 안이면 그 측점에 붙음(234-1).
|
|
|
|
TS 짝 = `common_util/common_util_structure_station.ts`(화면 입력 · 끌기 · 방향키 · 정수 맞춤 단추).
|
|
거울 시험 = `resources/tester/test_234_1_structure_station.py`.
|
|
일반 측점 = 격자(간격) · 시종점 — 서버 자동 배치 · 노선 재투영이 쓴다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from collections.abc import Sequence
|
|
from dataclasses import replace
|
|
from typing import TypeVar
|
|
|
|
from common_util.common_util_route_geometry import RouteVertex, interpolate_vertex
|
|
from config.config_system_design import SECTION_STATION_INTERVAL_M
|
|
|
|
_Candidate = TypeVar("_Candidate")
|
|
|
|
# 일반 측점에 붙는 폭(m) — 이 안이면 그 측점 값(끝 측점처럼 정수가 아니어도).
|
|
GENERAL_SNAP_M = 1.0
|
|
_EPS = 1e-6
|
|
|
|
|
|
def general_stations(total_m: float, interval_m: float) -> list[float]:
|
|
"""격자 측점 + 끝 측점(`Sections_Core._chainages` 와 같은 줄)."""
|
|
if not (total_m > 0 and interval_m > 0):
|
|
return [0.0]
|
|
values = [index * interval_m for index in range(int(total_m // interval_m) + 1)]
|
|
if total_m - values[-1] > _EPS:
|
|
values.append(float(total_m))
|
|
return values
|
|
|
|
|
|
def _near_general(value: float, stations: Sequence[float]) -> float | None:
|
|
best: float | None = None
|
|
for station in stations:
|
|
gap = abs(station - value)
|
|
if gap < GENERAL_SNAP_M - _EPS and (best is None or gap < abs(best - value)):
|
|
best = float(station)
|
|
return best
|
|
|
|
|
|
def snap_structure_station(chainage_m: float, stations: Sequence[float]) -> float:
|
|
"""기준 측점 맞춤 — 일반 측점 1 m 안이면 그 측점 · 아니면 정수 m(.5 는 올림) · 정수가 일반
|
|
측점 1 m 안이면 그 측점. 두 번 맞춰도 같다."""
|
|
value = float(chainage_m)
|
|
if not math.isfinite(value):
|
|
return value
|
|
direct = _near_general(value, stations)
|
|
if direct is not None:
|
|
return direct
|
|
whole = float(math.floor(value + 0.5))
|
|
near = _near_general(whole, stations)
|
|
return whole if near is None else near
|
|
|
|
|
|
def route_general_stations(total_m: float) -> list[float]:
|
|
"""노선 길이의 일반 측점 — 서버 측점 간격(`SECTION_STATION_INTERVAL_M`)."""
|
|
return general_stations(total_m, SECTION_STATION_INTERVAL_M)
|
|
|
|
|
|
def snap_candidates(
|
|
candidates: Sequence[_Candidate], vertices: Sequence[RouteVertex]
|
|
) -> list[_Candidate]:
|
|
"""자동 배치 관 자리 맞춤 — 좌표는 맞춘 자리로 다시 찍고, 같은 자리로 모이면 앞선 것만.
|
|
|
|
`candidates` 는 `chainage_m · x · y` 를 가진 dataclass(`StructureCandidate`)."""
|
|
total_m = float(vertices[-1].chainage_m)
|
|
stations = route_general_stations(total_m)
|
|
snapped: list[_Candidate] = []
|
|
taken: set[float] = set()
|
|
for candidate in candidates:
|
|
at = min(snap_structure_station(candidate.chainage_m, stations), total_m)
|
|
if at in taken:
|
|
continue
|
|
taken.add(at)
|
|
x, y, _ = interpolate_vertex(list(vertices), at)
|
|
snapped.append(replace(candidate, chainage_m=at, x=x, y=y))
|
|
return snapped
|