/* 400 계획고 이월 거울 — 진짜 TS(`logic/B05_Profile_Grade_Carry` · `logic/B05_Profile_Pipeline_Grade` follow)를 * Node 로 돌림. 입력 argv[2] JSON(파이썬과 같은 값) · 표준출력 JSON 한 벌. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); function loadTs(file, stubs = {}) { return makeLoader({ root: ROOT, cache: false, requireFor: (full) => (request) => { if (stubs[request]) return stubs[request]; throw new Error(`stub 없음: ${request} (${file})`); }, }).loadTs(path.join(ROOT, file)); } 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 seat = loadTs("B05_Profile/logic/B05_Profile_Grade_Seat.ts", { "./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": {}, "../../common_util/common_util_structure_station": { PLUS_OWNER_PREFIX: "plus-", SAME_STATION_M: 0.005, }, "./B05_Profile_Grade_Seat": seat, "./B05_Profile_UI_Profile_Alignment": alignment, "./B05_Profile_Grade_Carry": carry, "./B05_Profile_Sections_Stations": { SECTION_STATION_INTERVAL_M: 20 }, "./B05_Profile_Grade_Ground": ground, }); const curve = loadTs("B05_Profile/logic/B05_Profile_UI_RouteEdit_Curve.ts"); const edits = loadTs("B05_Profile/logic/B05_Profile_UI_RouteEdit_Edits.ts", { "./B05_Profile_UI_RouteEdit_Curve": curve, }); const frame = loadTs("B05_Profile/logic/B05_Profile_UI_RouteEdit_Frame.ts"); const fit = loadTs("B05_Profile/logic/B05_Profile_RouteEdit_StationFit.ts", { "./B05_Profile_UI_RouteEdit_Curve": curve, "./B05_Profile_UI_RouteEdit_Edits": edits, "./B05_Profile_UI_RouteEdit_Frame": frame, }); /** 측점 스냅 켠 노선 — 저장 노선(맞춤) · 교점 하나 옮긴 뒤 맞춤(편집기와 같은 `fitCurveStations`). */ function snapFit(item) { const n = item.nodes.length; const cols = (planned) => ({ planned, curveOn: Array(n).fill(true), curveRadius: Array(n).fill(null), curveLock: Array(n).fill(null), curveArc: Array(n).fill(null), apexLock: Array(n).fill(false), }); const line = (planned) => curve.buildEditedPolyline( planned, Array(n).fill(true), Array(n).fill(null), item.limits.minRadiusM, ).vertices; const old = fit.fitCurveStations(cols(item.nodes), item.limits, 1); const moved = old.map((p, i) => (i === item.index ? item.to : p)); const now = fit.fitCurveStations(cols(moved), item.limits, Math.max(1, item.index - 1)); return { old_nodes: old, new_nodes: now, old_line: line(old), new_line: line(now) }; } const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const routeOf = (line) => { const chainage = [0]; for (let i = 1; i < line.length; i += 1) chainage.push( chainage[i - 1] + Math.hypot(line[i][0] - line[i - 1][0], line[i][1] - line[i - 1][1]), ); return { line, curves: [], chainage, lengthM: chainage[chainage.length - 1] }; }; /** follow 단계를 노선 걸음 차례대로 — 각 걸음 뒤 편집(묶음이 입력에 넣는 것과 같음). */ function walk(item) { let stations = []; const stage = grade.createFollowStage({ savedLine: () => null, stationsAt: () => stations }); const planline = { policy: item.policy, basePvi: item.base_pvi }; let alignmentNow = item.edits; let before = routeOf(item.ref_line); const out = []; for (const step of item.steps) { stations = step.stations; const now = routeOf(step.line); const patch = stage({ inputs: { route: { nodes: [] }, pipes: null, structures: null, alignment: alignmentNow }, out: { route: now }, before: { route: before, planline }, }); if (patch.alignment) alignmentNow = patch.alignment; out.push(alignmentNow); before = now; } return out; } const result = { match: input.match.map((item) => { const fn = carry.lineMatcher(item.old_xy, item.new_xy, item.tol); return item.queries.map((t) => fn(t)); }), base: input.base.map((item) => carry.carryBase(item.old_xy, item.new_xy, item.old_base, item.ground_pvi), ), edits: input.edits.map((item) => carry.carryEdits(item.old_xy, item.new_xy, item.old_base, item.edits, item.stations, item.half), ), follow: input.follow.map(walk), snap: (input.snap ?? []).map(snapFit), }; process.stdout.write(JSON.stringify(result));