// 433-4 · 433-11 — 측점 갈아끼움(`swapCrossSections` · 진짜) · 횡단 엔진(`computeCrossDesign` · 진짜) · // 유토(`computeMassHaul` · 진짜). 인자 = 표준단면 json · 토량환산계수 json. const fs = require("fs"); const path = require("path"); const ROOT = path.resolve(__dirname, "..", ".."); const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); const idOf = (m) => `station_${String(Math.round(m * 1000)).padStart(12, "0")}`; const STUBS = { B05_Profile_Pipeline_Grade: { stationIdOf: idOf }, B05_Profile_UI_Profile_Data: { toDesignProfile: () => ({}) }, }; const CATALOG = require("./helper_322_catalog_module.cjs"); const cache = new Map(); function load(full) { if (cache.has(full)) return cache.get(full).exports; 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); const req = (r) => { if (CATALOG.isCatalogRequest(r)) return CATALOG; // 형상 얹기(Pipeline_Detail · 457)가 끌어감 const name = r.split("/").pop(); if (STUBS[name]) return STUBS[name]; if (r.startsWith("@util/")) return load(path.join(ROOT, "common_util", `${r.slice(6)}.ts`)); if (r.startsWith(".")) return load(path.resolve(path.dirname(full), `${r}.ts`)); throw new Error(`stub 없음: ${r}`); }; new Function("exports", "module", "require", js)(box.exports, box, req); return box.exports; } const swap = load(path.join(ROOT, "B06_Section/logic/B06_Section_Station_Swap.ts")); const { computeCrossDesign } = load(path.join(ROOT, "common_util/common_util_cross_design.ts")); const { computeMassHaul } = load(path.join(ROOT, "common_util/common_util_mass_haul.ts")); const standard = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const conversion = JSON.parse(fs.readFileSync(process.argv[3], "utf8")); const st = (key, m, kind, ground = null) => ({ key, chainageM: m, kind, x: m, y: 0, left: [0, 1], azimuthDeg: 90, planRadiusM: null, curveOuterSide: null, curveWideningM: 0, ground, }); /* ── 433-4: R 바꿔 MC 측점이 움직임(curve:47.35 → curve:49.10) ── */ const mcSec = (m, kind) => ({ station_id: idOf(m), chainage_m: m, kind, center_x: m, center_y: 0, frame: { left_xy: [0, 1] }, samples: [], uphill_side: "left", design: { memo: `MY-EDIT@${m}` }, }); const mcDetail = { longitudinal: { stations: [], length_m: 100 }, cross_sections: [ mcSec(0, "bp"), mcSec(20, "regular"), mcSec(47.35, "curve"), mcSec(60, "regular"), mcSec(100, "ep"), ], }; const mcOut = swap.swapCrossSections( mcDetail, [ st("end:BP", 0, "end"), st("grid:1", 20, "grid"), st("curve:49.10", 49.1, "curve"), st("grid:3", 60, "grid"), st("end:EP", 101, "end"), ], ["end:BP", "grid:1", "curve:47.35", "grid:3", "end:EP"], 20, ); // 멀리(> CURVE_NEAR_M) 간 MC = 못 이음(새 측점 · 노선 밖) const farOut = swap.swapCrossSections( mcDetail, [ st("end:BP", 0, "end"), st("grid:1", 20, "grid"), st("curve:62.00", 62, "curve"), st("grid:3", 60, "grid"), st("end:EP", 100, "end"), ], ["end:BP", "grid:1", "curve:47.35", "grid:3", "end:EP"], 20, ); /* ── 433-11: 덧 측점 넣기 · 빼기 — 서버 표본(A) 대 브라우저 지형(B) ── */ const OFFSETS = [-12, -9, -6, -4, -2, 0, 2, 4, 6, 9, 12]; const groundA = (m, o) => 100 + 0.02 * m + 0.35 * o; // 서버 샘플링(저장 상세) const groundB = (m, o) => groundA(m, o) + 0.6 * Math.sin(m / 7 + o / 3); // 브라우저 설계 지형 const planZ = (m) => 100.2 + 0.02 * m; const samplesOf = (fn, m) => OFFSETS.map((o) => ({ offset_m: o, elevation_m: fn(m, o), valid: true })); const groundOf = (fn, m) => ({ centerZ: fn(m, 0), left: OFFSETS.filter((o) => o > 0).map((o) => ({ offsetM: o, z: fn(m, o) })), right: OFFSETS.filter((o) => o < 0).map((o) => ({ offsetM: -o, z: fn(m, o) })), }); const designOf = (samples, m) => ({ ...computeCrossDesign(samples, planZ(m), { standard, groundType: "soil", sectionMode: "left_cut", }), design_elevation_m: planZ(m), }); const baseKeys = [ ["end:BP", 0, "end"], ["grid:1", 20, "grid"], ["grid:2", 40, "grid"], ["grid:3", 60, "grid"], ["end:EP", 80, "end"], ]; const kindOf = (kind, key) => kind === "end" ? (key.endsWith("EP") ? "ep" : "bp") : kind === "grid" ? "regular" : "irregular"; const savedSec = ([key, m, kind]) => { const samples = samplesOf(groundA, m); return { station_id: idOf(m), chainage_m: m, kind: kindOf(kind, key), center_x: m, center_y: 0, center_z: groundA(m, 0), frame: { left_xy: [0, 1] }, uphill_side: "left", samples, design: designOf(samples, m), }; }; const saved = { longitudinal: { stations: [], length_m: 80 }, cross_sections: baseKeys.map(savedSec), }; const savedKeys = baseKeys.map(([key]) => key); // 브라우저 다시 셈 = 상세 표본으로 면적(Cross_Refresh 꼴) const recross = (detail) => detail.cross_sections.map((s) => ({ ...s, design: designOf(s.samples, s.chainage_m) })); const cumulative = (sections) => computeMassHaul(sections, conversion).points.map((p) => [ p.chainage_m, Math.round(p.cumulative_volume_m3 * 1000) / 1000, ]); const plusList = [...baseKeys.slice(0, 2), ["pipe:p30", 30, "pipe"], ...baseKeys.slice(2)]; const plusStations = plusList.map(([key, m, kind]) => st(key, m, kind, groundOf(groundB, m))); const added = swap.swapCrossSections(saved, plusStations, savedKeys, 20); const addedSections = recross(added.detail); // 서버 기준 = 저장 상세(표본 A) + 덧 측점(브라우저 표본 B · 새 측점은 표본이 그것뿐) const serverPlus = [...saved.cross_sections]; serverPlus.splice(2, 0, addedSections[2]); const backStations = baseKeys.map(([key, m, kind]) => st(key, m, kind, groundOf(groundB, m))); const removed = swap.swapCrossSections( added.detail, backStations, plusList.map(([key]) => key), 20, ); // 노선이 옮겨 간 측점(좌표 다름)은 새 지형 표본 const shifted = swap.swapCrossSections( saved, baseKeys.map(([key, m, kind]) => ({ ...st(key, m, kind, groundOf(groundB, m)), y: 1.5 })), savedKeys, 20, ); console.log( JSON.stringify({ mc: { added: mcOut.added, removed: mcOut.removed, moves: [...mcOut.moves], memo: mcOut.detail.cross_sections[2].design.memo ?? null, stationId: mcOut.detail.cross_sections[2].station_id, far: { added: farOut.added, removed: farOut.removed }, }, plus: { keptSamples: added.detail.cross_sections .filter((_, i) => i !== 2) .every( (s, i) => JSON.stringify(s.samples) === JSON.stringify(saved.cross_sections[i].samples), ), keptCenterZ: added.detail.cross_sections.filter((_, i) => i !== 2).map((s) => s.center_z), savedCenterZ: saved.cross_sections.map((s) => s.center_z), newSamples: JSON.stringify(added.detail.cross_sections[2].samples) === JSON.stringify(samplesOf(groundB, 30)), browser: cumulative(addedSections), server: cumulative(serverPlus), back: cumulative(recross(removed.detail)), saved: cumulative(saved.cross_sections), // 고치기 전 꼴(모든 측점 브라우저 표본) — 차가 큼을 보임 allBrowser: cumulative( plusList.map(([, m], i) => ({ ...addedSections[i], design: designOf(samplesOf(groundB, m), m), })), ), shiftedFresh: shifted.detail.cross_sections.every( (s, i) => JSON.stringify(s.samples) === JSON.stringify(samplesOf(groundB, baseKeys[i][1])), ), }, }), );