Files
Aislo/resources/tester/helper_472_1_browser.mjs
T
eomsangdonandClaude Opus 5.5 7aca51596a fix(475-1 노선 자리): 노선 선 소수 자리 한 벌 — 서버 노선 geojson x · y 4자리(= 계획노선 csv) · 브라우저 초안 그린 선 · 곡선표도 같은 자리
- B05_Profile_Engine._route_geojson x · y 3 → 4자리(z 3자리 그대로) · 짝 머리 표시
- draftLine 그린 선 x · y 4자리 · 곡선 start · apex · end 4자리 · R 3자리(= RouteCurve.as_dict) · 파이썬 round 짝(toFixed)
- 교점 끌기 재현 브라우저 ↔ 서버 차 0.81 → 0.00 ㎥ · 옹벽 + 추가 측점 0.88 → 0.00 ㎥(test_475_1_route_digits)
- 472-1 헬퍼 — 옮김 인자 · 페이지 배선(stationsAt · storedPvi) · 단면 모양 = 따라간 구조물(헛 차 덤 고침) · golden 값 그대로
- 옛 시험 두 곳(346_1) — 안 깎은 선 단언 → 서버 저장 자리 단언
- 관 지문 허용오차 주석 · calculations R1 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:23:23 +09:00

192 lines
7.5 KiB
JavaScript

