Files
Aislo/resources/tester/helper_458_swap_attach.cjs
T
eomsangdonandClaude Opus 5.5 37a738aff4 fix(458 B06 노선 바뀜): 주인 측점 거리 상한 0.51 m · 시종점 밖 시설 안 얹음 · 갈아끼움 grid 지움 · 늘 다시 얹기 · 이름 = 계획 제목
- 관 세트 주인 측점 = 0.51 m 안 가장 가까운 측점(TS attachCulvertSets ↔ 파이썬 attach_culvert_specs) · 넘으면 연동 이웃에도 안 얹고 missed
- 형상 얹기 한 벌(attachShapes ↔ attach_shapes) — 시종점 측점 자리인데 좌표가 노선 진행 반대쪽 0.51 m 넘게 바깥 = 「노선 밖」 · 안 얹음 · 안 얹은 것 돌려줌(서버 = 로그 경고)
- 측점 갈아끼움 — 격자 아닌 자리면 앞 횡단 grid 표시 지움 · 갈아끼움 뒤 가게 초안 없어도 늘 다시 얹음 · 카드 · 종단 측점 이름 = 계획 제목 · 안 얹은 시설 알림(바뀐 때 한 번)
- 시험: test_458_swap_attach(재현 A · D) · test_323_4 거울 moved 경우(상한 · 시종점 밖 · 연동 폭) · test_299 같은 키 견본 = 주인 0.51 m 안으로 · 273_2 흉내 돌려줌

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

193 lines
6.3 KiB
JavaScript

