- 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>
192 lines
7.5 KiB
JavaScript
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);
|