/* 366-3 측점 스냅 = 곡선 가운데(MC)를 일반측점에 — `logic/B05_Profile_RouteEdit_StationFit.ts` 를 * Node 로 돌려 JSON 한 줄을 냄(`test_366_3_station_fit.py` 가 판정). * `.cjs` = 루트 package.json 이 "type": "module" 이라서. */ 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) => request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) : require(request); new Function("exports", "module", "require", js)(box.exports, box, localRequire); cache.set(full, box.exports); return box.exports; } const LOGIC = path.join(ROOT, "B05_Profile", "logic"); const fit = loadTs(path.join(LOGIC, "B05_Profile_RouteEdit_StationFit.ts")); const curve = loadTs(path.join(LOGIC, "B05_Profile_UI_RouteEdit_Curve.ts")); const frame = loadTs(path.join(LOGIC, "B05_Profile_UI_RouteEdit_Frame.ts")); const INTERVAL = 20; const LIMITS = { minRadiusM: 30, limitRadiusM: 12, limitArcM: 0, intervalM: INTERVAL }; // 지그재그 노선 — 꺾임 다섯(교각 다양) · 직선 넉넉 const ROUTE = [ [0, 0], [93, 7], [171, 61], [262, 48], [337, 112], [431, 101], [503, 160], ]; function columns(planned, lock = []) { const n = planned.length; return { planned, curveOn: planned.map(() => true), curveRadius: planned.map(() => null), curveLock: planned.map(() => null), curveArc: planned.map(() => null), apexLock: planned.map((_, i) => i === 0 || i === n - 1 || lock.includes(i)), }; } function measure(planned, interval = INTERVAL) { const built = curve.buildEditedPolyline( planned, planned.map(() => true), planned.map(() => null), LIMITS.minRadiusM, ); const spans = frame.curveSpans(built.vertices, built.curves); const mcs = spans.map(([a, b]) => (a + b) / 2); const total = frame.stationsAlong(built.vertices, INTERVAL, false); const length = built.vertices.reduce( (sum, v, i) => i ? sum + Math.hypot(v[0] - built.vertices[i - 1][0], v[1] - built.vertices[i - 1][1]) : 0, 0, ); return { curves: built.curves.length, mcs, residues: mcs.map((mc) => Math.abs(mc - Math.round(mc / interval) * interval)), innerStations: total.filter((s) => s > 0 && s < length).length, off: fit.offStationCount(built.vertices, built.curves, interval), }; } const moved = (a, b) => a.map((v, i) => Math.hypot(v[0] - b[i][0], v[1] - b[i][1])); const before = measure(ROUTE); const all = fit.fitCurveStations(columns(ROUTE), LIMITS, 1); const locked = fit.fitCurveStations(columns(ROUTE, [3]), LIMITS, 1); const fromLater = fit.fitCurveStations(columns(ROUTE), LIMITS, 4); const everyLocked = fit.fitCurveStations(columns(ROUTE, [1, 2, 3, 4, 5]), LIMITS, 1); console.log( JSON.stringify({ before, all: { ...measure(all), moved: moved(ROUTE, all) }, locked: { ...measure(locked), moved: moved(ROUTE, locked) }, fromLater: { ...measure(fromLater), moved: moved(ROUTE, fromLater) }, everyLocked: { ...measure(everyLocked), moved: moved(ROUTE, everyLocked) }, again: moved(all, fit.fitCurveStations(columns(all), LIMITS, 1)), // 390-2 스냅 간격 — 0.1 · 1 · 10 m 배수 · 측점 밖 개수도 같은 간격 steps: Object.fromEntries( [0.1, 1, 10].map((step) => { const fitted = fit.fitCurveStations(columns(ROUTE), { ...LIMITS, intervalM: step }, 1); const offBefore = measure(ROUTE, step).off; return [step, { offBefore, ...measure(fitted, step), moved: moved(ROUTE, fitted) }]; }), ), tol: [0.1, 1, 10, 20].map((step) => fit.mcTolM(step)), noInterval: moved(ROUTE, fit.fitCurveStations(columns(ROUTE), { ...LIMITS, intervalM: 0 }, 1)), start: { moved4: fit.fitStart( columns(ROUTE), columns(ROUTE.map((v, i) => (i === 4 ? [v[0] + 1, v[1]] : v))), ), radius2: fit.fitStart(columns(ROUTE), { ...columns(ROUTE), curveRadius: ROUTE.map((_, i) => (i === 2 ? 40 : null)), }), dropLast: fit.fitStart(columns(ROUTE), columns(ROUTE.filter((_, i) => i !== 5))), same: fit.fitStart(columns(ROUTE), columns(ROUTE)), none: fit.fitStart(null, columns(ROUTE)), }, }), );