import { readFileSync, readdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import * as XLSX from 'xlsx'; import { describe, expect, it } from 'vitest'; import { swapXYAzimuth } from '../src/core/coordinate-frame'; import { parseWorkbook } from '../src/core/parseWorkbook'; import { buildPier } from '../src/core/geometry'; import { parseProject, serializeProject, SCHEMA_VERSION, type ProjectFile } from '../src/core/project'; import { loadP1 } from './helpers'; describe('bearing position / direction coordinate agreement', () => { it('reflects the direction vector with the exchanged coordinate axes', () => { for (const angle of [0, 45, 90, 153.200861111, 180, 270, 350, -30, 720]) { const original = angle * Math.PI / 180; const converted = swapXYAzimuth(angle) * Math.PI / 180; expect(Math.sin(converted)).toBeCloseTo(Math.cos(original), 12); expect(Math.cos(converted)).toBeCloseTo(Math.sin(original), 12); } expect(swapXYAzimuth(NaN)).toBeNaN(); }); it('keeps every real Sanwoe bearing approximately aligned with its coping', () => { const dir = fileURLToPath(new URL('../pierzainer_ground/data/input/sanwoe/', import.meta.url)); const name = readdirSync(dir).find((name) => name.includes('산외교') && name.endsWith('.xlsx'))!; const { piers, issues } = parseWorkbook(XLSX.read(readFileSync(`${dir}/${name}`), { type: 'buffer' })); expect(issues).toEqual([]); expect(piers).toHaveLength(7); for (const input of piers) { const model = buildPier(input); expect(model.blocks).toHaveLength(4); for (const block of model.blocks) expect(Math.abs(block.rowSkewDeg), `${input.name}/${block.no}`).toBeLessThan(0.03); } expect(piers[0].bearings[0].azimuthDeg).toBeCloseTo(296.799138889, 8); }); it('migrates legacy input/defaults/draft once, including backup-style project round trips', () => { const normalized = loadP1(); const legacy = structuredClone(normalized); delete legacy.bearingAzimuthFrame; legacy.bearings = legacy.bearings.map((b) => ({ ...b, azimuthDeg: swapXYAzimuth(b.azimuthDeg) })); const file: ProjectFile = { schemaVersion: SCHEMA_VERSION, project: { name: '', bridge: '', client: '', author: '', crs: '', memo: '' }, source: { fileName: 'legacy.xlsx', sheets: [] }, piers: [{ id: 'P1', input: legacy, defaults: structuredClone(legacy), draft: structuredClone(legacy) }], history: [], }; const opened = parseProject(JSON.stringify(file)); for (const input of [opened.piers[0].input, opened.piers[0].defaults!, opened.piers[0].draft!]) { expect(input.bearingAzimuthFrame).toBe('model-xy'); input.bearings.forEach((b, i) => expect(b.azimuthDeg).toBeCloseTo(normalized.bearings[i].azimuthDeg, 10)); expect(input.ips).toEqual(normalized.ips); } expect(parseProject(serializeProject(opened))).toEqual(opened); expect(legacy.bearingAzimuthFrame).toBeUndefined(); }); });