Files
Aislo/resources/tester/helper_475_1_moved.py
T
eomsangdonandClaude Opus 5.5 7aca51596a fix(475-1 노선 자리): 노선 선 소수 자리 한 벌 — 서버 노선 geojson x · y 4자리(= 계획노선 csv) · 브라우저 초안 그린 선 · 곡선표도 같은 자리
- B05_Profile_Engine._route_geojson x · y 3 → 4자리(z 3자리 그대로) · 짝 머리 표시
- draftLine 그린 선 x · y 4자리 · 곡선 start · apex · end 4자리 · R 3자리(= RouteCurve.as_dict) · 파이썬 round 짝(toFixed)
- 교점 끌기 재현 브라우저 ↔ 서버 차 0.81 → 0.00 ㎥ · 옹벽 + 추가 측점 0.88 → 0.00 ㎥(test_475_1_route_digits)
- 472-1 헬퍼 — 옮김 인자 · 페이지 배선(stationsAt · storedPvi) · 단면 모양 = 따라간 구조물(헛 차 덤 고침) · golden 값 그대로
- 옛 시험 두 곳(346_1) — 안 깎은 선 단언 → 서버 저장 자리 단언
- 관 지문 허용오차 주석 · calculations R1 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:23:23 +09:00

179 lines
8.0 KiB
Python

