"""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 collections.abc import Sequence from pathlib import Path from typing import Any import helper_472_1_paths as P def seat_route_moved( alignment: dict[str, Any], born: Sequence[float], held: Sequence[float] = () ) -> list[dict[str, Any]]: """옮긴 시설 측점(`born`)에 427 규칙으로 꼭짓점을 다시 세움(459) — 서버 [저장] 길(Node `carry_seat` 의 seat) 앞뒤 그대로: 그 자리 옛 꼭짓점을 걷고 `_seat_born_nodes`. 파이썬 짝(`Route_Save_Parts.seat_route_moved`) = 운영 부름 0 이라 걷음(511-B B-6) — 거울 기준만 여기(`test_459_route_seat_mirror` · `test_475_1_route_digits`).""" from B05_Profile.B05_Profile_Router_Confirm import _SAME_STATION_M, _seat_born_nodes base = alignment.get("base_pvi") or [] if not born: return base kept = [ node for node in base if all(abs(float(node["chainage_m"]) - value) >= _SAME_STATION_M for value in born) ] return _seat_born_nodes(alignment, kept, list(born), list(held)) 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_policy, changed_span, ) from B05_Profile.B05_Profile_Engine_Grade_Node import carry_seat 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, 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_seat( old_line, points, old_alignment["base_pvi"], old_alignment.get("edits"), stations, half )[0] 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