/* 472-6 6-B 헬퍼 — 노선 바뀐 [저장] 횡단 넘김: 브라우저 옛 길(세션 함수를 옛 `carryAfterRouteSave` → * `saveCachedCrossPatches` 차례 그대로) 결과 · 새 길(서버 Node `stations.carry`) 입력을 JSON 한 벌로. * node helper_472_6_route_carry.mjs <묶음.mjs> * 시험(`test_472_6_route_save.py`)이 새 길 입력을 진짜 Node 입구로 돌려 두 결과를 견줌. */ import { pathToFileURL } from "node:url"; const store = () => { const m = new Map(); return { getItem: (k) => (m.has(k) ? m.get(k) : null), setItem: (k, v) => m.set(k, String(v)), removeItem: (k) => m.delete(k), key: (i) => [...m.keys()][i] ?? null, get length() { return m.size; }, clear: () => m.clear(), }; }; globalThis.window = globalThis; globalThis.sessionStorage = store(); globalThis.localStorage = store(); const any = new Proxy(function () {}, { get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any), apply: () => any, construct: () => any, }); globalThis.document = any; globalThis.requestAnimationFrame = (run) => setTimeout(run, 0); const [bundlePath] = process.argv.slice(2); const m = await import(pathToFileURL(bundlePath).href); const P = "p4726"; const R = 7; // 옛 노선 const R2 = 9; // 새 노선(서버가 저장한 것) const clone = (value) => JSON.parse(JSON.stringify(value)); const pipe = { structure: "파형강관 D800", culvert: {} }; const section = (chainage_m, kind, extra = {}) => ({ station_id: `s${chainage_m}`, chainage_m, kind, label: String(chainage_m), samples: [], design: {}, ...extra, }); const detailOf = (sections) => ({ longitudinal: { stations: sections, profile_alignment: { policy: { station_interval_m: 20 } } }, cross_sections: sections, }); /* 새 노선 서버 상세 — 50 m 뒤가 3 m 밀림(관 80 → 83) · MC 110 → 112 · 구조물 130 사라짐 · 끝 140 → 143 */ const fresh = () => detailOf([ section(0, "bp"), section(20, "regular", { design: { ground_type: "rock" } }), section(40, "regular"), section(60, "regular", { design: { display_half_width_m: 5, ground_type: "soil" } }), section(83, "irregular", pipe), section(103, "regular"), section(112, "curve"), section(123, "regular"), section(143, "ep"), ]); /* 옛 노선 열쇠 목록(브라우저가 끌기 동안 세운 것) · 측점 주인(초안 자리) · 관 장부 */ m.useStationKeyList(P, R, [ { key: "end:BP", chainageM: 0 }, { key: "grid:1", chainageM: 20 }, { key: "grid:2", chainageM: 40 }, { key: "grid:3", chainageM: 60 }, { key: "pipe:p80", chainageM: 80 }, { key: "grid:5", chainageM: 100 }, { key: "curve:110.00", chainageM: 110 }, { key: "grid:6", chainageM: 120 }, { key: "structure:s1", chainageM: 130 }, { key: "end:EP", chainageM: 140 }, ]); m.useStationOwners(P, [{ chainage_m: 83, owners: ["p80"] }], [{ id: "p80" }]); m.writeState("pipe-ids", { next: 2, ids: [{ id: "pipe-s1", at: 83 }] }, P); const pipesBefore = [{ id: "p80", chainage_m: 80 }]; const pipesAfter = [ { id: "p80", chainage_m: 83, options: { inlet_revet_height_m: 1.8, inlet_revet_form: "I" } }, ]; /* 옛 노선 세션 고침 — 측점 열쇠 칸 다섯 · 누가거리 칸 · 프로젝트 칸(종류 바꿈 · 상단측) */ m.writeCrossDesignChoice(P, R, 20, { ground_type: "soil", section_mode: "left_cut", ditch_type: "u_flume", two_stage_slope: true, }); m.writeCrossDesignChoice(P, R, 130, { ground_type: "soil", section_mode: "right_cut" }); // 밖 m.writeShoulderExtend(P, R, 40, "left", 0.5); m.writeBermDepth(P, R, 130, "right", 1); // 밖 m.writeState("rockb", { "grid:5": 1.5, "@at": { "grid:5": 100 } }, P, R); m.writeState("cutslope", { "curve:110.00": 0.8, "@at": { "curve:110.00": 110 } }, P, R); const wall = { x: 0.3, d: null, h: 1.5, m: null }; m.writeState("revetx", { "80.00:outlet": wall, "40.00:inlet": wall }, P, R); // 40 = 일반 측점 → 버림 m.writeState("basinadjust", { "80.00": { depth_m: 1.2 } }, P, R); m.writeState("crossw", { "60.00": 6 }, P, R); m.writeState("structure-notes", { "130.00": "메모", "20.00": "앞 메모" }, P, R); m.writeState("detail-table", { "80.00": { rows: 2 } }, P, R); m.writeState("revetlink", { detached: { "80.00": true }, grade: {} }, P, R); m.writeState("kindreset", { "60.00": "culvert" }, P); // 일반 측점 = 관 · 구조물 열쇠 아님 → 버림 m.writeState("uphill", { "60.00": "right" }, P); const out = {}; /* 새 길 입력 — 브라우저가 한 본문 `carry` 칸에 싣는 것(넘기기 전 세션 그대로) */ out.carry = clone(m.routeCarryInputs(P, R)); out.detail = fresh(); out.pipesBefore = pipesBefore; out.pipesAfter = pipesAfter; /* 옛 길 — `loadSectionDetail`(관 옵션 얹기) → `carryAfterRouteSave` → `saveCachedCrossPatches` 차례 그대로 */ const detail = fresh(); m.layPipeOptions(detail, pipesAfter); m.useStationKeys(P, R2, detail); const newAt = (id) => pipesAfter.find((p) => id !== undefined && p.id === id)?.chainage_m ?? null; const pipeAt = (key) => { const id = key.startsWith("pipe:") ? key.slice(5) : null; if (!id) return null; return id.startsWith("pipe-") ? m.pipeChainageOf(id, P) : newAt(id); }; const movedAt = (chainageM) => newAt(pipesBefore.find((p) => Math.abs(p.chainage_m - chainageM) < 0.01)?.id); const outside = m.carryStationDrafts(P, R, R2, { quiet: true }) + m.carryChainageStates(P, R, R2, { keyAt: pipeAt, movedAt }); m.withDraftAdjusts(detail, P, R2); m.withCarriedEdits(detail, P, R2); const base = m.crossPatchesFromCache( detail, m.kindChangedChainages(P), detail.cross_sections.map((s) => s.chainage_m), ); const overlay = []; m.crossDesignChoices(P, R2).forEach((choice, chainage) => { const patch = { chainage_m: chainage, ground_type: choice.ground_type, section_mode: choice.section_mode, }; if (choice.ditch_side !== undefined) patch.ditch_side = choice.ditch_side; if (choice.ditch_type !== undefined) patch.ditch_type = choice.ditch_type; if (choice.paved !== undefined) patch.paved = choice.paved; if (choice.two_stage_slope !== undefined) patch.two_stage_slope = choice.two_stage_slope; if (typeof choice.ditch_choice === "boolean") patch.ditch_choice = choice.ditch_choice; overlay.push(patch); }); // 옛 `readRockBoundarySession` · `readCutSlopeSession` = 측점 열쇠 칸을 새 목록 누가거리로(`readKeyed`) const keyed = (name) => [...m.readKeyed(name, P, R2)]; for (const [chainage, offset] of keyed("rockb")) if (typeof offset === "number") overlay.push({ chainage_m: Number(chainage), rock_boundary_offset_m: offset }); for (const [chainage, ratio] of keyed("cutslope")) overlay.push({ chainage_m: Number(chainage), cut_slope_ratio_user: ratio }); out.oldPatches = JSON.parse( JSON.stringify( m.mergeCrossPatches( base, overlay.filter((p) => Number.isFinite(p.chainage_m)), ), ), ); out.oldOutside = outside; /* 관 · 구조물 초안 다시 얹기 — 브라우저 옛 길이 부르던 같은 함수(서버 Node `facility.rebase` 와 견줌) */ out.rebaseInput = { pipes: { draft: [ { id: "p80", chainage_m: 80, x: 80, y: 0 }, // 따라가기만 함 → 서버 자리 { id: "p20", chainage_m: 25, x: 25, y: 0 }, // 사용자가 옮김 → 그대로 ], before: [ { id: "p80", chainage_m: 80, x: 80, y: 0 }, { id: "p20", chainage_m: 20, x: 20, y: 0 }, ], after: [ { id: "p80", chainage_m: 83, x: 80, y: 0 }, { id: "p20", chainage_m: 20, x: 20, y: 0 }, { id: "pnew", chainage_m: 120, x: 117, y: 2 }, // 서버가 새로 놓은 교차 관 ], }, structures: { draft: [{ structure_id: "s1", chainage_m: 60, start_m: null, end_m: null, x: 60, y: 0 }], before: [{ structure_id: "s1", chainage_m: 60, start_m: null, end_m: null, x: 60, y: 0 }], after: [{ structure_id: "s1", chainage_m: 63, start_m: null, end_m: null, x: 60, y: 0 }], }, }; const r = out.rebaseInput; out.oldRebase = { pipes: m.rebasePipeDraft(r.pipes.draft, r.pipes.before, r.pipes.after), structures: m.rebaseStructureDraft(r.structures.draft, r.structures.before, r.structures.after), }; process.stdout.write(JSON.stringify(out));