import type { KilometersPerHour, Meters, Seconds } from '../units'; import { ANLAGENART_GRENZEN, ENGSTELLE_PRAXIS, RILSA_DEFAULTS, type RilsaDefaults, } from './constants'; import { computeEngstellenIntergreen } from './intergreen'; import { minGreenTime } from './signalTimes'; import { minimumCycleTime } from './cycle'; /** * Was die Laenge einer Engstelle fuer den Betrieb der Anlage bedeutet. * * Stand bis 5.3.4 in src/ui/assistent.ts und damit in der Oberflaechenschicht - * entgegen dem Grundsatz, dass ausschliesslich in src/domain gerechnet wird. * Aufgefallen ist es, weil der Ausdruck dieselben Zahlen braucht: Die * Planunterlage begruendet dort die Obergrenze der Umlaufzeit fuer eine * Arbeitsstelle. Sie trug sie als FESTE Zahlen im Satz, waehrend die Laenge * daneben gerechnet wurde - und die festen Zahlen waren falsch. Zwei Stellen, * die dasselbe herleiten, laufen auseinander; eine gemeinsame rechnet. */ export interface EngstellenFolgen { /** Zwischenzeit je Fahrtrichtung - fuer die angelegte Planung exakt. */ readonly zwischenzeit: Seconds; /** Kuerzester ueberhaupt schaltbarer Umlauf. */ readonly umlaufzeit: Seconds; /** Laengste Rotzeit einer Richtung, mindestens. */ readonly wartezeit: Seconds; readonly stufe: 'hinweis' | 'warnung' | 'fehler'; /** * Einstufung der Laenge nach Behoerdenpraxis (ENGSTELLE_PRAXIS) - getrennt * von `stufe`, weil sie etwas anderes bewertet: nicht die Wartezeit, sondern * ob die Behoerde eine Baustellenampel dieser Laenge ueberhaupt aufstellt. * `null`, solange die Laenge unauffaellig ist. */ readonly praxisHinweis: EngstellenPraxisHinweis | null; } /** * Hinweis auf den Abstimmungsbedarf mit der Strassenverkehrsbehoerde. * * BEHOERDENPRAXIS, KEIN REGELWERKSWERT - deshalb ist der Text Teil des * Ergebnisses: Wer ihn zeigt, zeigt auch die Einordnung, und Assistent und * Pruefbericht sagen dasselbe (Fassung 5.4.0, Befund C5). */ export interface EngstellenPraxisHinweis { readonly stufe: 'hinweis' | 'warnung'; /** Schwelle in Metern, die ueberschritten ist. */ readonly ab: Meters; readonly text: string; } /** * Stuft eine Engstellenlaenge nach Behoerdenpraxis ein. * * Ab ENGSTELLE_PRAXIS.hinweisAb ein Hinweis, ab ENGSTELLE_PRAXIS.warnungAb eine * Warnung, jeweils einschliesslich; darunter `null`. Vor Fassung 5.4.0 * (Befund C5) meldete das Programm zwischen 400 und rund 835 m nichts. */ export function engstellenPraxisHinweis(laenge: Meters): EngstellenPraxisHinweis | null { if (!Number.isFinite(laenge) || laenge < ENGSTELLE_PRAXIS.hinweisAb) return null; const warnung = laenge >= ENGSTELLE_PRAXIS.warnungAb; return { stufe: warnung ? 'warnung' : 'hinweis', ab: warnung ? ENGSTELLE_PRAXIS.warnungAb : ENGSTELLE_PRAXIS.hinweisAb, text: `Die Engstelle ist ${meter(laenge)} m lang. Behördenpraxis (z. B. Hessen Mobil), kein ` + `Regelwerkswert: Baustellenampeln werden bis ${meter(ENGSTELLE_PRAXIS.hinweisAb)} m ` + `aufgestellt, ausnahmsweise und mit Zustimmung bis ${meter(ENGSTELLE_PRAXIS.warnungAb)} m. ` + // "erreicht oder ueberschreitet", nicht "liegt ueber": Die Schwellen // gelten einschliesslich (>=), und bei genau 400 m bzw. 600 m waere // "ueber 400 m" falsch. (warnung ? `Diese Länge erreicht oder überschreitet auch die Ausnahmegrenze von ` + `${meter(ENGSTELLE_PRAXIS.warnungAb)} m; ohne ausdrückliche Zustimmung der ` + 'Straßenverkehrsbehörde ist mit einer Ablehnung zu rechnen.' : `Diese Länge erreicht oder überschreitet die übliche Obergrenze von ` + `${meter(ENGSTELLE_PRAXIS.hinweisAb)} m; die Abstimmung mit der ` + 'Straßenverkehrsbehörde ist vor der Anordnung zu suchen.'), }; } /** * Engstellenlaenge, wie dieses Programm sie abbildet: der groesste Raeumweg der * Konfliktbeziehungen. Bei einer einstreifigen Verkehrsfuehrung IST der Raeumweg * die Engstelle; eine eigene Laengenangabe fuehrt das Modell nicht. Ohne * Beziehung mit Raeumweg ist die Laenge unbekannt - `null`, nicht 0: Aus 0 m * folgte sonst "kurze Engstelle". */ export function engstellenlaengeAusRaeumwegen( conflicts: readonly { readonly clearingDistance: Meters }[], ): Meters | null { let laenge = 0; for (const conflict of conflicts) { if (Number.isFinite(conflict.clearingDistance) && conflict.clearingDistance > laenge) { laenge = conflict.clearingDistance; } } return laenge > 0 ? laenge : null; } /** * Rechnet aus, was die Laenge der Engstelle bewirkt. * * DAS MUSS DER ANWENDER VOR DEM ANLEGEN ERFAHREN: Die Laenge ist bei einer * einstreifigen Fuehrung die einzige Stellschraube, und sie geht doppelt in den * Umlauf ein - je Fahrtrichtung einmal. Wer 800 m absperrt, laesst die * Gegenrichtung fast drei Minuten warten, und das erfaehrt er sonst erst, wenn * die Planung fertig ist und der Pruefbericht sie beanstandet. * * Gerechnet wird ausschliesslich mit den Funktionen des Regelwerks, und zwar mit * denselben Eingangsgroessen, die das angelegte Projekt spaeter traegt (Pkw als * Fahrzeugart, Einfahrweg 0). Die genannte Zwischenzeit ist deshalb keine * Schaetzung, sondern der Wert, der anschliessend im Plan steht. * * Umlaufzeit und Wartezeit sind dagegen UNTERGRENZEN: Sie unterstellen die * kuerzest zulaessige Freigabezeit fuer die Gegenrichtung. Mit einer brauchbaren * Freigabezeit fallen beide laenger aus - die Warnung schlaegt also eher zu spaet * als zu frueh an, und das ist die sichere Seite fuer eine Warnung, die zum * Verzicht auf die Ampel raten kann. * * `defaults` SIND DIE KENNWERTE DER PLANUNG (Fassung 5.4.0, Befund D1): Bis * dahin rechnete diese Funktion fest mit RILSA_DEFAULTS, waehrend der * Plan daneben mit den aufgeloesten Vorgaben des Projekts rechnete. Wer die * Raeumgeschwindigkeit auf 8 m/s gesetzt hatte, bekam in Assistent und * Ausdruck eine Zwischenzeit, die im Plan nicht vorkommt - und im Ausdruck ist * genau diese Zahl die BEGRUENDUNG der Umlaufzeitobergrenze. Der Vorgabewert * haelt alte Aufrufe uebersetzbar; jeder Aufrufer soll die Kennwerte seines * Projekts durchreichen. */ export function engstellenFolgen( laenge: Meters, vZul: KilometersPerHour, defaults: RilsaDefaults = RILSA_DEFAULTS, ): EngstellenFolgen { /* * NACHGEZOGEN (Fassung 5.27.0): Gerechnet wird das Verfahren des Abschnitts * 5.2 (Engstellensignalisierung) und nicht mehr das des Abschnitts 2. * * Der Grundsatz dieser Funktion bleibt und ist der Grund fuer die * Aenderung: Die genannte Zwischenzeit ist keine Schaetzung, sondern der * Wert, der anschliessend im Plan steht. Seit der Plan die Engstelle nach * 5.2 rechnet (resolveIntergreens), muss diese Vorschau es auch - sonst * nennt der Assistent eine Zahl, die der Plan daneben nicht fuehrt, und * die Planunterlage begruendet ihre Umlaufzeitobergrenze mit einer dritten. * * `defaults` geht hier nicht mehr ein: Das Verfahren des Abschnitts 5.2 * kennt keine Vorgabenwerte - Ueberfahrzeit und Raeumgeschwindigkeit stehen * fest im Regelwerk. Der Parameter bleibt, weil `umlaufBeiFreigabe` und die * Schranken der Anlagenart ihn weiter brauchen. */ const zwischenzeit = computeEngstellenIntergreen( { mode: 'kfz', movement: 'geradeaus', clearingDistance: laenge, vehicleClass: 'pkw', vZul }, { mode: 'kfz', movement: 'geradeaus', enteringDistance: 0 }, defaults, ).intergreen; const freigabe = minGreenTime('kfz', defaults); const umlaufzeit = minimumCycleTime([freigabe, freigabe], [zwischenzeit, zwischenzeit]); // Die Wartezeit ist der Umlauf ABZUEGLICH der eigenen Freigabezeit: die Rotzeit // dessen, der die Haltlinie erreicht, sobald seine Richtung endet. const wartezeit = minimumCycleTime([freigabe], [zwischenzeit, zwischenzeit]); /* * Die Schranken bleiben die der ANLAGENART und werden bewusst nicht aus * `defaults` genommen: Wartezeitwarnung, Wartezeitfehler und die 300 s * Umlauf sind Schwellen dieses Programms (ANLAGENART_GRENZEN, Befund B8), * keine anpassbaren Vorgaben. Aenderte sie ein Vorgabewert mit, liesse sich * die Warnung, die vom Verzicht auf die Ampel abraet, wegstellen. */ const grenzen = ANLAGENART_GRENZEN.einstreifig; const stufe: EngstellenFolgen['stufe'] = wartezeit >= grenzen.warteZeitFehler || umlaufzeit > grenzen.cycleTime.max ? 'fehler' : wartezeit >= grenzen.warteZeitWarnung ? 'warnung' : 'hinweis'; return { zwischenzeit, umlaufzeit, wartezeit, stufe, praxisHinweis: engstellenPraxisHinweis(laenge), }; } /** * Umlauf bei einer BRAUCHBAREN Freigabezeit statt der kuerzest zulaessigen. * * Fuer Begruendungstexte: Die Untergrenze aus engstellenFolgen unterstellt 5 s * Freigabe je Richtung und ist als Schaltvorschlag unbrauchbar. Wer erklaeren * will, warum eine Obergrenze dort liegt, wo sie liegt, muss mit einer * Freigabezeit rechnen, die man tatsaechlich anordnen wuerde. * * `defaults` wie bei engstellenFolgen (Befund D1): Der Satz, der diese Zahl * traegt, steht im Ausdruck neben den Zwischenzeiten des Plans. */ export function umlaufBeiFreigabe( laenge: Meters, vZul: KilometersPerHour, freigabe: Seconds, defaults: RilsaDefaults = RILSA_DEFAULTS, ): Seconds { const { zwischenzeit } = engstellenFolgen(laenge, vZul, defaults); return minimumCycleTime([freigabe, freigabe], [zwischenzeit, zwischenzeit]); } function meter(wert: number): string { return Number.isInteger(wert) ? String(wert) : wert.toFixed(1).replace('.', ','); }