lsa-planer

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

/ tests domain planblatt.test.ts

6,6 KB Rohdatei
tests/domain/planblatt.test.ts — 181 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { blattstreckeInMeter, planblatt, planblattMoeglich } from '@/domain/geometrie/planblatt';
3 import { massstabsbalken } from '@/domain/geometrie/vermessung';
4 import type { Lageplan } from '@/domain/geometrie/lageplan';
5
6 /**
7 * Fassung 5.32.0 - das massstaebliche Lageplanblatt.
8 *
9 * DIE ZUSAGE, AN DER ALLES HAENGT: Was auf dem Blatt steht, muss sich abgreifen
10 * lassen. Ein Blatt, dessen Massstab nicht stimmt, ist schlimmer als keines -
11 * wer daraus einen zu kurzen Raeumweg abliest, bekommt eine zu kurze
12 * Zwischenzeit, und das ist die gefaehrliche Richtung.
13 */
14
15 /** Ein Lageplan mit bekanntem Massstab: 100 Bildpunkte sind 20 m. */
16 function lageplan(mitLinien = true): Lageplan {
17 return {
18 arbeitsbereiche: [],
19 signalgeber: [],
20 bild: null,
21 kalibrierung: {
22 von: { x: 0, y: 0 },
23 bis: { x: 100, y: 0 },
24 laengeMeter: 20,
25 herkunft: 'gemessen',
26 },
27 haltlinien: mitLinien
28 ? [
29 {
30 id: 'h1',
31 name: 'Nord',
32 art: 'zufahrt',
33 armId: null,
34 von: { x: 0, y: 0 },
35 bis: { x: 0, y: 100 },
36 },
37 ]
38 : [],
39 linien: mitLinien
40 ? [
41 {
42 id: 'l1',
43 name: 'K1',
44 mode: 'kfz',
45 movement: 'geradeaus',
46 breiteMeter: 3,
47 punkte: [
48 { x: 0, y: 50 },
49 { x: 200, y: 50 },
50 ],
51 haltlinieId: 'h1',
52 startT: 0.5,
53 signalGroupId: null,
54 },
55 ]
56 : [],
57 };
58 }
59
60 describe('Planblatt - der Massstab stimmt', () => {
61 it('bildet eine gezeichnete Strecke maßstäblich ab', () => {
62 /*
63 * DIE KERNZUSAGE. Die Fahrlinie ist 200 Bildpunkte lang, und 100
64 * Bildpunkte sind 20 m - also 40 m. Was immer das Blatt aus ihr macht:
65 * Zurueckgerechnet mit `meterJeBlattpunkt` müssen wieder 40 m
66 * herauskommen.
67 */
68 const blatt = planblatt(lageplan(), 400, 300);
69 expect(blatt, 'ein Blatt entsteht').not.toBeNull();
70 const linie = blatt!.linien[0]!;
71 expect(blattstreckeInMeter(blatt!, linie.punkte[0]!, linie.punkte[1]!)).toBeCloseTo(40, 2);
72 });
73
74 it('nimmt in beiden Richtungen denselben Faktor', () => {
75 /*
76 * Ein Blatt mit zwei Massstaeben ist keines: Ein Raeumweg quer zur
77 * Fahrtrichtung liesse sich daraus nicht abgreifen.
78 *
79 * GEPRUEFT AN BEIDEN BLATTFORMEN, und das ist noetig: Die Zeichnung ist
80 * 200 auf 100 Bildpunkte gross. Auf einem breiten Blatt (600 x 200)
81 * begrenzt die Hoehe, auf einem hohen (400 x 300) die Breite. Nur wo die
82 * beiden Kandidaten AUSEINANDERGEHEN, faellt eine getrennte Stauchung von
83 * x und y auf - mit nur einer Blattform bliebe sie unbemerkt.
84 */
85 for (const [breite, hoehe] of [
86 [600, 200],
87 [400, 300],
88 ] as const) {
89 const blatt = planblatt(lageplan(), breite, hoehe)!;
90 const halt = blatt.haltlinien[0]!;
91 const linie = blatt.linien[0]!;
92 const form = `${String(breite)}x${String(hoehe)}`;
93 // Haltlinie: 100 Bildpunkte senkrecht = 20 m. Fahrlinie: 200 waagerecht = 40 m.
94 expect(blattstreckeInMeter(blatt, halt.von, halt.bis), form).toBeCloseTo(20, 2);
95 expect(blattstreckeInMeter(blatt, linie.punkte[0]!, linie.punkte[1]!), form).toBeCloseTo(
96 40,
97 2,
98 );
99 }
100 });
101
102 it('hält die Zeichnung innerhalb des Blattes', () => {
103 const blatt = planblatt(lageplan(), 400, 300)!;
104 for (const linie of blatt.linien) {
105 for (const p of linie.punkte) {
106 expect(p.x).toBeGreaterThanOrEqual(0);
107 expect(p.x).toBeLessThanOrEqual(400);
108 expect(p.y).toBeGreaterThanOrEqual(0);
109 expect(p.y).toBeLessThanOrEqual(300);
110 }
111 }
112 });
113
114 it('liefert kein Blatt ohne Maßstab oder ohne Zeichnung', () => {
115 const ohneMassstab: Lageplan = { ...lageplan(), kalibrierung: null };
116 expect(planblatt(ohneMassstab, 400, 300)).toBeNull();
117 expect(planblattMoeglich(ohneMassstab)).toBe(false);
118 expect(planblatt(lageplan(false), 400, 300)).toBeNull();
119 expect(planblattMoeglich(lageplan(false))).toBe(false);
120 expect(planblattMoeglich(lageplan())).toBe(true);
121 // Und kein Blatt, das kleiner ist als sein eigener Rand.
122 expect(planblatt(lageplan(), 20, 300)).toBeNull();
123 });
124 });
125
126 describe('Planblatt - der Balken ist die maßgebende Angabe', () => {
127 it('wählt eine runde Meterzahl', () => {
128 const blatt = planblatt(lageplan(), 400, 300)!;
129 expect([1, 2, 5, 10, 20, 50, 100, 200, 500, 1000]).toContain(blatt.balken.meter);
130 });
131
132 it('ist so lang, wie er sagt', () => {
133 /*
134 * Der Balken behauptet eine Länge in Metern. Nimmt man seine Länge in
135 * Blattpunkten und rechnet sie mit dem Blattmaßstab zurück, muss genau
136 * diese Zahl herauskommen - sonst ist er eine Lüge auf dem Papier.
137 */
138 const blatt = planblatt(lageplan(), 400, 300)!;
139 expect(blatt.balken.laenge * blatt.meterJeBlattpunkt).toBeCloseTo(blatt.balken.meter, 6);
140 });
141
142 it('nimmt dieselbe Stufenwahl wie der Bildschirm', () => {
143 // Eine zweite Fassung in der Leinwand liefe auseinander; die Funktion
144 // steht deshalb im Fachkern und wird von beiden Seiten gerufen.
145 const blatt = planblatt(lageplan(), 400, 300)!;
146 const geteilt = massstabsbalken(blatt.meterJeBlattpunkt, (400 - 2 * 18) / 3);
147 expect(blatt.balken).toEqual(geteilt);
148 });
149
150 it('nennt einen runden Blattmaßstab und rundet ihn nach oben', () => {
151 /*
152 * "1:250" kann ein Prüfer mit dem Maßstabsdreieck nachmessen, "1:247"
153 * nicht. Aufgerundet wird, weil der genannte Maßstab dann kleiner ist als
154 * der wirkliche: Wer daraus abgreift, bekommt eher zu wenig als zu viel.
155 */
156 const blatt = planblatt(lageplan(), 400, 300)!;
157 expect(blatt.massstabsnenner).not.toBeNull();
158 const wirklich = (blatt.meterJeBlattpunkt * 72 * 1000) / 25.4;
159 expect(blatt.massstabsnenner!).toBeGreaterThanOrEqual(wirklich);
160 expect([50, 100, 200, 250, 500, 1000, 2000, 2500, 5000, 10000]).toContain(
161 blatt.massstabsnenner,
162 );
163 });
164 });
165
166 describe('Massstabsbalken - die geteilte Stufenwahl', () => {
167 it('bleibt bei unbrauchbaren Angaben stumm', () => {
168 for (const wert of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) {
169 expect(massstabsbalken(wert, 120), String(wert)).toEqual({ meter: 0, laenge: 0 });
170 }
171 });
172
173 it('trifft die nächstgrößere runde Stufe', () => {
174 // 120 Punkte * 0,1 m = 12 m roh -> die Stufe 20.
175 expect(massstabsbalken(0.1, 120).meter).toBe(20);
176 // 120 * 0,04 = 4,8 m -> 5.
177 expect(massstabsbalken(0.04, 120).meter).toBe(5);
178 // Und die Länge gehört zur Zahl.
179 expect(massstabsbalken(0.04, 120).laenge).toBeCloseTo(5 / 0.04, 6);
180 });
181 });