/* PLAN 458 노선 바뀜 — 진짜 모듈(따라감 · 측점 · 갈아끼움 · 형상 얹기)을 그 자리에서 트랜스파일해 Node 로 돌림.
* 저장 노선 (0,0)→(100,0)→(200,60) 위 관 · 세월교 · 옹벽 → 시점 당김 / 밀어냄 → 갈아끼움 + 지금 현황으로 다시 얹기.
* 결과 = 표준출력 JSON. 짝 = `test_458_swap_attach.py`.
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "..");
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
const cache = new Map();
function resolveTs(base) {
for (const c of [`${base}.ts`, base, path.join(base, "index.ts")])
if (fs.existsSync(c) && fs.statSync(c).isFile()) return c;
throw new Error(`모름: ${base}`);
}
function load(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 need = (spec0) => {
if (/common_util_structure_catalog(\.js)?$/.test(spec0))
return require(path.join(__dirname, "helper_322_catalog_module.cjs"));
const spec = spec0.replace(/\.js$/, "");
if (spec.endsWith(".json"))
return JSON.parse(fs.readFileSync(path.join(path.dirname(full), spec), "utf8"));
if (spec.startsWith("@util/"))
return load(resolveTs(path.join(ROOT, "common_util", spec.slice(6))));
if (spec.startsWith(".")) return load(resolveTs(path.join(path.dirname(full), spec)));
return require(spec);
};
new Function("exports", "module", "require", js)(box.exports, box, need);
return box.exports;
}
const P = (...parts) => path.join(ROOT, ...parts);
const follow = load(P("common_util", "common_util_route_follow.ts"));
const SS = load(P("B05_Profile", "logic", "B05_Profile_Sections_Stations.ts"));
const SW = load(P("B06_Section", "logic", "B06_Section_Station_Swap.ts"));
const AT = load(P("common_util", "common_util_structure_attach.ts"));
const def = JSON.parse(
fs.readFileSync(P("resources", "master_template", "구조물정의.json"), "utf8"),
);
const types = def.types
.filter((t) => t.enabled !== false)
.map((t) => ({
type_id: t.type_id,
group: t.group,
placement: t.placement,
managed_by: t.managed_by,
name: t.name,
options: (t.options || []).map((o) => ({ key: o.key, label: o.label, default: o.default })),
}));
const INTERVAL = 20;
const xy = (L) => L.map(([x, y]) => ({ x, y }));
function at(L, ch) {
let walked = 0;
for (let i = 1; i < L.length; i++) {
const [a, b] = [L[i - 1], L[i]];
const span = Math.hypot(b[0] - a[0], b[1] - a[1]);
if (walked + span >= ch) {
const t = (ch - walked) / span;
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
}
walked += span;
}
return L[L.length - 1];
}
const kindOf = (s) =>
s.kind === "end"
? s.key.endsWith("EP")
? "ep"
: "bp"
: s.kind === "grid"
? "regular"
: s.kind === "curve"
? "curve"
: "irregular";
/** 저장 상세 — 서버처럼 격자 자리 시설 측점에 `grid` · 설계에 열쇠 메모. */
const sectionsFrom = (stations) =>
stations.map((s) => ({
station_id: SW.stationIdOf(s.chainageM),
chainage_m: s.chainageM,
label: "",
kind: kindOf(s),
...(kindOf(s) === "irregular" && s.chainageM % INTERVAL === 0 ? { grid: true } : {}),
center_x: s.x,
center_y: s.y,
center_z: 100,
frame: { left_xy: s.left },
samples: [{ offset_m: 0, elevation_m: 100, valid: true }],
uphill_side: "left",
design: { memo: s.key, design_elevation_m: 101 },
}));
const ground = (s) => ({
...s,
ground: { centerZ: 100, left: [{ offsetM: 5, z: 101 }], right: [{ offsetM: 5, z: 99 }] },
});
const KEYS = ["culvert", "ford", "box", "ford_pavement"];
const view = (sections) =>
sections.map((s) => ({
chainage_m: s.chainage_m,
kind: s.kind,
grid: !!s.grid,
shapes: Object.fromEntries(KEYS.filter((k) => s[k]).map((k) => [k, s[k].chainage_m])),
}));
function run(L1, L2, pipes, structures) {
const S1 = SS.buildStations(L1, [], pipes, structures, { types, intervalM: INTERVAL });
const saved = { longitudinal: { stations: [], length_m: 0 }, cross_sections: sectionsFrom(S1) };
AT.attachShapes(saved.cross_sections, pipes, structures, types);
const pipesF = follow.projectPipePoints(pipes, xy(L2));
const structuresF = follow
.followStructures(structures, xy(L2), undefined, INTERVAL)
.map((m) => m.item);
const S2 = SS.buildStations(L2, [], pipesF, structuresF, { types, intervalM: INTERVAL }).map(
ground,
);
const swap = SW.swapCrossSections(
saved,
S2,
S1.map((s) => s.key),
INTERVAL,
);
const now = swap.detail.cross_sections.map((s) => ({ ...s }));
const misses = AT.attachShapes(now, pipesF, structuresF, types);
// 가게 초안이 없는 채(저장분 현황) 얹기 — 상한이 옛 자리 관을 이웃 측점에 안 붙임
const stale = swap.detail.cross_sections.map((s) => ({ ...s }));
const staleMisses = AT.attachShapes(stale, pipes, structures, types);
return {
pipes: pipesF.map((p) => [p.id, p.chainage_m]),
now: view(now),
misses,
staleMisses,
stale: view(stale),
};
}
const L1 = [
[0, 0],
[100, 0],
[200, 60],
];
const pipe = (id, facility, ch, options) => {
const [x, y] = at(L1, ch);
return { id, facility, chainage_m: ch, x, y, options };
};
const pipes = [
pipe("p1", "pipe", 40, { pipe_diameter_mm: "800" }), // 격자 No.2 위
pipe("p2", "ford_bridge", 150, { ford_width_m: 10 }),
];
const [wx, wy] = at(L1, 70);
const wall = {
structure_id: "w1",
type_id: "retaining_wall",
chainage_m: 70,
start_m: 60,
end_m: 80,
x: wx,
y: wy,
options: { height_m: 2, side: "left" },
};
const out = {
// A — 시점 7 m 당김: 관 40 → 47(격자를 떠남) · 세월교 150 → 157
shift: run(
L1,
[
[-7, 0],
[100, 0],
[200, 60],
],
pipes,
[wall],
),
// D — 시점을 50 m 로 밀어냄: 관 40 = 새 시점 10 m 바깥 → 누가거리 0
outside: run(
L1,
[
[50, 0],
[100, 0],
[200, 60],
],
pipes,
[wall],
),
};
process.stdout.write(JSON.stringify(out));