lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain koordinierungImUmlauf.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { RILSA_DEFAULTS } from '@/domain/rilsa/constants'; |
| 3 | import { computeCoordination } from '@/domain/rilsa/coordination'; |
| 4 | |
| 5 | /* |
| 6 | * Befunde S2 und S4, behoben in Fassung 5.10.0. |
| 7 | * |
| 8 | * Der Versatz wurde zyklisch gebildet (modulo Umlaufzeit), die Bandbreite |
| 9 | * dagegen auf der Geraden: "greenStart - Reisezeit" wurde ohne Faltung in den |
| 10 | * Umlauf gegen die uebrigen Fenster geschnitten. Ein Signalprogramm wiederholt |
| 11 | * sich aber mit jedem Umlauf; die Fenster liegen auf einem Kreis der Laenge tU. |
| 12 | * Sobald "greenStart - Reisezeit" unter den Umlaufbeginn fiel, meldete die |
| 13 | * Rechnung "keine Bandbreite" - und zwar fuer genau die Versaetze, die sie in |
| 14 | * intersections[i].offset selbst empfiehlt. |
| 15 | * |
| 16 | * Fuer Fassung 5 ist gegenueber 4.x zugesagt, die Rechnung |
| 17 | * ermittle "die tatsächliche Schnittmenge der Freigabefenster". Sie wird von |
| 18 | * keiner Ansicht aufgerufen, gehoert aber ueber src/domain/rilsa/index.ts zur |
| 19 | * oeffentlichen Schnittstelle des Fachkerns. |
| 20 | */ |
| 21 | |
| 22 | describe('Bandbreite der Koordinierung auf dem Umlauf (Befunde S2 und S4)', () => { |
| 23 | it('faltet Freigabefenster in den Umlauf, wenn die Reisezeit den Umlauf uebersteigt', () => { |
| 24 | // tU 60 s, 50 km/h, 1000 m -> Reisezeit 72,0 s, empfohlener Versatz 12,0 s. |
| 25 | // Mit genau diesem Versatz decken sich beide Fenster vollstaendig. |
| 26 | const result = computeCoordination( |
| 27 | [ |
| 28 | { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 30 }, |
| 29 | { id: 'b', name: 'Knoten B', distanceToPrevious: 1000, greenStart: 12, greenDuration: 30 }, |
| 30 | ], |
| 31 | 60, |
| 32 | 50, |
| 33 | ); |
| 34 | |
| 35 | expect(result.intersections[1]!.travelTime).toBeCloseTo(72, 1); |
| 36 | expect(result.intersections[1]!.offset).toBeCloseTo(12, 1); |
| 37 | expect(result.bandwidth).toBe(30); |
| 38 | expect(result.bandwidthRatio).toBe(0.5); |
| 39 | expect(result.quality).toBe('gut'); |
| 40 | expect(result.notes.some((n) => n.code === 'keine-bandbreite')).toBe(false); |
| 41 | }); |
| 42 | |
| 43 | it('faltet auch, wenn nur der Umlaufbeginn ueberschritten wird', () => { |
| 44 | // tU 60 s, 60 km/h, 500 m -> Reisezeit 30,0 s. A ab 40 s, B ab 10 s: der |
| 45 | // Versatz passt, das zurueckgerechnete Fenster von B liegt bei 40 s. |
| 46 | const result = computeCoordination( |
| 47 | [ |
| 48 | { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 40, greenDuration: 20 }, |
| 49 | { id: 'b', name: 'Knoten B', distanceToPrevious: 500, greenStart: 10, greenDuration: 20 }, |
| 50 | ], |
| 51 | 60, |
| 52 | 60, |
| 53 | ); |
| 54 | |
| 55 | expect(result.intersections[1]!.travelTime).toBeCloseTo(30, 1); |
| 56 | expect(result.bandwidth).toBe(20); |
| 57 | expect(result.notes.some((n) => n.code === 'keine-bandbreite')).toBe(false); |
| 58 | }); |
| 59 | |
| 60 | it('nimmt den laengsten zusammenhaengenden Bogen, nicht die Summe der Teilbogen', () => { |
| 61 | // tU 60 s, 60 km/h, 1000 m -> Reisezeit 60,0 s, also ein voller Umlauf. |
| 62 | // A [0; 50), B zurueckgerechnet [20; 70) = [20; 60) und [0; 10). Die |
| 63 | // Schnittmenge zerfaellt in zwei Boegen von 30 s und 10 s; nutzbar ist der |
| 64 | // laengere, nicht die Summe von 40 s - und nicht der ungefaltete Rest von |
| 65 | // 10 s, den die geradlinige Rechnung uebrig liess. |
| 66 | const result = computeCoordination( |
| 67 | [ |
| 68 | { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 50 }, |
| 69 | { id: 'b', name: 'Knoten B', distanceToPrevious: 1000, greenStart: 20, greenDuration: 50 }, |
| 70 | ], |
| 71 | 60, |
| 72 | 60, |
| 73 | ); |
| 74 | |
| 75 | expect(result.intersections[1]!.travelTime).toBeCloseTo(60, 1); |
| 76 | expect(result.bandwidth).toBe(30); |
| 77 | }); |
| 78 | |
| 79 | it('nimmt die Progressionsgeschwindigkeit aus den Projektvorgaben, nicht aus den Regelwerten', () => { |
| 80 | const vorgaben = { |
| 81 | ...RILSA_DEFAULTS, |
| 82 | coordination: { ...RILSA_DEFAULTS.coordination, defaultSpeed: 40 }, |
| 83 | }; |
| 84 | const result = computeCoordination( |
| 85 | [ |
| 86 | { id: 'a', name: 'Knoten A', distanceToPrevious: 0, greenStart: 0, greenDuration: 30 }, |
| 87 | { id: 'b', name: 'Knoten B', distanceToPrevious: 400, greenStart: 36, greenDuration: 30 }, |
| 88 | ], |
| 89 | 90, |
| 90 | undefined, |
| 91 | vorgaben, |
| 92 | ); |
| 93 | |
| 94 | expect(RILSA_DEFAULTS.coordination.defaultSpeed).toBe(50); |
| 95 | expect(result.speed).toBe(40); |
| 96 | // 400 m bei 40 km/h = 11,111 m/s -> 36,0 s, nicht 28,8 s wie bei 50 km/h. |
| 97 | expect(result.intersections[1]!.travelTime).toBeCloseTo(36, 1); |
| 98 | }); |
| 99 | }); |