import type { KilometersPerHour, Meters, MetersPerSecond, Seconds } from '../units'; import { isFiniteNumber } from '../units'; import { OEPNV_UEBERGANGSZEIT_STAFFEL, RILSA_DEFAULTS, type RilsaDefaults } from './constants'; import type { SignalGroupTimes, TrafficMode } from './types'; /** * Gelbzeit einer Signalgruppe. * * KORREKTUR gegenueber dem Altbestand: Dort lieferte die Funktion fuer * Fussgaenger die Gelbzeit des Radverkehrs (3 s), weil der Fussgaenger-Fall * durch den Sammelzweig am Ende fiel. Fussgaengersignale haben kein Gelb. */ export function yellowTime( mode: TrafficMode, vZul: KilometersPerHour, defaults: RilsaDefaults = RILSA_DEFAULTS, ): Seconds { switch (mode) { case 'fuss': return 0; case 'rad': return defaults.yellowRad; case 'oepnv': /* * KORREKTUR (Fassung 5.27.0): Hier stand 0 s mit dem Vermerk * "Sondersignale (Balkensignale) kennen kein Gelb". Sie kennen kein * GELB, aber sehr wohl eine Uebergangszeit: das Uebergangssignal nach * Par. 51 BOStrab, dessen Dauer die RiLSA 2015 in Abschnitt 2.4 nach der * betrieblich zugelassenen Hoechstgeschwindigkeit staffelt (4 s bis * 30 km/h bis 8 s bei 70 km/h). Begruendung und die beiden nicht * abgebildeten Ausnahmen stehen bei OEPNV_UEBERGANGSZEIT_STAFFEL. */ return oepnvUebergangszeit(vZul); case 'kfz': { if (!isFiniteNumber(vZul) || vZul <= 0) { // Ohne belastbare Geschwindigkeitsangabe wird die laengste Gelbzeit // angesetzt. Das ist die sichere Seite; die Validierung meldet die // fehlende Angabe zusaetzlich als Fehler. return lastYellowStep(defaults); } for (const step of defaults.yellowBySpeed) { if (vZul <= step.upToKmh) return step.yellow; } return lastYellowStep(defaults); } } } /** * Dauer des Uebergangssignals eines gesondert signalisierten * OePNV-Fahrzeugs nach der Staffel der RiLSA 2015 (Abschnitt 2.4). * * Ohne Vmax und oberhalb des Staffelendes von 70 km/h gilt die oberste Stufe - * die laengere Uebergangszeit ist die sichere Seite, und oberhalb 70 km/h * sollen Lichtsignalanlagen ohnehin nicht betrieben werden. */ function oepnvUebergangszeit(vZul: KilometersPerHour): Seconds { const oberste = OEPNV_UEBERGANGSZEIT_STAFFEL.at(-1)?.uebergang ?? 8; if (!isFiniteNumber(vZul) || vZul <= 0) return oberste; for (const stufe of OEPNV_UEBERGANGSZEIT_STAFFEL) { if (vZul <= stufe.upToKmh) return stufe.uebergang; } return oberste; } function lastYellowStep(defaults: RilsaDefaults): Seconds { const last = defaults.yellowBySpeed[defaults.yellowBySpeed.length - 1]; return last ? last.yellow : 5; } /** * Rot-Gelb-Zeit einer Signalgruppe. * Fussgaenger- und OePNV-Sondersignale haben kein Rot-Gelb. */ export function redYellowTime( mode: TrafficMode, defaults: RilsaDefaults = RILSA_DEFAULTS, ): Seconds { return mode === 'fuss' || mode === 'oepnv' ? 0 : defaults.redYellow; } /** * Mindestfreigabezeit einer Signalgruppe - der REGELWERT je Verkehrsart. * * Fuer Fussgaengergruppen ist das nur die Untergrenze: Daneben gilt die * Furtregel (`furtFreigabebedarf`), und massgebend ist der groessere Wert. * Beides zusammen rechnet `mindestfreigabezeitDerGruppe` in * src/domain/plan/signalPlan.ts, weil dafuer die Konfliktbeziehungen des * Projekts gebraucht werden. */ export function minGreenTime(mode: TrafficMode, defaults: RilsaDefaults = RILSA_DEFAULTS): Seconds { return defaults.minGreen[mode]; } /** * Freigabezeitbedarf einer Fussgaengerfurt nach der Furtregel der RiLSA 2015. * * Ein Fussgaenger, der zu Beginn der Freigabezeit die Furt betritt, muss * waehrend der Freigabe die HALBE Furt zuruecklegen koennen; mit einer * Zusatzeinrichtung fuer Blinde und Sehbehinderte (DIN 32981) die GANZE Furt. * Zeitbedarf = Furtweg / vr, mit der Raeumgeschwindigkeit der Gruppe * (Regelwert 1,2 m/s; 1,0 m/s bei erhoehtem Zeitbedarf). * * NEU (Fassung 5.4.0, Befund C1): Bis dahin galt fuer jede * Fussgaengergruppe allein die feste Mindestfreigabezeit von 5 s. Eine 20 m * lange Furt erhielt damit 5 s Freigabe, obwohl schon die halbe Furt 8,3 s * verlangt - der Fussgaenger stand nach Freigabeende noch 3,3 s vor der Mitte. * * Alle Groessen stehen im Ergebnis, damit die Meldung den Rechenweg nennen kann * und ein Pruefer ihn nachrechnet. `zeitbedarf` ist ungerundet; * `zeitbedarfGerundet` ist der Wert, mit dem der Plan rechnet (ganze Sekunden, * aufgerundet) - HIER gerundet und nicht beim Aufrufer, weil die Rundungsregel * sonst an jeder Ausgabestelle noch einmal stand (signalPlan.ts zweimal, pdf.ts * einmal) und beim naechsten Aufrufer auseinanderliefe. */ export interface FurtFreigabebedarf { /** Furtlaenge in Metern. */ readonly furtlaenge: Meters; /** Ganze Furt (Zusatzeinrichtung fuer Blinde und Sehbehinderte) oder halbe Furt. */ readonly ganzeFurt: boolean; /** Angesetzter Furtweg: Furtlaenge bzw. Furtlaenge / 2. */ readonly furtweg: Meters; /** Angesetzte Raeumgeschwindigkeit vr in m/s. */ readonly vr: MetersPerSecond; /** Zeitbedarf furtweg / vr in Sekunden, ungerundet. */ readonly zeitbedarf: Seconds; /** * Zeitbedarf auf ganze Sekunden AUFGERUNDET - der Wert, der in den Plan * eingeht. Aufgerundet, nie abgerundet: Signalzeiten werden in ganzen * Sekunden geschaltet, und 5,83 s Bedarf sind mit 5 s nicht gedeckt. */ readonly zeitbedarfGerundet: Seconds; } /** * Toleranz gegen Gleitkommarauschen beim Aufrunden: 8,4 m / 1,2 m/s ist * rechnerisch 7,000000000000001 s und darf nicht 8 s ergeben. Eine echte * Ueberschreitung um weniger als eine Mikrosekunde gibt es bei Wegen in * Metern (zwei Nachkommastellen) und Geschwindigkeiten mit einer * Nachkommastelle nicht. */ const RUNDUNGSTOLERANZ: Seconds = 1e-6; export function furtFreigabebedarf( furtlaenge: Meters, vr: MetersPerSecond, ganzeFurt: boolean, ): FurtFreigabebedarf { const furtweg = ganzeFurt ? furtlaenge : furtlaenge / 2; // vr <= 0 kann aus den Vorgaben nicht kommen (Schranken in settings.ts); // hier trotzdem abgesichert, damit nie "Infinity s" entsteht. const zeitbedarf = vr > 0 ? furtweg / vr : 0; const zeitbedarfGerundet = Math.max(0, Math.ceil(zeitbedarf - RUNDUNGSTOLERANZ)); return { furtlaenge, ganzeFurt, furtweg, vr, zeitbedarf, zeitbedarfGerundet }; } /** Alle Signalzeiten einer Signalgruppe in einem Zug. */ export function signalGroupTimes( mode: TrafficMode, vZul: KilometersPerHour, defaults: RilsaDefaults = RILSA_DEFAULTS, ): SignalGroupTimes { return { minGreen: minGreenTime(mode, defaults), yellow: yellowTime(mode, vZul, defaults), redYellow: redYellowTime(mode, defaults), }; } /** * Dauer eines vollstaendigen Signalwechsels von Rot nach Rot: * Rot-Gelb + Freigabezeit + Gelb. */ export function signalGroupCycleSpan(times: SignalGroupTimes, green: Seconds): Seconds { return times.redYellow + green + times.yellow; }