lsa-planer

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

/ tests domain messgenauigkeit.test.ts

13,7 KB Rohdatei
tests/domain/messgenauigkeit.test.ts — 317 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import {
3 LEERER_LAGEPLAN,
4 MESSFEHLER_SCHRANKE,
5 ZEICHENFEHLER_BILDPUNKTE,
6 messgenauigkeit,
7 planungsgrundlage,
8 type Lageplan,
9 } from '@/domain/geometrie/lageplan';
10 import { RILSA_DEFAULTS, type RilsaDefaults } from '@/domain/rilsa/constants';
11
12 /**
13 * Messgenauigkeit der Bildunterlage.
14 *
15 * Diese Groesse entscheidet, ob aus einem Luftbild ueberhaupt vermessen werden
16 * darf. Der Schaden liegt in der unsicheren Richtung: Ein zu grober Ausschnitt,
17 * auf dem jemand ahnungslos eine Fussgaengerfurt abgreift, erzeugt eine zu
18 * KURZE Zwischenzeit. Geprueft werden deshalb vor allem die Raender der
19 * Einstufung - dort faellt die Entscheidung.
20 */
21
22 /**
23 * Lageplan mit einem Massstab von `meterJeBildpunkt`.
24 * Die Bezugsstrecke ist 100 Bildpunkte lang; ihre Laenge in Metern setzt den
25 * Massstab.
26 */
27 function mitMassstab(meterJeBildpunkt: number): Lageplan {
28 return {
29 arbeitsbereiche: [],
30 signalgeber: [],
31 bild: {
32 datenUrl: 'data:image/png;base64,AAAA',
33 breite: 1000,
34 hoehe: 1000,
35 herkunft: 'Pruefbild',
36 geladenAm: '2026-03-04T09:15:00.000Z',
37 },
38 kalibrierung: {
39 von: { x: 0, y: 0 },
40 bis: { x: 100, y: 0 },
41 laengeMeter: meterJeBildpunkt * 100,
42 herkunft: 'georeferenziert',
43 },
44 haltlinien: [],
45 linien: [],
46 };
47 }
48
49 /*
50 * KENNWERTE, AUS DENEN HIER NACHGERECHNET WIRD
51 *
52 * Die Zahlen stehen ausgeschrieben, damit im Test erkennbar bleibt, woher ein
53 * erwarteter Wert kommt. Sie sind dieselben wie in RILSA_DEFAULTS - laeuft
54 * eine der beiden Seiten weg, faellt genau dieser Fall auf.
55 *
56 * Die PAARUNG ist die eigentliche Aussage: Der Fussgaengerfehler beschreibt den
57 * Konflikt "Fussgaenger raeumt die Furt, Kraftfahrzeug faehrt ein". Angesetzt
58 * wird dabei der mobilitaetseingeschraenkte Fussgaenger - er ist der
59 * Bemessungsfall und die sichere Seite.
60 */
61 /** clearingSpeed.fussReducedMobility - der raeumende Fussgaenger. */
62 const V_RAEUM_FUSS = 1.0;
63 /** clearingSpeed.kfzTurning - abbiegend, der unguenstigere der Kfz-Werte. */
64 const V_RAEUM_KFZ = 7.0;
65 /** enteringSpeed.kfz - das einfahrende Kraftfahrzeug, in BEIDEN Zeilen. */
66 const V_EINFAHR_KFZ = 11.1;
67
68 /** Zeitfehler je Meter Bodenaufloesung: 2 * (1/1,0 + 1/11,1) = 2,180180 s/m. */
69 const JE_METER_FUSS = ZEICHENFEHLER_BILDPUNKTE * (1 / V_RAEUM_FUSS + 1 / V_EINFAHR_KFZ);
70 /** Dasselbe fuer den abbiegenden Kfz-Strom: 2 * (1/7,0 + 1/11,1) = 0,465894 s/m. */
71 const JE_METER_KFZ = ZEICHENFEHLER_BILDPUNKTE * (1 / V_RAEUM_KFZ + 1 / V_EINFAHR_KFZ);
72
73 /**
74 * Groebste Unterlage, auf der eine Furt noch abgreifbar ist.
75 * 0,25 s / 2,180180 s/m = 0,114669 m je Bildpunkt, also gut 11,4 cm.
76 */
77 const SCHRANKE_FUSS_METER = MESSFEHLER_SCHRANKE / JE_METER_FUSS;
78
79 describe('Messgenauigkeit', () => {
80 it('gibt ohne Massstab keine Genauigkeit an', () => {
81 // Ohne Kalibrierung ist unbekannt, wie viel ein Bildpunkt am Boden misst.
82 // Eine Einstufung waere hier frei erfunden.
83 expect(messgenauigkeit(LEERER_LAGEPLAN)).toBeNull();
84 expect(messgenauigkeit({ ...mitMassstab(0.05), kalibrierung: null })).toBeNull();
85 });
86
87 it('rechnet den Zeitfehler aus Weg und Geschwindigkeit', () => {
88 // 0,10 m je Bildpunkt, zwei Bildpunkte Zeichenfehler: 0,20 m auf dem
89 // Raeumweg und noch einmal 0,20 m auf dem Einfahrweg.
90 const g = ZEICHENFEHLER_BILDPUNKTE * 0.1;
91 const genauigkeit = messgenauigkeit(mitMassstab(0.1))!;
92
93 expect(genauigkeit.meterJeBildpunkt).toBeCloseTo(0.1, 9);
94 // Furt: 1,0 m/s raeumend, 11,1 m/s einfahrend - 0,2 * (1 + 0,090090) =
95 // 0,218018 s.
96 expect(genauigkeit.fehlerFussSekunden).toBeCloseTo(
97 g * (1 / V_RAEUM_FUSS + 1 / V_EINFAHR_KFZ),
98 6,
99 );
100 // Abbiegende Kfz: 7,0 m/s raeumend, 11,1 m/s einfahrend - 0,2 * 0,232947 =
101 // 0,046589 s.
102 expect(genauigkeit.fehlerKfzSekunden).toBeCloseTo(g * (1 / V_RAEUM_KFZ + 1 / V_EINFAHR_KFZ), 6);
103 });
104
105 it('paart den raeumenden Fussgaenger mit dem einfahrenden Kraftfahrzeug', () => {
106 // DIE PAARUNG SELBST - der Fehler, der nicht zurueckkommen darf.
107 //
108 // Abgebildet ist der Konflikt "Fussgaenger raeumt die Furt, waehrend ein
109 // KRAFTFAHRZEUG einfaehrt". Der umgekehrte Fall - ein Fussgaenger faehrt
110 // in die Furt ein, die ein Fussgaenger raeumt - kommt als signalisierter
111 // Konflikt praktisch nicht vor. Stand er hier, war der Fehler um zwei
112 // Drittel zu gross: 1/1,2 + 1/1,5 = 1,50 statt 1/1,0 + 1/11,1 = 1,09 - und
113 // keine reale Unterlage erreichte je die Stufe "voll".
114 const plan = mitMassstab(0.1);
115 const g = ZEICHENFEHLER_BILDPUNKTE * 0.1;
116
117 // Die Einfahrgeschwindigkeit der KRAFTFAHRZEUGE geht ein: Halbiert auf
118 // 5,55 m/s ergibt sie 0,2 * (1/1,0 + 1/5,55) = 0,236036 s statt 0,218018 s.
119 const langsamereKfz: RilsaDefaults = {
120 ...RILSA_DEFAULTS,
121 enteringSpeed: { ...RILSA_DEFAULTS.enteringSpeed, kfz: V_EINFAHR_KFZ / 2 },
122 };
123 expect(messgenauigkeit(plan, langsamereKfz)!.fehlerFussSekunden).toBeCloseTo(
124 g * (1 / V_RAEUM_FUSS + 2 / V_EINFAHR_KFZ),
125 6,
126 );
127
128 // Die der Fussgaenger geht NICHT ein: Ein anderer Wert laesst den Fehler
129 // unberuehrt.
130 const andereFussEinfahrt: RilsaDefaults = {
131 ...RILSA_DEFAULTS,
132 enteringSpeed: { ...RILSA_DEFAULTS.enteringSpeed, fuss: 0.8 },
133 };
134 expect(messgenauigkeit(plan, andereFussEinfahrt)!.fehlerFussSekunden).toBe(
135 messgenauigkeit(plan)!.fehlerFussSekunden,
136 );
137
138 // Und geraeumt wird mit dem mobilitaetseingeschraenkten Fussgaenger, nicht
139 // mit dem Regelfussgaenger: Auch dessen Kennwert bleibt ohne Wirkung.
140 const andererRegelfussgaenger: RilsaDefaults = {
141 ...RILSA_DEFAULTS,
142 clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, fuss: 0.9 },
143 };
144 expect(messgenauigkeit(plan, andererRegelfussgaenger)!.fehlerFussSekunden).toBe(
145 messgenauigkeit(plan)!.fehlerFussSekunden,
146 );
147 });
148
149 it('trifft die Geschwindigkeit im Nenner - der Fussgaengerfehler ist der grosse', () => {
150 // Der eigentliche Grund fuer die ganze Groesse: Derselbe Zeichenfehler
151 // kostet an der Furt ein Vielfaches dessen, was er beim Kfz kostet.
152 // Ein Bild, das fuer Fahrspuren reicht, reicht deshalb nicht fuer eine Furt.
153 //
154 // Beide Zeilen haben dieselbe Einfahrgeschwindigkeit; der Unterschied kommt
155 // allein aus dem Raeumen mit 1,0 statt 7,0 m/s. Das Verhaeltnis
156 // (1/1,0 + 1/11,1) / (1/7,0 + 1/11,1) = 4,68 haengt nicht am Massstab.
157 const genauigkeit = messgenauigkeit(mitMassstab(0.2))!;
158 const verhaeltnis = JE_METER_FUSS / JE_METER_KFZ;
159
160 expect(verhaeltnis).toBeCloseTo(4.68, 2);
161 expect(genauigkeit.fehlerFussSekunden / genauigkeit.fehlerKfzSekunden).toBeCloseTo(
162 verhaeltnis,
163 4,
164 );
165 expect(genauigkeit.fehlerFussSekunden).toBeGreaterThan(genauigkeit.fehlerKfzSekunden * 4);
166 });
167
168 it('stuft genau auf der Schranke noch als voll geeignet ein', () => {
169 // 0,114669 m je Bildpunkt: 2 Bildpunkte mal (1/1,0 + 1/11,1) ergeben exakt
170 // 0,25 s. Die Schranke ist eine Obergrenze - sie einzuhalten genuegt.
171 const genauigkeit = messgenauigkeit(mitMassstab(SCHRANKE_FUSS_METER))!;
172
173 expect(SCHRANKE_FUSS_METER).toBeCloseTo(0.114669, 6);
174 expect(genauigkeit.fehlerFussSekunden).toBe(MESSFEHLER_SCHRANKE);
175 expect(genauigkeit.eignung).toBe('voll');
176 });
177
178 it('kippt unmittelbar oberhalb der Schranke auf nur-kfz', () => {
179 // Ein Hundertstel Meter je Bildpunkt groeber - und die Furt ist nicht mehr
180 // abgreifbar: 2 * 0,124669 * 2,180180 = 0,271802 s. Die Kfz-Stroeme bleiben
181 // es mit weitem Abstand (0,058083 s).
182 const genauigkeit = messgenauigkeit(mitMassstab(SCHRANKE_FUSS_METER + 0.01))!;
183
184 expect(genauigkeit.fehlerFussSekunden).toBeCloseTo(
185 (SCHRANKE_FUSS_METER + 0.01) * JE_METER_FUSS,
186 6,
187 );
188 expect(genauigkeit.fehlerFussSekunden).toBeGreaterThan(MESSFEHLER_SCHRANKE);
189 expect(genauigkeit.fehlerKfzSekunden).toBeLessThan(MESSFEHLER_SCHRANKE);
190 expect(genauigkeit.eignung).toBe('nur-kfz');
191 });
192
193 it('haelt bei einem Massstab knapp unter der Schranke die volle Eignung', () => {
194 // Einen Millimeter je Bildpunkt feiner: 0,24782 s, noch unter 0,25 s.
195 const genauigkeit = messgenauigkeit(mitMassstab(SCHRANKE_FUSS_METER - 0.001))!;
196
197 expect(genauigkeit.fehlerFussSekunden).toBeCloseTo(
198 (SCHRANKE_FUSS_METER - 0.001) * JE_METER_FUSS,
199 6,
200 );
201 expect(genauigkeit.fehlerFussSekunden).toBeLessThan(MESSFEHLER_SCHRANKE);
202 expect(genauigkeit.eignung).toBe('voll');
203 });
204
205 it('erklaert einen sehr groben Ausschnitt zum Uebersichtsbild', () => {
206 // Hier haelt auch der Kfz-Fehler die Schranke nicht mehr. Aus einem solchen
207 // Bild darf ueberhaupt nicht vermessen werden.
208 const grob = messgenauigkeit(mitMassstab(1.0))!;
209
210 expect(grob.fehlerKfzSekunden).toBeGreaterThan(MESSFEHLER_SCHRANKE);
211 expect(grob.eignung).toBe('uebersicht');
212 });
213
214 it('stuft die Raender des Kfz-Bereichs richtig ein', () => {
215 // Grenzmassstab fuer Kfz: 2 * m * (1/7,0 + 1/11,1) = 0,25 s.
216 const grenze = MESSFEHLER_SCHRANKE / (ZEICHENFEHLER_BILDPUNKTE * (1 / 7.0 + 1 / 11.1));
217
218 expect(messgenauigkeit(mitMassstab(grenze - 0.001))!.eignung).toBe('nur-kfz');
219 expect(messgenauigkeit(mitMassstab(grenze + 0.001))!.eignung).toBe('uebersicht');
220 });
221
222 it('bildet die Ausschnittsgroessen des Anwendungsfalls ab', () => {
223 // 2000 m Kantenlaenge auf 7000 Bildpunkten sind 28,6 cm je Bildpunkt: ein
224 // Uebersichtsbild fuer die Gruene Welle - Kfz-Wege bleiben abgreifbar,
225 // Furten nicht.
226 const uebersicht = messgenauigkeit(mitMassstab(2000 / 7000))!;
227 expect(uebersicht.eignung).toBe('nur-kfz');
228 // 0,285714 m je Bildpunkt mal 2,180180 s/m = 0,622909 s an der Furt - das
229 // Zweieinhalbfache der hinnehmbaren 0,25 s.
230 expect(uebersicht.fehlerFussSekunden).toBeCloseTo((2000 / 7000) * JE_METER_FUSS, 6);
231 expect(uebersicht.fehlerFussSekunden).toBeGreaterThan(MESSFEHLER_SCHRANKE * 2);
232
233 // 100 m Kantenlaenge auf 2000 Bildpunkten sind 5 cm je Bildpunkt: der
234 // Knotenpunkt in nativer Aufloesung, auch die Furt ist abgreifbar.
235 expect(messgenauigkeit(mitMassstab(100 / 2000))!.eignung).toBe('voll');
236 });
237
238 it('folgt angepassten Kennwerten, statt sie ein zweites Mal zu fuehren', () => {
239 // Der Anwender darf die Raeumgeschwindigkeiten anpassen. Eine langsamer
240 // angesetzte Fussgaengergeschwindigkeit vergroessert den Zeitfehler und
241 // verschaerft damit die Anforderung an das Bild. Waeren die Zahlen hier
242 // abgeschrieben, bliebe die Einstufung stehen und wuerde falsch.
243 //
244 // Veraendert wird clearingSpeed.fussReducedMobility (0,6 m/s ist ein
245 // zulaessiger Wert): Das ist der Kennwert, der in die Einstufung eingeht.
246 // Frueher stand hier clearingSpeed.fuss - seit der Fussgaengerfehler den
247 // Bemessungsfall ansetzt, wird der gar nicht mehr gelesen, und der Fall
248 // pruefte damit nichts mehr. Die Zusage bleibt dieselbe.
249 //
250 // Auf der Schrankenunterlage: 2 * 0,114669 * (1/0,6 + 1/11,1) = 0,402893 s
251 // statt 0,25 s - die volle Eignung faellt auf nur-kfz.
252 const langsamer: RilsaDefaults = {
253 ...RILSA_DEFAULTS,
254 clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, fussReducedMobility: 0.6 },
255 };
256 const plan = mitMassstab(SCHRANKE_FUSS_METER);
257
258 expect(messgenauigkeit(plan)!.eignung).toBe('voll');
259 expect(messgenauigkeit(plan, langsamer)!.eignung).toBe('nur-kfz');
260 expect(messgenauigkeit(plan, langsamer)!.fehlerFussSekunden).toBeCloseTo(
261 SCHRANKE_FUSS_METER * ZEICHENFEHLER_BILDPUNKTE * (1 / 0.6 + 1 / V_EINFAHR_KFZ),
262 6,
263 );
264 expect(messgenauigkeit(plan, langsamer)!.fehlerFussSekunden).toBeGreaterThan(
265 messgenauigkeit(plan)!.fehlerFussSekunden,
266 );
267 });
268
269 it('stuft bei einer Geschwindigkeit von 0 zur sicheren Seite ein', () => {
270 // Ein solcher Kennwertsatz kommt aus der Vorgabenpruefung nicht heraus.
271 // Falls doch: kein Absturz und keine geschoenten 0 s, sondern die
272 // schlechteste Einstufung.
273 //
274 // Auf 0 zu setzen sind die Kennwerte, die tatsaechlich gelesen werden -
275 // fussReducedMobility und kfzTurning. Ueber clearingSpeed.fuss ist der
276 // entartete Fall seit der neuen Paarung nicht mehr erreichbar.
277 const entartet: RilsaDefaults = {
278 ...RILSA_DEFAULTS,
279 clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, fussReducedMobility: 0, kfzTurning: 0 },
280 };
281 const genauigkeit = messgenauigkeit(mitMassstab(0.05), entartet)!;
282
283 expect(genauigkeit.eignung).toBe('uebersicht');
284 expect(Number.isFinite(genauigkeit.fehlerFussSekunden)).toBe(false);
285 });
286 });
287
288 describe('Messgenauigkeit in der Planungsgrundlage', () => {
289 it('wird mit dem Nachweis zusammen gefuehrt', () => {
290 const grundlage = planungsgrundlage(mitMassstab(0.05));
291
292 expect(grundlage.messgenauigkeit).not.toBeNull();
293 expect(grundlage.messgenauigkeit!.eignung).toBe('voll');
294 // Dieselbe Zahl wie im uebrigen Nachweis - zwei Massstaebe im selben
295 // Ausdruck waeren nicht nachrechenbar.
296 expect(grundlage.messgenauigkeit!.meterJeBildpunkt).toBe(grundlage.meterJeBildpunkt);
297 });
298
299 it('bleibt ohne Massstab leer, statt eine Eignung zu behaupten', () => {
300 expect(planungsgrundlage(LEERER_LAGEPLAN).messgenauigkeit).toBeNull();
301 });
302
303 it('reicht angepasste Kennwerte durch', () => {
304 // Wie oben: Massgebend ist die Raeumgeschwindigkeit des
305 // mobilitaetseingeschraenkten Fussgaengers. Auf der Schrankenunterlage
306 // wird aus 0,25 s ein Fehler von 0,402893 s - und aus "voll" wird
307 // "nur-kfz", auch auf dem Weg ueber die Planungsgrundlage.
308 const langsamer: RilsaDefaults = {
309 ...RILSA_DEFAULTS,
310 clearingSpeed: { ...RILSA_DEFAULTS.clearingSpeed, fussReducedMobility: 0.6 },
311 };
312 const plan = mitMassstab(SCHRANKE_FUSS_METER);
313
314 expect(planungsgrundlage(plan).messgenauigkeit!.eignung).toBe('voll');
315 expect(planungsgrundlage(plan, langsamer).messgenauigkeit!.eignung).toBe('nur-kfz');
316 });
317 });