lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ src domain rilsa engstelle.ts

9,4 KB Rohdatei
src/domain/rilsa/engstelle.ts — 212 Zeilen
1 import type { KilometersPerHour, Meters, Seconds } from '../units';
2 import {
3 ANLAGENART_GRENZEN,
4 ENGSTELLE_PRAXIS,
5 RILSA_DEFAULTS,
6 type RilsaDefaults,
7 } from './constants';
8 import { computeEngstellenIntergreen } from './intergreen';
9 import { minGreenTime } from './signalTimes';
10 import { minimumCycleTime } from './cycle';
11
12 /**
13 * Was die Laenge einer Engstelle fuer den Betrieb der Anlage bedeutet.
14 *
15 * Stand bis 5.3.4 in src/ui/assistent.ts und damit in der Oberflaechenschicht -
16 * entgegen dem Grundsatz, dass ausschliesslich in src/domain gerechnet wird.
17 * Aufgefallen ist es, weil der Ausdruck dieselben Zahlen braucht: Die
18 * Planunterlage begruendet dort die Obergrenze der Umlaufzeit fuer eine
19 * Arbeitsstelle. Sie trug sie als FESTE Zahlen im Satz, waehrend die Laenge
20 * daneben gerechnet wurde - und die festen Zahlen waren falsch. Zwei Stellen,
21 * die dasselbe herleiten, laufen auseinander; eine gemeinsame rechnet.
22 */
23 export interface EngstellenFolgen {
24 /** Zwischenzeit je Fahrtrichtung - fuer die angelegte Planung exakt. */
25 readonly zwischenzeit: Seconds;
26 /** Kuerzester ueberhaupt schaltbarer Umlauf. */
27 readonly umlaufzeit: Seconds;
28 /** Laengste Rotzeit einer Richtung, mindestens. */
29 readonly wartezeit: Seconds;
30 readonly stufe: 'hinweis' | 'warnung' | 'fehler';
31 /**
32 * Einstufung der Laenge nach Behoerdenpraxis (ENGSTELLE_PRAXIS) - getrennt
33 * von `stufe`, weil sie etwas anderes bewertet: nicht die Wartezeit, sondern
34 * ob die Behoerde eine Baustellenampel dieser Laenge ueberhaupt aufstellt.
35 * `null`, solange die Laenge unauffaellig ist.
36 */
37 readonly praxisHinweis: EngstellenPraxisHinweis | null;
38 }
39
40 /**
41 * Hinweis auf den Abstimmungsbedarf mit der Strassenverkehrsbehoerde.
42 *
43 * BEHOERDENPRAXIS, KEIN REGELWERKSWERT - deshalb ist der Text Teil des
44 * Ergebnisses: Wer ihn zeigt, zeigt auch die Einordnung, und Assistent und
45 * Pruefbericht sagen dasselbe (Fassung 5.4.0, Befund C5).
46 */
47 export interface EngstellenPraxisHinweis {
48 readonly stufe: 'hinweis' | 'warnung';
49 /** Schwelle in Metern, die ueberschritten ist. */
50 readonly ab: Meters;
51 readonly text: string;
52 }
53
54 /**
55 * Stuft eine Engstellenlaenge nach Behoerdenpraxis ein.
56 *
57 * Ab ENGSTELLE_PRAXIS.hinweisAb ein Hinweis, ab ENGSTELLE_PRAXIS.warnungAb eine
58 * Warnung, jeweils einschliesslich; darunter `null`. Vor Fassung 5.4.0
59 * (Befund C5) meldete das Programm zwischen 400 und rund 835 m nichts.
60 */
61 export function engstellenPraxisHinweis(laenge: Meters): EngstellenPraxisHinweis | null {
62 if (!Number.isFinite(laenge) || laenge < ENGSTELLE_PRAXIS.hinweisAb) return null;
63 const warnung = laenge >= ENGSTELLE_PRAXIS.warnungAb;
64 return {
65 stufe: warnung ? 'warnung' : 'hinweis',
66 ab: warnung ? ENGSTELLE_PRAXIS.warnungAb : ENGSTELLE_PRAXIS.hinweisAb,
67 text:
68 `Die Engstelle ist ${meter(laenge)} m lang. Behördenpraxis (z. B. Hessen Mobil), kein ` +
69 `Regelwerkswert: Baustellenampeln werden bis ${meter(ENGSTELLE_PRAXIS.hinweisAb)} m ` +
70 `aufgestellt, ausnahmsweise und mit Zustimmung bis ${meter(ENGSTELLE_PRAXIS.warnungAb)} m. ` +
71 // "erreicht oder ueberschreitet", nicht "liegt ueber": Die Schwellen
72 // gelten einschliesslich (>=), und bei genau 400 m bzw. 600 m waere
73 // "ueber 400 m" falsch.
74 (warnung
75 ? `Diese Länge erreicht oder überschreitet auch die Ausnahmegrenze von ` +
76 `${meter(ENGSTELLE_PRAXIS.warnungAb)} m; ohne ausdrückliche Zustimmung der ` +
77 'Straßenverkehrsbehörde ist mit einer Ablehnung zu rechnen.'
78 : `Diese Länge erreicht oder überschreitet die übliche Obergrenze von ` +
79 `${meter(ENGSTELLE_PRAXIS.hinweisAb)} m; die Abstimmung mit der ` +
80 'Straßenverkehrsbehörde ist vor der Anordnung zu suchen.'),
81 };
82 }
83
84 /**
85 * Engstellenlaenge, wie dieses Programm sie abbildet: der groesste Raeumweg der
86 * Konfliktbeziehungen. Bei einer einstreifigen Verkehrsfuehrung IST der Raeumweg
87 * die Engstelle; eine eigene Laengenangabe fuehrt das Modell nicht. Ohne
88 * Beziehung mit Raeumweg ist die Laenge unbekannt - `null`, nicht 0: Aus 0 m
89 * folgte sonst "kurze Engstelle".
90 */
91 export function engstellenlaengeAusRaeumwegen(
92 conflicts: readonly { readonly clearingDistance: Meters }[],
93 ): Meters | null {
94 let laenge = 0;
95 for (const conflict of conflicts) {
96 if (Number.isFinite(conflict.clearingDistance) && conflict.clearingDistance > laenge) {
97 laenge = conflict.clearingDistance;
98 }
99 }
100 return laenge > 0 ? laenge : null;
101 }
102
103 /**
104 * Rechnet aus, was die Laenge der Engstelle bewirkt.
105 *
106 * DAS MUSS DER ANWENDER VOR DEM ANLEGEN ERFAHREN: Die Laenge ist bei einer
107 * einstreifigen Fuehrung die einzige Stellschraube, und sie geht doppelt in den
108 * Umlauf ein - je Fahrtrichtung einmal. Wer 800 m absperrt, laesst die
109 * Gegenrichtung fast drei Minuten warten, und das erfaehrt er sonst erst, wenn
110 * die Planung fertig ist und der Pruefbericht sie beanstandet.
111 *
112 * Gerechnet wird ausschliesslich mit den Funktionen des Regelwerks, und zwar mit
113 * denselben Eingangsgroessen, die das angelegte Projekt spaeter traegt (Pkw als
114 * Fahrzeugart, Einfahrweg 0). Die genannte Zwischenzeit ist deshalb keine
115 * Schaetzung, sondern der Wert, der anschliessend im Plan steht.
116 *
117 * Umlaufzeit und Wartezeit sind dagegen UNTERGRENZEN: Sie unterstellen die
118 * kuerzest zulaessige Freigabezeit fuer die Gegenrichtung. Mit einer brauchbaren
119 * Freigabezeit fallen beide laenger aus - die Warnung schlaegt also eher zu spaet
120 * als zu frueh an, und das ist die sichere Seite fuer eine Warnung, die zum
121 * Verzicht auf die Ampel raten kann.
122 *
123 * `defaults` SIND DIE KENNWERTE DER PLANUNG (Fassung 5.4.0, Befund D1): Bis
124 * dahin rechnete diese Funktion fest mit RILSA_DEFAULTS, waehrend der
125 * Plan daneben mit den aufgeloesten Vorgaben des Projekts rechnete. Wer die
126 * Raeumgeschwindigkeit auf 8 m/s gesetzt hatte, bekam in Assistent und
127 * Ausdruck eine Zwischenzeit, die im Plan nicht vorkommt - und im Ausdruck ist
128 * genau diese Zahl die BEGRUENDUNG der Umlaufzeitobergrenze. Der Vorgabewert
129 * haelt alte Aufrufe uebersetzbar; jeder Aufrufer soll die Kennwerte seines
130 * Projekts durchreichen.
131 */
132 export function engstellenFolgen(
133 laenge: Meters,
134 vZul: KilometersPerHour,
135 defaults: RilsaDefaults = RILSA_DEFAULTS,
136 ): EngstellenFolgen {
137 /*
138 * NACHGEZOGEN (Fassung 5.27.0): Gerechnet wird das Verfahren des Abschnitts
139 * 5.2 (Engstellensignalisierung) und nicht mehr das des Abschnitts 2.
140 *
141 * Der Grundsatz dieser Funktion bleibt und ist der Grund fuer die
142 * Aenderung: Die genannte Zwischenzeit ist keine Schaetzung, sondern der
143 * Wert, der anschliessend im Plan steht. Seit der Plan die Engstelle nach
144 * 5.2 rechnet (resolveIntergreens), muss diese Vorschau es auch - sonst
145 * nennt der Assistent eine Zahl, die der Plan daneben nicht fuehrt, und
146 * die Planunterlage begruendet ihre Umlaufzeitobergrenze mit einer dritten.
147 *
148 * `defaults` geht hier nicht mehr ein: Das Verfahren des Abschnitts 5.2
149 * kennt keine Vorgabenwerte - Ueberfahrzeit und Raeumgeschwindigkeit stehen
150 * fest im Regelwerk. Der Parameter bleibt, weil `umlaufBeiFreigabe` und die
151 * Schranken der Anlagenart ihn weiter brauchen.
152 */
153 const zwischenzeit = computeEngstellenIntergreen(
154 { mode: 'kfz', movement: 'geradeaus', clearingDistance: laenge, vehicleClass: 'pkw', vZul },
155 { mode: 'kfz', movement: 'geradeaus', enteringDistance: 0 },
156 defaults,
157 ).intergreen;
158
159 const freigabe = minGreenTime('kfz', defaults);
160 const umlaufzeit = minimumCycleTime([freigabe, freigabe], [zwischenzeit, zwischenzeit]);
161 // Die Wartezeit ist der Umlauf ABZUEGLICH der eigenen Freigabezeit: die Rotzeit
162 // dessen, der die Haltlinie erreicht, sobald seine Richtung endet.
163 const wartezeit = minimumCycleTime([freigabe], [zwischenzeit, zwischenzeit]);
164
165 /*
166 * Die Schranken bleiben die der ANLAGENART und werden bewusst nicht aus
167 * `defaults` genommen: Wartezeitwarnung, Wartezeitfehler und die 300 s
168 * Umlauf sind Schwellen dieses Programms (ANLAGENART_GRENZEN, Befund B8),
169 * keine anpassbaren Vorgaben. Aenderte sie ein Vorgabewert mit, liesse sich
170 * die Warnung, die vom Verzicht auf die Ampel abraet, wegstellen.
171 */
172 const grenzen = ANLAGENART_GRENZEN.einstreifig;
173 const stufe: EngstellenFolgen['stufe'] =
174 wartezeit >= grenzen.warteZeitFehler || umlaufzeit > grenzen.cycleTime.max
175 ? 'fehler'
176 : wartezeit >= grenzen.warteZeitWarnung
177 ? 'warnung'
178 : 'hinweis';
179
180 return {
181 zwischenzeit,
182 umlaufzeit,
183 wartezeit,
184 stufe,
185 praxisHinweis: engstellenPraxisHinweis(laenge),
186 };
187 }
188
189 /**
190 * Umlauf bei einer BRAUCHBAREN Freigabezeit statt der kuerzest zulaessigen.
191 *
192 * Fuer Begruendungstexte: Die Untergrenze aus engstellenFolgen unterstellt 5 s
193 * Freigabe je Richtung und ist als Schaltvorschlag unbrauchbar. Wer erklaeren
194 * will, warum eine Obergrenze dort liegt, wo sie liegt, muss mit einer
195 * Freigabezeit rechnen, die man tatsaechlich anordnen wuerde.
196 *
197 * `defaults` wie bei engstellenFolgen (Befund D1): Der Satz, der diese Zahl
198 * traegt, steht im Ausdruck neben den Zwischenzeiten des Plans.
199 */
200 export function umlaufBeiFreigabe(
201 laenge: Meters,
202 vZul: KilometersPerHour,
203 freigabe: Seconds,
204 defaults: RilsaDefaults = RILSA_DEFAULTS,
205 ): Seconds {
206 const { zwischenzeit } = engstellenFolgen(laenge, vZul, defaults);
207 return minimumCycleTime([freigabe, freigabe], [zwischenzeit, zwischenzeit]);
208 }
209
210 function meter(wert: number): string {
211 return Number.isInteger(wert) ? String(wert) : wert.toFixed(1).replace('.', ',');
212 }