From 54c855ab1ab28feef455a05b8b33c4593c099b79 Mon Sep 17 00:00:00 2001 From: ypshim Date: Fri, 6 Nov 2026 15:35:48 +0900 Subject: [PATCH] =?UTF-8?q?fix(bearing):=20=EC=A7=9D=20=EB=B0=9B=EC=B9=A8?= =?UTF-8?q?=EC=9D=84=20=EB=AA=A9=EB=A1=9D=20=EC=88=9C=EC=84=9C=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=88=EB=9D=BC=20=EA=B1=B0=EB=8D=94=20=EA=B8=B0?= =?UTF-8?q?=ED=95=98=EB=A1=9C=20=EC=B0=BE=EC=9D=8C=20(=EB=91=90=20?= =?UTF-8?q?=EB=85=B8=EC=84=A0=20=EA=B4=91=EC=A3=BC=C2=B7=EC=99=84=EB=8F=84?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 운곡교(완도,광주) 데이터에서 구조물 목록이 A1-광주, A1-완도, P1-완도 … P6-완도, P1-광주 … P6-광주, A2-광주, A2-완도 순서라 "바로 다음 구조물"만 보던 짝짓기가 A1-광주→P1-광주와 P6-완도→A2-완도를 놓쳤다. 이제 모든 다른 구조물의 받침 중 거더를 따라 진행 방향으로 처음 만나는 받침(방위각 ≤ 0.1°, 거더선 ≤ 0.1 m)을 짝으로 한다. 이 데이터에서 14개 연결 × 5 = 70쌍, WS01·WS04 없음, S −2.028 ~ −1.754 %. 교대만 있는 다경간 입력(A1·A2 353 m, 방위각 8° 차)은 짝 없음 → 교대 종단경사 사용 (정상). Co-Authored-By: Claude Opus 5.5 --- src/core/bearing-pair.ts | 14 ++++++++------ tests/bearing-pair.test.ts | 10 ++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/core/bearing-pair.ts b/src/core/bearing-pair.ts index 8a0675b..268ed4c 100644 --- a/src/core/bearing-pair.ts +++ b/src/core/bearing-pair.ts @@ -52,7 +52,7 @@ const DEG = Math.PI / 180; const angleDiffDeg = (a: number, b: number) => Math.abs(((a - b + 540) % 360) - 180); /** - * Girder pairs between neighbouring structures (Q-SH1, Q-SH4). For each bearing b on structure k the candidates on k + 1 lie + * Girder pairs over the bridge (Q-SH1, Q-SH4). For each bearing b the candidates on the other structures lie * ahead along b's azimuth, within 0.1° of its azimuth and 0.1 m of its girder line. The pair is the first one reached along * the girder: on a straight bridge the next span's bearing (후열) lies on the same line further on, so "closest to the line" * alone would be a tie (2026-11-06 review). WS04 when another candidate is within 0.5 m of it along the girder. @@ -60,11 +60,13 @@ const angleDiffDeg = (a: number, b: number) => Math.abs(((a - b + 540) % 360) - */ export function girderPairs(structures: PairStructure[]): BearingPair[] { const pairs: BearingPair[] = []; - for (let k = 0; k + 1 < structures.length; k++) { - const here = structures[k], next = structures[k + 1]; + // Every other structure is searched, not only the next one in the list: with two lines (광주 · 완도) the list order + // A1-광주, A1-완도, P1-완도 … mixes the lines, and the geometry alone tells which bearing ends the girder (2026-11-06). + for (const here of structures) { + const others = structures.filter((s) => s !== here).flatMap((s) => s.bearings.map((c) => ({ ...c, structure: s.name }))); for (const b of here.bearings) { const u = { e: Math.sin(b.azimuthDeg * DEG), n: Math.cos(b.azimuthDeg * DEG) }; - const candidates = next.bearings.map((c) => { + const candidates = others.map((c) => { const d = { e: c.x - b.x, n: c.y - b.y }; return { c, along: d.e * u.e + d.n * u.n, offset: Math.abs(d.e * u.n - d.n * u.e), dAz: angleDiffDeg(c.azimuthDeg, b.azimuthDeg), d }; }).filter((t) => t.along > 0 && t.dAz <= PAIR_AZIMUTH_TOL_DEG && t.offset <= PAIR_OFFSET_TOL_M) @@ -83,9 +85,9 @@ export function girderPairs(structures: PairStructure[]): BearingPair[] { if (fromCheckSec > AZIMUTH_CHECK_SEC || toCheckSec > AZIMUTH_CHECK_SEC) warnings.push('WS01'); if (ambiguous) warnings.push('WS04'); pairs.push({ - from: { structure: here.name, no: b.no }, to: { structure: next.name, no: best.c.no }, + from: { structure: here.name, no: b.no }, to: { structure: best.c.structure, no: best.c.no }, distanceM, offsetM: best.offset, azimuthDiffDeg: best.dAz, directionDeg, fromCheckSec, toCheckSec, - slopePct, uphill: best.c.z > b.z ? next.name : here.name, warnings, + slopePct, uphill: best.c.z > b.z ? best.c.structure : here.name, warnings, }); } } diff --git a/tests/bearing-pair.test.ts b/tests/bearing-pair.test.ts index 51a2231..82ddd42 100644 --- a/tests/bearing-pair.test.ts +++ b/tests/bearing-pair.test.ts @@ -124,3 +124,13 @@ describe('교대만 있는 프로젝트 (가져오기 허용 2026-11-06)', () => expect(back.abutments?.map((a) => a.name)).toEqual(['A1']); }); }); + +describe('두 노선 (광주 · 완도) — 목록 순서와 무관하게 거더 기하로 짝 (2026-11-06)', () => { + it('A1-광주, A1-완도, P1-완도, P1-광주 in that order still pair 광주 with 광주 and 완도 with 완도', () => { + const shift = (s: PairStructure, name: string, de: number): PairStructure => ({ name, bearings: s.bearings.map((b) => ({ ...b, x: b.x + de })) }); + const list = [shift(A1, 'A1-광주', 13), shift(A1, 'A1-완도', 0), shift(P1, 'P1-완도', 0), shift(P1, 'P1-광주', 13)]; + const pairs = girderPairs(list); + expect(pairs).toHaveLength(10); + expect(pairs.every((p) => p.from.structure.slice(3) === p.to.structure.slice(3) && p.from.structure.startsWith('A1') && p.from.no === p.to.no)).toBe(true); + }); +});