Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -160,12 +160,14 @@ Promise.resolve(
|
||||
|
||||
// ④ 계획고 편집 따라감 — 한꺼번에 여럿 옮김(사라진 둘 · 새 둘)
|
||||
const straightJudge = loadTs("B05_Profile/logic/B05_Profile_Profile_Straight.ts");
|
||||
const curveLaw = loadTs("B05_Profile/logic/B05_Profile_Profile_CurveLaw.ts");
|
||||
const pivot = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Pivot.ts", {
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
});
|
||||
const alignment = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts", {
|
||||
"./B05_Profile_UI_Profile_Pivot": pivot,
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
});
|
||||
const gone = [
|
||||
{ id: "pipe-80.17", chainage_m: 80.17 },
|
||||
|
||||
@@ -21,14 +21,17 @@ function loadTs(file, stubs = {}) {
|
||||
}
|
||||
|
||||
const straightJudge = loadTs("B05_Profile/logic/B05_Profile_Profile_Straight.ts");
|
||||
const curveLaw = loadTs("B05_Profile/logic/B05_Profile_Profile_CurveLaw.ts");
|
||||
const pivot = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Pivot.ts", {
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
});
|
||||
const alignment = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts", {
|
||||
"./B05_Profile_UI_Profile_Pivot": pivot,
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
});
|
||||
const ground = loadTs("B05_Profile/logic/B05_Profile_Grade_Ground.ts", {
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
"./B05_Profile_UI_Profile_Alignment": alignment,
|
||||
});
|
||||
const carry = loadTs("B05_Profile/logic/B05_Profile_Grade_Carry.ts", {
|
||||
|
||||
@@ -22,12 +22,14 @@ function loadTs(file, stubs = {}) {
|
||||
|
||||
const curve = loadTs("B05_Profile/logic/B05_Profile_UI_RouteEdit_Curve.ts");
|
||||
const straightJudge = loadTs("B05_Profile/logic/B05_Profile_Profile_Straight.ts");
|
||||
const curveLaw = loadTs("B05_Profile/logic/B05_Profile_Profile_CurveLaw.ts");
|
||||
const pivot = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Pivot.ts", {
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
});
|
||||
const alignment = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts", {
|
||||
"./B05_Profile_UI_Profile_Pivot": pivot,
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
});
|
||||
const carry = loadTs("B05_Profile/logic/B05_Profile_Grade_Carry.ts", {
|
||||
"./B05_Profile_UI_Profile_Alignment": alignment,
|
||||
|
||||
@@ -21,20 +21,24 @@ function loadTs(file, stubs = {}) {
|
||||
}
|
||||
|
||||
const straightJudge = loadTs("B05_Profile/logic/B05_Profile_Profile_Straight.ts");
|
||||
const curveLaw = loadTs("B05_Profile/logic/B05_Profile_Profile_CurveLaw.ts");
|
||||
const pivot = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Pivot.ts", {
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
});
|
||||
const alignment = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts", {
|
||||
"./B05_Profile_UI_Profile_Pivot": pivot,
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
});
|
||||
const carry = loadTs("B05_Profile/logic/B05_Profile_Grade_Carry.ts", {
|
||||
"./B05_Profile_UI_Profile_Alignment": alignment,
|
||||
});
|
||||
const ground = loadTs("B05_Profile/logic/B05_Profile_Grade_Ground.ts", {
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
"./B05_Profile_UI_Profile_Alignment": alignment,
|
||||
});
|
||||
const grade = loadTs("B05_Profile/logic/B05_Profile_Pipeline_Grade.ts", {
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
"../../common_util/common_util_route_follow": {},
|
||||
"./B05_Profile_UI_Profile_Alignment": alignment,
|
||||
"./B05_Profile_Grade_Carry": carry,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* 432-1 종단곡선 초기값 직선 · 법정 미달 거울 헬퍼 — `logic/B05_Profile_Grade_Ground.ts`
|
||||
* `groundFollowingPlan` · `_UI_Profile_Alignment.ts` `buildAlignment` · `_Profile_CurveLaw.ts` 를
|
||||
* 그 자리에서 트랜스파일해 돌림. 입력 argv[2] JSON 파일 · 표준출력 JSON 한 벌.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const LOGIC = path.join(ROOT, "B05_Profile", "logic");
|
||||
const align = loadTs(path.join(LOGIC, "B05_Profile_UI_Profile_Alignment.ts"));
|
||||
const ground = loadTs(path.join(LOGIC, "B05_Profile_Grade_Ground.ts"));
|
||||
const law = loadTs(path.join(LOGIC, "B05_Profile_Profile_CurveLaw.ts"));
|
||||
const carry = loadTs(path.join(LOGIC, "B05_Profile_Grade_Carry.ts"));
|
||||
|
||||
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const plan = ground.groundFollowingPlan(input.ground);
|
||||
const base = {
|
||||
policy: input.policy,
|
||||
onlyExplicitCurves: false,
|
||||
basePvi: input.basePvi,
|
||||
chainage: input.chainage,
|
||||
ground: input.groundZ,
|
||||
stations: input.stations.map((s) => ({ station_id: null, chainage_m: s })),
|
||||
};
|
||||
const cases = input.edits.map((edits) => {
|
||||
const al = align.buildAlignment(base, edits);
|
||||
return {
|
||||
curves: al.curves.map((c) => [c.chainage_m, c.r_m, c.l_m]),
|
||||
shortfalls: law.verticalCurveShortfalls(al.curves, input.policy),
|
||||
warnings: al.warnings,
|
||||
};
|
||||
});
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
pvi: plan ? plan.pvi : null,
|
||||
keys: plan ? plan.noCurveKeys : null,
|
||||
cases,
|
||||
consts: [law.DEFAULT_CURVE_LENGTH_M, law.INITIAL_STRAIGHT_RADIUS_M],
|
||||
carried: input.carry
|
||||
? carry.carryNoCurveKeys(
|
||||
input.carry.old_xy,
|
||||
input.carry.new_xy,
|
||||
input.carry.old_keys,
|
||||
input.carry.ground_keys,
|
||||
input.carry.ground_pvi,
|
||||
)
|
||||
: null,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,289 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""432-1 종단곡선 기본 L 20 m · 법정 미달 경고 · 초기값 R > 500 m 꼭짓점 = 곡선 없음(요구 262).
|
||||
|
||||
파이썬 `B05_Profile_Engine_Grade_CurveLaw.py` · `design_ground_following_profile` ·
|
||||
`build_alignment`
|
||||
(정책 `no_curve_keys`) ↔ TS `logic/B05_Profile_Profile_CurveLaw.ts` · `_Grade_Ground.ts`
|
||||
`groundFollowingPlan` · `_UI_Profile_Alignment.ts` 거울(`helper_432_1_curve_law.cjs`).
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_432_1_curve_law.py -q
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: E402
|
||||
from B05_Profile.B05_Profile_Engine_Grade_Alignment import ( # noqa: E402
|
||||
AlignmentPolicy,
|
||||
build_alignment,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_Grade_Carry import ( # noqa: E402
|
||||
carry_base,
|
||||
carry_no_curve_keys,
|
||||
carry_policy,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_Grade_CurveLaw import initial_no_curve_keys # noqa: E402
|
||||
from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402
|
||||
design_ground_following_profile,
|
||||
)
|
||||
from config.config_system import FOREST_ROAD_PROFILE_ALIGNMENT # noqa: E402
|
||||
|
||||
# 20 m 마디 기울기(%) — 꼭짓점 대수차 1 · 6 · 0.5 · 7.5 · 0.5 · 7.5 · 5.5 · 0.5 · 3 %
|
||||
GRADES = [2.0, 3.0, 9.0, 9.5, 2.0, 2.5, 10.0, 4.5, 4.0, 1.0]
|
||||
STATIONS = [20.0 * i for i in range(len(GRADES) + 1)]
|
||||
NO_CURVE = ["20.000", "60.000", "100.000", "160.000", "180.000"]
|
||||
CURVED = [40.0, 80.0, 120.0, 140.0]
|
||||
|
||||
|
||||
def _vertex_z() -> list[float]:
|
||||
z = [100.0]
|
||||
for grade in GRADES:
|
||||
z.append(z[-1] + grade / 100.0 * 20.0)
|
||||
return z
|
||||
|
||||
|
||||
def _longitudinal() -> dict:
|
||||
zs = _vertex_z()
|
||||
samples = [
|
||||
{"chainage_m": float(s), "elevation_m": float(np.interp(s, STATIONS, zs))}
|
||||
for s in range(0, int(STATIONS[-1]) + 1)
|
||||
]
|
||||
return {"samples": samples, "stations": [{"chainage_m": s} for s in STATIONS]}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def initial():
|
||||
longitudinal = _longitudinal()
|
||||
options = resolve_grade_options("trunk", terrain_type="normal")
|
||||
alignment, profile = design_ground_following_profile(longitudinal, options)
|
||||
return longitudinal, options, alignment, profile
|
||||
|
||||
|
||||
def _rebuild(alignment: dict, edits: dict, policy: dict | None = None) -> dict:
|
||||
longitudinal = _longitudinal()
|
||||
return build_alignment(
|
||||
base_s=np.array([n["chainage_m"] for n in alignment["base_pvi"]]),
|
||||
base_z=np.array([n["elevation_m"] for n in alignment["base_pvi"]]),
|
||||
chainage=np.array([row["chainage_m"] for row in longitudinal["samples"]]),
|
||||
ground=np.array([row["elevation_m"] for row in longitudinal["samples"]]),
|
||||
stations=longitudinal["stations"],
|
||||
policy=AlignmentPolicy.from_dict(policy or alignment["policy"]),
|
||||
edits=edits,
|
||||
)
|
||||
|
||||
|
||||
EDITS = [
|
||||
{},
|
||||
{"station_offsets": {"60.000": 0.1}}, # 그 꼭짓점 손 편집 → 기본 곡선
|
||||
{"station_offsets": {"80.000": 0.2}}, # 이웃 손 편집 → 60 · 100 은 얼린 채
|
||||
{"curve_radii": {"100.000": 800.0}}, # R 넣음 → 곡선
|
||||
]
|
||||
|
||||
|
||||
# 노선 [저장] — 끝 노드만 옮김(100 m 까지 같은 자리) · 옛 열쇠(추천) · 새 지반 열쇠
|
||||
NEW_TOTAL = 100.0 + float(np.hypot(100.0, 30.0))
|
||||
NEW_STATIONS = [20.0 * i for i in range(11)] + [round(NEW_TOTAL, 3)]
|
||||
CARRY = {
|
||||
"old_xy": [(0.0, 0.0), (100.0, 0.0), (200.0, 0.0)],
|
||||
"new_xy": [(0.0, 0.0), (100.0, 0.0), (200.0, 30.0)],
|
||||
"old_keys": NO_CURVE,
|
||||
"ground_keys": ["140.000", "180.000"],
|
||||
"ground_pvi": [{"chainage_m": s, "elevation_m": 0.0} for s in NEW_STATIONS],
|
||||
}
|
||||
# 같은 자리 옛 꼭짓점 열쇠(20 · 60 · 100) + 바뀐 자리 새 지반 열쇠(140 · 180) · 옛 160 은 버림
|
||||
CARRIED = ["20.000", "60.000", "100.000", "140.000", "180.000"]
|
||||
|
||||
|
||||
def _curve_at(alignment: dict) -> list[float]:
|
||||
return [round(c["chainage_m"], 3) for c in alignment["curves"]]
|
||||
|
||||
|
||||
def test_기본_L_20_문턱_500_config_TS_짝():
|
||||
assert FOREST_ROAD_PROFILE_ALIGNMENT["default_curve_length_m"] == 20.0
|
||||
assert FOREST_ROAD_PROFILE_ALIGNMENT["initial_straight_radius_m"] == 500.0
|
||||
law = (ROOT / "B05_Profile/logic/B05_Profile_Profile_CurveLaw.ts").read_text(encoding="utf-8")
|
||||
assert "DEFAULT_CURVE_LENGTH_M = 20;" in law and "INITIAL_STRAIGHT_RADIUS_M = 500;" in law
|
||||
|
||||
|
||||
def test_초기값_대수차_4퍼센트_안쪽은_곡선_없음(initial):
|
||||
_long, _options, alignment, _profile = initial
|
||||
assert alignment["policy"]["default_curve_length_m"] == 20.0
|
||||
assert alignment["policy"]["no_curve_keys"] == NO_CURVE
|
||||
assert _curve_at(alignment) == CURVED
|
||||
# 꼭짓점 높이 그대로 — 곡선 없는 측점 계획고 = 지반고(원지반 지남)
|
||||
rows = {round(r["chainage_m"], 3): r for r in alignment["stations"]}
|
||||
for key in NO_CURVE:
|
||||
row = rows[float(key)]
|
||||
assert row["plan_elevation_m"] == pytest.approx(row["ground_elevation_m"], abs=1e-4)
|
||||
|
||||
|
||||
def test_법정_미달_경고_45퍼센트로_줄어든_곳도(initial):
|
||||
_long, options, alignment, _profile = initial
|
||||
assert options.min_vertical_radius_m == 100.0 and options.min_curve_length_m == 20.0
|
||||
assert alignment["policy"]["min_curve_length_m"] == 20.0
|
||||
# 20 m 마디 = 반쪽 9 m 한도 → L 18 m(줄어든 경고 글 그대로) + 법정 L 20 미달 경고
|
||||
lengths = [round(c["l_m"], 3) for c in alignment["curves"]]
|
||||
assert lengths == [18.0] * len(CURVED)
|
||||
reduced = [w for w in alignment["warnings"] if "줄였습니다" in w]
|
||||
short = [w for w in alignment["warnings"] if "미달" in w]
|
||||
assert len(reduced) == len(CURVED) and len(short) == len(CURVED)
|
||||
assert short[0].startswith("40.0m: 종단곡선 R") and "법정 최소 R 100m · L 20m 미달" in short[0]
|
||||
|
||||
|
||||
def test_손_편집은_기본_곡선_이웃은_얼린_채(initial):
|
||||
alignment = initial[2]
|
||||
own = _rebuild(alignment, EDITS[1])
|
||||
assert 60.0 in _curve_at(own)
|
||||
neighbor = _rebuild(alignment, EDITS[2])
|
||||
assert 60.0 not in _curve_at(neighbor) and 100.0 not in _curve_at(neighbor)
|
||||
assert 80.0 in _curve_at(neighbor)
|
||||
radius = _rebuild(alignment, EDITS[3])
|
||||
assert 100.0 in _curve_at(radius)
|
||||
|
||||
|
||||
def test_옛_저장분은_그대로(initial):
|
||||
alignment = initial[2]
|
||||
old = {**alignment["policy"], "default_curve_length_m": 15.0}
|
||||
old.pop("no_curve_keys")
|
||||
got = _rebuild(alignment, {}, old)
|
||||
assert len(got["curves"]) == len(STATIONS) - 2 # 꼭짓점마다 곡선(옛 규칙)
|
||||
|
||||
|
||||
def test_TS_거울_첫_계획선_열쇠_곡선_경고(initial, tmp_path):
|
||||
longitudinal, options, alignment, _profile = initial
|
||||
payload = {
|
||||
"ground": {
|
||||
"samples": longitudinal["samples"],
|
||||
"stations": STATIONS,
|
||||
"policy": {**alignment["policy"], "no_curve_keys": []},
|
||||
"startOffsetM": options.start_elevation_offset_m,
|
||||
"endOffsetM": options.end_elevation_offset_m,
|
||||
},
|
||||
"policy": alignment["policy"],
|
||||
"basePvi": alignment["base_pvi"],
|
||||
"chainage": [row["chainage_m"] for row in longitudinal["samples"]],
|
||||
"groundZ": [row["elevation_m"] for row in longitudinal["samples"]],
|
||||
"stations": STATIONS,
|
||||
"edits": [{"station_offsets": {}, "curve_radii": {}, **e} for e in EDITS],
|
||||
"carry": {
|
||||
**CARRY,
|
||||
"old_xy": [{"x": x, "y": y} for x, y in CARRY["old_xy"]],
|
||||
"new_xy": [{"x": x, "y": y} for x, y in CARRY["new_xy"]],
|
||||
},
|
||||
}
|
||||
source = tmp_path / "in.json"
|
||||
source.write_text(json.dumps(payload), encoding="utf-8")
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_432_1_curve_law.cjs"), str(source)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
ts = json.loads(proc.stdout)
|
||||
assert ts["consts"] == [20, 500]
|
||||
assert ts["keys"] == NO_CURVE
|
||||
assert [n["chainage_m"] for n in ts["pvi"]] == [n["chainage_m"] for n in alignment["base_pvi"]]
|
||||
assert [n["elevation_m"] for n in ts["pvi"]] == pytest.approx(
|
||||
[n["elevation_m"] for n in alignment["base_pvi"]], abs=1e-6
|
||||
)
|
||||
assert ts["carried"] == CARRIED
|
||||
for edits, got in zip(EDITS, ts["cases"], strict=True):
|
||||
py = _rebuild(alignment, edits)
|
||||
assert [c[0] for c in got["curves"]] == pytest.approx(
|
||||
[c["chainage_m"] for c in py["curves"]], abs=1e-6
|
||||
)
|
||||
assert [c[1] for c in got["curves"]] == pytest.approx(
|
||||
[c["r_m"] for c in py["curves"]], rel=1e-6
|
||||
)
|
||||
# 미달 경고 글은 두 쪽 같은 글(옛 경고 글은 앞머리가 원래 다름)
|
||||
py_short = [w for w in py["warnings"] if "미달" in w]
|
||||
assert got["shortfalls"] == py_short
|
||||
assert [w for w in got["warnings"] if "미달" in w] == py_short
|
||||
|
||||
|
||||
def test_추천_적용_균형_종단에서_열쇠(initial, tmp_path):
|
||||
from B05_Profile.B05_Profile_Router_RouteInitial import _set_base_pvi
|
||||
|
||||
longitudinal, _options, alignment, _profile = initial
|
||||
path = tmp_path / "long.json"
|
||||
path.write_text(json.dumps({**longitudinal, "profile_alignment": alignment}), encoding="utf-8")
|
||||
# 균형 종단 — 100 m 를 2 m 낮춤(그 둘레 대수차가 커짐 · 나머지는 지반 그대로)
|
||||
plan = [z - (2.0 if s == 100.0 else 0.0) for s, z in zip(STATIONS, _vertex_z())]
|
||||
_set_base_pvi(path, STATIONS, plan)
|
||||
stored = json.loads(path.read_text(encoding="utf-8"))["profile_alignment"]["policy"]
|
||||
expected = initial_no_curve_keys(np.array(STATIONS), np.array(plan), 20.0)
|
||||
assert stored["no_curve_keys"] == list(expected)
|
||||
assert "100.000" not in stored["no_curve_keys"] and "20.000" in stored["no_curve_keys"]
|
||||
|
||||
|
||||
def test_부르는_곳():
|
||||
def read(rel: str) -> str:
|
||||
return (ROOT / rel).read_text(encoding="utf-8")
|
||||
|
||||
assert "skipsInitialCurve(policy, key, pvi[index], hasRequested)" in read(
|
||||
"B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts"
|
||||
)
|
||||
assert "verticalCurveShortfalls(alignment.curves, policy)" in read(
|
||||
"B05_Profile/B05_Profile_UI_Profile_Balance.ts"
|
||||
)
|
||||
assert "carryNoCurveKeys(" in read("B05_Profile/logic/B05_Profile_Pipeline_Grade.ts")
|
||||
assert "carry_policy(old_xy, new_xy, old_alignment, alignment)" in read(
|
||||
"B05_Profile/B05_Profile_Route_Save.py"
|
||||
)
|
||||
|
||||
|
||||
def test_노선_저장_열쇠_옮김():
|
||||
args = [CARRY[k] for k in ("old_xy", "new_xy", "old_keys", "ground_keys", "ground_pvi")]
|
||||
assert carry_no_curve_keys(*args) == CARRIED
|
||||
|
||||
|
||||
def test_추천_적용_종단_노선_고쳐_저장_안_바뀐_구간_그대로(initial, tmp_path):
|
||||
from B05_Profile.B05_Profile_Router_RouteInitial import _set_base_pvi
|
||||
|
||||
longitudinal, options, alignment, _profile = initial
|
||||
path = tmp_path / "long.json"
|
||||
path.write_text(json.dumps({**longitudinal, "profile_alignment": alignment}), encoding="utf-8")
|
||||
plan = [z - (2.0 if s == 100.0 else 0.0) for s, z in zip(STATIONS, _vertex_z())]
|
||||
_set_base_pvi(path, STATIONS, plan)
|
||||
old = json.loads(path.read_text(encoding="utf-8"))["profile_alignment"]
|
||||
old_rebuilt = _rebuild(old, {})
|
||||
# 새 노선 종단 = 서버가 새로 냄(정책 config · L 20 · 새 지반 열쇠) → 옛 정책 · 옛 꼭짓점 이어받음
|
||||
zs = _vertex_z()
|
||||
samples = [
|
||||
{"chainage_m": float(s), "elevation_m": float(np.interp(s, STATIONS, zs))}
|
||||
for s in range(0, int(NEW_TOTAL) + 1)
|
||||
] + [{"chainage_m": NEW_TOTAL, "elevation_m": float(zs[-1])}]
|
||||
new_long = {"samples": samples, "stations": [{"chainage_m": s} for s in NEW_STATIONS]}
|
||||
fresh, _ = design_ground_following_profile(new_long, options)
|
||||
old_policy = {**old["policy"], "default_curve_length_m": 15.0} # 옛 L 이 이어지는지
|
||||
policy = carry_policy(CARRY["old_xy"], CARRY["new_xy"], {**old, "policy": old_policy}, fresh)
|
||||
assert policy["default_curve_length_m"] == 15.0
|
||||
base = carry_base(CARRY["old_xy"], CARRY["new_xy"], old["base_pvi"], fresh["base_pvi"])
|
||||
carried = build_alignment(
|
||||
base_s=np.array([n["chainage_m"] for n in base]),
|
||||
base_z=np.array([n["elevation_m"] for n in base]),
|
||||
chainage=np.array([row["chainage_m"] for row in samples]),
|
||||
ground=np.array([row["elevation_m"] for row in samples]),
|
||||
stations=new_long["stations"],
|
||||
policy=AlignmentPolicy.from_dict({**policy, "default_curve_length_m": 20.0}),
|
||||
edits={},
|
||||
)
|
||||
before = [(c["chainage_m"], round(c["r_m"], 3)) for c in old_rebuilt["curves"]]
|
||||
after = [(c["chainage_m"], round(c["r_m"], 3)) for c in carried["curves"]]
|
||||
# 안 바뀐 구간(< 80 m · 끝 노드 영향 밖) R · 직선 그대로
|
||||
assert [c for c in after if c[0] < 80] == [c for c in before if c[0] < 80]
|
||||
old_keys = set(old["policy"]["no_curve_keys"])
|
||||
assert {k for k in policy["no_curve_keys"] if float(k) <= 100} == {
|
||||
k for k in old_keys if float(k) <= 100
|
||||
}
|
||||
Reference in New Issue
Block a user