"""475-1 헬퍼 — 노선 저장 체인(DB 없이) · 노드 하나 옮긴 초안 → 서버 정본 값.
`helper_472_1_paths.server_route` 꼴에 노선 바뀜 갈래를 더함 —
`_write_planned_polyline`(csv 4자리 · 곡선표) → 노선 geojson(`_route_geojson`) → 관 따라감 ·
구조물 따라감 · 추가 측점 → `run_section_generation` → `_carry_profile`(옛 · 새 계획 폴리라인 ·
`carry_base` · `seat_route_moved`) → `_carry_cross` →
상세 읽기 → `_save_tail`. 짝 = `helper_472_1_browser.mjs` 옮김 인자 · `test_475_1_route_digits.py`.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import helper_472_1_paths as P
def server_moved(root: Path, move: dict[str, Any]) -> dict[str, Any]:
from B05_Profile.B05_Profile_Engine import _route_geojson
from B05_Profile.B05_Profile_Engine_AsPlanned import _vertex_z
from B05_Profile.B05_Profile_Engine_Grade import GradeDesignOptions
from B05_Profile.B05_Profile_Engine_Grade_Carry import (
carry_base,
carry_edits,
carry_policy,
changed_span,
)
from B05_Profile.B05_Profile_Engine_Sections import run_section_generation
from B05_Profile.B05_Profile_Engine_Sections_Core import SectionGenerationOptions
from B05_Profile.B05_Profile_Plus_Stations import save_plus_stations
from B05_Profile.B05_Profile_Route_Save_Parts import (
carry_cross_inputs,
follow_pipes,
route_moved_stations,
seat_route_moved,
station_keys,
vertices_of,
)
from B05_Profile.B05_Profile_Router import _apply_alignment_edits
from B05_Profile.B05_Profile_Router_Replan import _vertices_of, _write_planned_polyline
from B05_Profile.B05_Profile_Structures_Repository import load_structures
from B05_Profile.B05_Profile_Structures_Router import follow_structures_to_route
from B06_Section.B06_Section_Engine_Design import ROUTE_CARRY_KEYS
from B06_Section.B06_Section_Router import _read_section_detail
from B06_Section.B06_Section_Router_Confirm import _rowless_station_chainages
from B06_Section.B06_Section_Router_Design import (
CARRY_INPUT_KEYS,
STRUCTURE_ADJUST_KEYS,
attach_default_designs,
compute_default_designs,
)
from common_util.common_util_drainage_pipes import (
pipe_points_path_in,
read_pipe_points_file,
route_signature,
)
from common_util.common_util_json import atomic_write_json
from common_util.common_util_route_geometry import (
planned_route_initial_path,
planned_route_working_path,
)
saved = P.read("detail.json")
summary = P.read("summary.json")["longitudinal"]["data"]
options = {k: v for k, v in summary["options"].items() if k != "extra_stations"}
old_line = _vertices_of(planned_route_working_path(root)) or _vertices_of(
planned_route_initial_path(root)
)
old_geo = [
[c[0], c[1]]
for c in json.loads((root / P.ROUTE).read_text(encoding="utf-8"))["geometry"]["coordinates"]
]
old_pipes = read_pipe_points_file(pipe_points_path_in(root))
if move.get("structures"):
atomic_write_json(
root / "B05_Profile" / "route" / "structures.json",
{"revision": 1, "structures": move["structures"], "definition_version": None},
)
old_structures = load_structures(str(root))[1]
# 노선 저장 = 초안 노드 그대로(브라우저 `flattenServerPlan` 초안 · 헬퍼 출력 draft_nodes)
draft = move["draft"]
working = planned_route_working_path(root)
_write_planned_polyline(
working,
[tuple(n) for n in draft["nodes"]],
12.0,
simplify=False,
curve_flags=list(draft["curveOn"]),
radii=list(draft["curveRadius"]),
station_snap=draft["stationSnap"],
station_snap_step=draft["stationSnapStep"],
locks=list(draft["curveLock"]),
arcs=list(draft["curveArc"]),
)
points = _vertices_of(working)
if move.get("structures"):
follow_structures_to_route(root, P.ROUTE, [{"x": x, "y": y} for x, y in points])
z = _vertex_z(root, "classification", "dtm", True, [tuple(p) for p in points])
atomic_write_json(root / P.ROUTE, _route_geojson([[x, y, h] for (x, y), h in zip(points, z)]))
verts = vertices_of(points)
pairs = follow_pipes(old_pipes, verts)
doc = json.loads(pipe_points_path_in(root).read_text(encoding="utf-8"))
doc["route_signature"] = route_signature(verts)
doc["points"] = [p.as_dict() for _, p in pairs]
atomic_write_json(pipe_points_path_in(root), doc)
if move.get("plus"):
save_plus_stations(root, move["plus"], points)
run_section_generation(
root,
P.ROUTE,
*P.SURFACE,
options=SectionGenerationOptions(**options),
grade_options=GradeDesignOptions(**summary["grade_options"]),
crs=f"EPSG:{P.read('context.json')['crs_epsg']}",
)
# _carry_profile — 옛 · 새 계획 폴리라인
old_alignment = saved["longitudinal"]["profile_alignment"]
longitudinal = json.loads((root / P.LONG).read_text(encoding="utf-8"))
alignment = longitudinal.get("profile_alignment") or {}
span = changed_span([tuple(p) for p in old_line], [tuple(p) for p in points])
if span is None:
base, edits = old_alignment["base_pvi"], old_alignment.get("edits") or {}
else:
stations = [float(r["chainage_m"]) for r in longitudinal.get("stations") or []]
half = float((alignment.get("policy") or {}).get("station_interval_m") or 20.0) / 2
base = carry_base(old_line, points, old_alignment["base_pvi"], alignment["base_pvi"])
edits = carry_edits(
old_line, points, old_alignment["base_pvi"], old_alignment.get("edits"), stations, half
)
alignment["policy"] = carry_policy(old_line, points, old_alignment, alignment)
alignment["base_pvi"] = base
if span is not None:
born = route_moved_stations(longitudinal.get("stations") or [], old_line, points)
held = [float(k) for k in (edits.get("station_offsets") or {})]
alignment["base_pvi"] = seat_route_moved(alignment, born, held)
longitudinal["profile_alignment"] = alignment
atomic_write_json(root / P.LONG, longitudinal)
alignment, _s, _d = _apply_alignment_edits(root, P.LONG, edits)
# _carry_cross
new_long = json.loads((root / P.LONG).read_text(encoding="utf-8"))
_, structures = load_structures(str(root))
interval = float(options["station_interval_m"])
old_keys = station_keys(
saved["longitudinal"]["stations"],
[p for _, p in follow_pipes(old_pipes, vertices_of(old_geo))],
old_structures,
interval,
)
new_keys = station_keys(
new_long.get("stations") or [], [p for _, p in pairs], structures, interval
)
designs = {
round(float(s["chainage_m"]), 3): s["design"]
for s in saved["cross_sections"]
if isinstance(s.get("design"), dict)
}
inputs, adjusts, _dropped = carry_cross_inputs(
old_keys, new_keys, designs, (*CARRY_INPUT_KEYS, *ROUTE_CARRY_KEYS), STRUCTURE_ADJUST_KEYS
)
chainages = _rowless_station_chainages(root, P.LONG, [])
entries = compute_default_designs(root, P.LONG, chainages, None, inputs)
by_m = {round(at, 3): {**d, **adjusts.get(round(at, 3), {})} for at, d in entries}
detail = _read_section_detail(root, P.LONG, P.ROUTE)
for section in detail["cross_sections"]:
hit = next(
(d for at, d in by_m.items() if abs(at - float(section["chainage_m"])) < 0.01), None
)
if hit is not None:
section["design"] = hit
attach_default_designs(detail["longitudinal"], detail["cross_sections"], root, None)
keys = P._keys(detail["longitudinal"], detail["cross_sections"], root)
sections, haul = P._save_tail(detail, root)
out = P.values_of(
sections, keys, haul, alignment.get("warnings") or [], alignment.get("violations") or []
)
out["route_line"] = [list(p) for p in points]
return out