- 초안 상세(카드)도 캐시와 같은 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>
218 lines
7.6 KiB
JavaScript
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])),
|
|
),
|
|
},
|
|
}),
|
|
);
|