fix(B05·B06): 한 자리 측점 둘 숨음 — 측점 0.006 m 안 꼭짓점 · 편집 키를 그 측점으로 모음(213-1)
- 까닭: 자동설계 관 측점 263.206 → 화면 저장 0.01 m 반올림 263.21 로 측점이 다시 섬 · 계획선 꼭짓점 · 편집 키는 옛 글자 그대로 → 새 측점 편집과 0.004 m 사이 두 꼭짓점(−15003% 계단 · 엄상준_검증 No.13+3) - 계획선 공용 함수 짝(파이썬 snap_to_stations ↔ TS snapToStations) — 측점 위 것이 이김 · 옮긴 꼭짓점은 옛 선의 측점 높이(측점 계획고 그대로) · 양 끝 · 측점에서 먼 키는 그대로 · 읽을 때 · [저장] 때 모두 정리 - 192 병합(_follow_moved_stations)도 옛 측점 0.006 m 안 글자를 같은 측점으로 봄 - 시험 test_213_1(합성 · 거울 · 엄상준 사본 재현) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
@@ -182,6 +182,65 @@ export function toAlignmentBase(alignment: ProfileAlignment): AlignmentBase {
|
||||
};
|
||||
}
|
||||
|
||||
/** 반올림만 다른 같은 측점(m) — 서버 `SAME_STATION_SNAP_M`(B05_Profile_Engine_Grade_Alignment.py) 와 짝. */
|
||||
const SAME_STATION_SNAP_M = 0.006;
|
||||
|
||||
function nearestStation(value: number, stationS: number[]): number | null {
|
||||
let near: number | null = null;
|
||||
stationS.forEach((station) => {
|
||||
if (near === null || Math.abs(station - value) < Math.abs(near - value)) near = station;
|
||||
});
|
||||
return near !== null && Math.abs(near - value) < SAME_STATION_SNAP_M ? near : null;
|
||||
}
|
||||
|
||||
function snapKeys(values: Record<string, number>, stationS: number[]): Record<string, number> {
|
||||
const best = new Map<string, { gap: number; value: number }>();
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
const chainage = Number(key);
|
||||
const near = Number.isFinite(chainage) ? nearestStation(chainage, stationS) : null;
|
||||
const target = near === null ? key : chainageKey(near);
|
||||
const gap = near === null ? 0 : Math.abs(near - chainage);
|
||||
const held = best.get(target);
|
||||
if (!held || gap < held.gap) best.set(target, { gap, value });
|
||||
});
|
||||
return Object.fromEntries([...best].map(([key, entry]) => [key, entry.value]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 측점 0.006m 안의 꼭짓점(base_pvi) · 편집 키를 그 측점으로 모은다(213-1) — 서버
|
||||
* `snap_to_stations` 와 같은 규칙. 관 측점이 반올림으로 옮겨도(263.206 → 263.21) 옛 글자로 남아
|
||||
* 0.004m 사이 두 꼭짓점이 되던 흠. 겹치면 측점 위 것이 이김 · 옮긴 꼭짓점은 옛 선의 측점 높이
|
||||
* (측점 계획고 그대로) · 양 끝 · 측점에서 먼 키는 그대로.
|
||||
*/
|
||||
function snapToStations(
|
||||
base: AlignmentBase,
|
||||
edits: AlignmentEdits,
|
||||
): { basePvi: AlignmentNode[]; edits: AlignmentEdits } {
|
||||
const stationS = base.stations.map((station) => station.chainage_m);
|
||||
const baseS = base.basePvi.map((node) => node.chainage_m);
|
||||
const baseZ = base.basePvi.map((node) => node.elevation_m);
|
||||
const last = base.basePvi.length - 1;
|
||||
const nodes = new Map<number, { gap: number; node: AlignmentNode }>();
|
||||
base.basePvi.forEach((node, index) => {
|
||||
const near = index === 0 || index === last ? null : nearestStation(node.chainage_m, stationS);
|
||||
const at = near ?? node.chainage_m;
|
||||
const gap = near === null ? 0 : Math.abs(near - node.chainage_m);
|
||||
const slot = Number(at.toFixed(3));
|
||||
const held = nodes.get(slot);
|
||||
// 옮긴 꼭짓점 높이 = 옛 선의 그 측점 높이 — 측점 계획고가 그대로
|
||||
const elevation = near === null ? node.elevation_m : interpolate(baseS, baseZ, at);
|
||||
if (!held || gap < held.gap)
|
||||
nodes.set(slot, { gap, node: { chainage_m: at, elevation_m: elevation } });
|
||||
});
|
||||
return {
|
||||
basePvi: [...nodes.keys()].sort((a, b) => a - b).map((slot) => nodes.get(slot)!.node),
|
||||
edits: {
|
||||
station_offsets: snapKeys(edits.station_offsets, stationS),
|
||||
curve_radii: snapKeys(edits.curve_radii, stationS),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 자동 변화점에 사용자 편집 측점을 합쳐 최종 변화점 집합을 만든다. */
|
||||
function resolvePvi(
|
||||
base: AlignmentBase,
|
||||
@@ -316,8 +375,10 @@ function normalizeEdits(edits: AlignmentEdits | undefined): AlignmentEdits {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAlignment(base: AlignmentBase, input: AlignmentEdits): ProfileAlignment {
|
||||
const edits = normalizeEdits(input);
|
||||
export function buildAlignment(raw: AlignmentBase, input: AlignmentEdits): ProfileAlignment {
|
||||
const snapped = snapToStations(raw, normalizeEdits(input));
|
||||
const base: AlignmentBase = { ...raw, basePvi: snapped.basePvi };
|
||||
const edits = snapped.edits;
|
||||
const warnings: string[] = [];
|
||||
const nodes = resolvePvi(base, edits.station_offsets);
|
||||
const pviS = nodes.map((node) => node.chainage_m);
|
||||
|
||||
Reference in New Issue
Block a user