/* 472-1 헬퍼 — 브라우저 길 기준값(진짜 단계 함수 · vite ssr 묶음 · helper_472_1_browser_entry.ts).
* node helper_472_1_browser.mjs <묶음.mjs> <고정 자료 폴더> <격자 .bin> [옮김 .json]
* 옮김(475-1) = { node, dx, dy, structures?, plus? } — 열기 뒤 `pipe.update("route", 초안)`(노드 하나 옮김 · 페이지
* `flattenServerPlan` 초안 꼴) · 구조물 · 추가 측점 바꿔 넣기.
* 페이지 묶음(`design_pipeline_page`)과 같은 단계 붙임 — 저장 노선 · 초안 없음 · 세션 = 메모리 가짜 · fetch 없음 ·
* 격자 = 서버 `dtm_grid_bytes` 가 고정 npz 로 지은 이진. 묶음 출력 → 새 노선 상세(`draftSectionDetail`) → 구조물
* 면적 얹기 → 누가토량. 표준출력 JSON 한 벌(값 꼴 = test_472_1_golden.py `values_of`). */
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const storage = () => {
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 = storage();
globalThis.localStorage = storage();
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);
globalThis.fetch = () => Promise.reject(new Error("서버 없음"));
const [bundle, dir, gridPath, movePath] = process.argv.slice(2);
const move = movePath ? JSON.parse(readFileSync(movePath, "utf8")) : {};
const m = await import(pathToFileURL(bundle).href);
const read = (name) => JSON.parse(readFileSync(join(dir, name), "utf8"));
const detail = read("detail.json");
const plan = read("plan.json");
const context = read("context.json");
const pipes = read("pipes.json").pipe_points.map(({ lonlat: _l, ...pipe }) => pipe);
const structures = move.structures ?? read("structures.json").structures;
const types = read("structure_types.json").types;
const plus = move.plus ?? read("plus_stations.json").chainages;
const P = context.project_id;
const R = context.route_id;
const bin = readFileSync(gridPath);
const grid = m.parseDtmGrid(bin.buffer.slice(bin.byteOffset, bin.byteOffset + bin.byteLength));
// 페이지가 열 때 세션에 두는 것 — 표준 · 암 경계 기본 · 저장 상세(design_view_profile · 상세 가게)
localStorage.setItem("frd_current_project_id", P);
m.writeState("std-cross-default", context.standard_cross_section, P);
m.writeStateRaw("rock-boundary-default", String(context.rock_boundary_default_offset_m), P);
m.writeState("section-detail", detail, P, R);
const interval = m.SECTION_STATION_INTERVAL_M;
const enabled = types.filter((type) => type.enabled !== false);
const savedLine = () => plan.planned.map(([x, y]) => [x, y]);
const savedCurves = () =>
(plan.curves ?? []).map(({ start, apex, end, radius_m }) => ({ start, apex, end, radius_m }));
const savedStations = m.buildStations(savedLine(), savedCurves(), pipes, structures, {
intervalM: interval,
types: enabled,
stationSnap: plan.station_snap ?? false,
stationSnapStep: plan.station_snap_step,
});
const crossSource = m.pipelineCrossSource(P, detail, savedStations, R);
const edits = m.readAlignment(detail.longitudinal).edits;
const bermAt = (list) => {
const spans = m.bermSpansFromStructures(Array.isArray(list) ? list : []);
return (chainageM) => m.bermSpecAt(spans, chainageM);
};
const crossStage = m.createCrossStage({ grid: () => ({ grid }) });
const pipe = m.createDesignPipeline({
stages: {
route: m.createRouteStage({
minRadiusM: () => plan.min_radius_m ?? 0,
savedLine: () => ({ line: savedLine(), curves: savedCurves() }),
}),
follow: m.createFollowStage({
savedLine,
saved: () => ({ pipes, structures, alignment: edits }),
// 페이지 배선(`design_pipeline_page` stationsAt)
stationsAt: (route, patch, inputs) =>
m
.buildStations(
route.line,
route.curves,
patch.pipes ?? inputs.pipes ?? [],
patch.structures ?? inputs.structures ?? [],
{ intervalM: interval, types: enabled, ...m.snapOptions(inputs.route, plan) },
)
.map((station) => station.chainageM),
}),
stations: m.createStationsStage({
intervalM: interval,
types: () => enabled,
saved: () => plan,
}),
ground: m.createGroundStage({
grid: () => ({ grid, design: "las" }),
remote: () => Promise.reject(new Error("서버 없음")),
}),
planline: m.createPlanlineStage({
savedBase: (at) => m.plantedBase({ base: null, detail }, at, types),
savedLine,
storedPvi: () => m.toAlignmentBase(m.readAlignment(detail.longitudinal)).basePvi,
}),
profile: m.profileStage,
cross: (ctx) =>
crossStage.stage({
...ctx,
inputs: {
...ctx.inputs,
cross: m.crossWithContext(
ctx.inputs.cross,
ctx.out.ground?.stations ?? [],
m.shapeStatus(ctx.inputs.pipes, ctx.inputs.structures, types),
bermAt(ctx.inputs.structures),
),
},
}),
footprint: m.footprintStage,
},
});
const record = await pipe.start({
route: null,
alignment: edits,
cross: crossSource?.input ?? null,
pipes,
structures,
plusStations: plus,
});
await pipe.settled();
if (record.outcome !== "done") throw new Error(`묶음 ${record.outcome}`);
if (move.node != null) {
const flat = m.flattenServerPlan(plan.nodes, plan.curves ?? [], plan.node_edits);
const nodes = flat.planned.map(([x, y]) => [x, y]);
nodes[move.node] = [nodes[move.node][0] + move.dx, nodes[move.node][1] + move.dy];
const moved = await pipe.update("route", {
rev: 1,
nodes,
curveOn: flat.curveOn.map((on) => on !== false),
curveRadius: flat.curveRadius.map((r) => r ?? null),
curveLock: flat.curveLock ?? nodes.map(() => null),
curveArc: flat.curveArc ?? nodes.map(() => null),
stationSnap: plan.station_snap ?? false,
stationSnapStep: plan.station_snap_step ?? null,
terrain: null,
});
await pipe.settled();
if (moved.outcome !== "done") throw new Error(`묶음(옮김) ${moved.outcome}`);
}
// 단면 모양 = 따라간 관 · 구조물(페이지 = follow 뒤 가게 값 · 457)
const follow = pipe.output("follow");
const status = m.shapeStatus(follow?.pipes ?? pipes, follow?.structures ?? structures, types);
const ground = pipe.output("ground");
const profile = pipe.output("profile");
const shown = m.draftSectionDetail(
detail,
ground,
profile,
pipe.output("cross"),
crossSource?.specs ?? new Map(),
interval,
status,
);
m.applyStructureAreaRows(shown.cross_sections, m.structureAreaRows(shown.cross_sections));
const haul = m.computeMassHaul(
shown.cross_sections,
context.earthwork_conversion,
context.natural_spoil_min_ground_slope ?? undefined,
);
process.stdout.write(
JSON.stringify({
keys: ground.stations.map((station) => station.key),
sections: shown.cross_sections,
mass_haul: haul,
warnings: profile.warnings ?? [],
violations: profile.violations ?? [],
footprint_records: pipe.output("footprint")?.records?.length ?? 0,
skipped: record.skipped,
route_line: pipe.output("route").line,
draft: pipe.input("route"),
}),
);
process.exit(0);