Files
Aislo/resources/tester/helper_433_swap.cjs
T
eomsangdonandClaude Opus 5.5 4a749584e6 fix(457 노선 바뀜 초안 카드): 그림 · 이름 · 횡단 맥락 = 지금 현황을 새 측점에 다시 얹음 · 등록 열쇠 짝 · 가게 쓰기 = 지금 가게 값과 견줌
- 초안 상세(카드)도 캐시와 같은 attachShapes 를 follow 뒤 현황으로 부름 · atChainage 지움 · 이름 = 측점 계획 제목
- cross 입력 넘김 = 사용자 칸 · 카드 번호만 · 포장 · 기울임 · 하강 = 새 자리 그림에서(crossWithShapes · 점 창도)
- 초안 자리 시설 측점 짝 = 등록 열쇠(B06 측점 열쇠 목록 · 처음 본 열쇠) · 차례 짝 지움
- writeFacilities = 지금 가게 값과 견줌(되돌리기 뒤 같은 조작도 씀)
- 시험 test_457_draft_card(재현 E · F · G · H) · 365 · 433 · 440 도우미 새 규칙으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:43:05 +09:00

218 lines
7.6 KiB
JavaScript

// 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])),
),
},
}),
);