lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain signalbilder.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { aspectAt, aspectSegments, aspectTotals, isGreenAt } from '@/domain/plan/aspects'; |
| 3 | import type { PlannedSignalGroup } from '@/domain/plan/signalPlan'; |
| 4 | |
| 5 | /* |
| 6 | * Befund F33: Reichte das Gelb des einen Freigabefensters ueber den Beginn des |
| 7 | * naechsten hinweg, kuerzte aspectSegments die FREIGABE statt das Gelb. |
| 8 | * |
| 9 | * Der Anwender sah dadurch in derselben Tabellenzeile zwei einander |
| 10 | * widersprechende Zahlen: In der Spalte "Freigabezeit (Summe)" stand die volle |
| 11 | * Freigabezeit aus dem Plan, in den Spalten Rot-Gelb/Gelb/Rot die um die |
| 12 | * Ueberschneidung gekuerzte Summe aus den Signalbildern - und der gezeichnete |
| 13 | * Balken zeigte Gelb, wo die Simulation Freigabe meldete. |
| 14 | */ |
| 15 | |
| 16 | type Pruefgruppe = Pick<PlannedSignalGroup, 'greens' | 'times'>; |
| 17 | |
| 18 | /** Zwei Freigabefenster ohne Abstand; das Gelb des ersten faellt ins zweite. */ |
| 19 | const OHNE_ABSTAND: Pruefgruppe = { |
| 20 | greens: [ |
| 21 | { start: 0, duration: 20 }, |
| 22 | { start: 20, duration: 15 }, |
| 23 | ], |
| 24 | times: { minGreen: 5, yellow: 3, redYellow: 1 }, |
| 25 | }; |
| 26 | |
| 27 | /** Drei Sekunden Abstand bei fuenf Sekunden Gelbzeit. */ |
| 28 | const KUERZER_ALS_GELB: Pruefgruppe = { |
| 29 | greens: [ |
| 30 | { start: 0, duration: 20 }, |
| 31 | { start: 23, duration: 15 }, |
| 32 | ], |
| 33 | times: { minGreen: 5, yellow: 5, redYellow: 1 }, |
| 34 | }; |
| 35 | |
| 36 | const UMLAUF = 60; |
| 37 | |
| 38 | describe('Signalbilder bei dicht aufeinanderfolgenden Freigabefenstern', () => { |
| 39 | it('zeigt die volle Freigabezeit, wenn zwei Fenster ohne Abstand aufeinanderfolgen', () => { |
| 40 | const totals = aspectTotals(OHNE_ABSTAND, UMLAUF); |
| 41 | const freigabe = OHNE_ABSTAND.greens.reduce((summe, g) => summe + g.duration, 0); |
| 42 | |
| 43 | expect(totals.gruen).toBeCloseTo(freigabe, 3); |
| 44 | }); |
| 45 | |
| 46 | it('kuerzt das Gelb und nicht die Freigabe, wenn der Abstand kleiner als die Gelbzeit ist', () => { |
| 47 | const totals = aspectTotals(KUERZER_ALS_GELB, UMLAUF); |
| 48 | const freigabe = KUERZER_ALS_GELB.greens.reduce((summe, g) => summe + g.duration, 0); |
| 49 | |
| 50 | expect(totals.gruen).toBeCloseTo(freigabe, 3); |
| 51 | // Von den 5 s Gelb des ersten Fensters bleiben die 3 s bis zum naechsten |
| 52 | // Freigabebeginn; das zweite Fenster behaelt seine vollen 5 s. |
| 53 | expect(totals.gelb).toBeCloseTo(8, 3); |
| 54 | }); |
| 55 | |
| 56 | it('sagt dasselbe wie die Freigabezeitfenster - Signalbild gegen isGreenAt', () => { |
| 57 | for (const gruppe of [OHNE_ABSTAND, KUERZER_ALS_GELB]) { |
| 58 | for (let t = 0; t < UMLAUF; t += 0.5) { |
| 59 | expect(aspectAt(gruppe, t, UMLAUF) === 'gruen', `t=${t}s`).toBe( |
| 60 | isGreenAt(gruppe, t, UMLAUF), |
| 61 | ); |
| 62 | } |
| 63 | } |
| 64 | }); |
| 65 | |
| 66 | it('zerlegt den Umlauf weiterhin lueckenlos und ueberschneidungsfrei', () => { |
| 67 | for (const gruppe of [OHNE_ABSTAND, KUERZER_ALS_GELB]) { |
| 68 | const segmente = aspectSegments(gruppe, UMLAUF); |
| 69 | let cursor = 0; |
| 70 | for (const segment of segmente) { |
| 71 | expect(segment.start).toBeCloseTo(cursor, 3); |
| 72 | expect(segment.duration).toBeGreaterThan(0); |
| 73 | cursor += segment.duration; |
| 74 | } |
| 75 | expect(cursor).toBeCloseTo(UMLAUF, 3); |
| 76 | } |
| 77 | }); |
| 78 | }); |