lsa-planer

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

/ src domain units.ts

4,7 KB Rohdatei
src/domain/units.ts — 115 Zeilen
1 /**
2 * Einheiten und Umrechnungen.
3 *
4 * Im Altbestand wurden Geschwindigkeiten mal in km/h und mal in m/s in denselben
5 * Feldern gefuehrt; die Umrechnung /3.6 wurde teils doppelt, teils gar nicht
6 * angewendet. Deshalb sind hier alle Einheiten im Typnamen sichtbar und es gibt
7 * genau eine Stelle fuer jede Umrechnung.
8 *
9 * Rechnerisch gilt projektweit: intern wird ausschliesslich in SI-Einheiten
10 * gerechnet (Meter, Sekunden, Meter je Sekunde). km/h existiert nur an der
11 * Benutzerschnittstelle.
12 */
13
14 /** Zeit in Sekunden. */
15 export type Seconds = number;
16 /** Laenge in Metern. */
17 export type Meters = number;
18 /** Geschwindigkeit in Metern je Sekunde (interne Rechengroesse). */
19 export type MetersPerSecond = number;
20 /** Geschwindigkeit in Kilometern je Stunde (nur Ein-/Ausgabe). */
21 export type KilometersPerHour = number;
22 /** Verkehrsstaerke in Fahrzeugen je Stunde. */
23 export type VehiclesPerHour = number;
24 /** Dimensionsloser Anteil im Bereich 0..1. */
25 export type Ratio = number;
26
27 const SECONDS_PER_HOUR = 3600;
28 const METERS_PER_KILOMETER = 1000;
29 /** Exakter Umrechnungsfaktor km/h -> m/s (= 1000/3600). */
30 export const KMH_TO_MS = METERS_PER_KILOMETER / SECONDS_PER_HOUR;
31
32 /** Rechnet km/h in m/s um. */
33 export function kmhToMs(v: KilometersPerHour): MetersPerSecond {
34 return v * KMH_TO_MS;
35 }
36
37 /** Rechnet m/s in km/h um. */
38 export function msToKmh(v: MetersPerSecond): KilometersPerHour {
39 return v / KMH_TO_MS;
40 }
41
42 /**
43 * Rundet auf ganze Sekunden auf.
44 *
45 * Sicherheitsrelevante Zeiten (Zwischenzeit, Raeumzeit) duerfen nie abgerundet
46 * werden. Der Rundungsfehler-Ausgleich faengt Gleitkomma-Artefakte ab: ohne ihn
47 * wuerde z. B. 3.0000000000000004 auf 4 s aufgerundet.
48 *
49 * KEINE NEGATIVE NULL (Fassung 5.10.0): Math.ceil gibt fuer jeden Wert zwischen
50 * -1 und 0 die negative Null zurueck. Sie rechnet wie 0, druckt sich aber nicht
51 * wie 0 - Intl formatiert sie als "-0", und so stand sie in der
52 * Zwischenzeitenmatrix der Planunterlage, im Rechenweg des Konfliktfensters und
53 * in der CSV-Ausgabe. Abgestreift wird sie hier, an der einen Stelle, die
54 * aufrundet, statt an jeder Ausgabe einzeln. Die Nachbarzeile roundTo faengt
55 * daneben den Vorzeichenfall von Math.round ab, streift die negative Null aber
56 * NICHT ab: roundTo(-0,4; 0) ergibt -0. Wer mit 0 Nachkommastellen rundet, muss
57 * sie dort weiter selbst erwarten.
58 */
59 export function ceilSeconds(t: Seconds): Seconds {
60 const corrected = roundTo(t, 9);
61 const aufgerundet = Math.ceil(corrected);
62 return aufgerundet === 0 ? 0 : aufgerundet;
63 }
64
65 /** Kaufmaennisches Runden auf eine feste Anzahl Nachkommastellen. */
66 export function roundTo(value: number, decimals: number): number {
67 if (!Number.isFinite(value)) return value;
68 const factor = 10 ** decimals;
69 // Math.round(-0.5) ergibt -0; die Vorzeichenbehandlung haelt negative Werte stabil.
70 return Math.sign(value) * (Math.round(Math.abs(value) * factor) / factor);
71 }
72
73 /** Rundet auf das naechste Vielfache von `step` (kaufmaennisch). */
74 export function roundToStep(value: number, step: number): number {
75 if (step <= 0) throw new RangeError('roundToStep: step muss groesser als 0 sein');
76 return Math.round(value / step) * step;
77 }
78
79 /** Rundet auf das naechste Vielfache von `step` auf. */
80 export function ceilToStep(value: number, step: number): number {
81 if (step <= 0) throw new RangeError('ceilToStep: step muss groesser als 0 sein');
82 return Math.ceil(roundTo(value / step, 9)) * step;
83 }
84
85 /** Begrenzt einen Wert auf das Intervall [min, max]. */
86 export function clamp(value: number, min: number, max: number): number {
87 if (min > max) throw new RangeError('clamp: min darf nicht groesser als max sein');
88 return Math.min(max, Math.max(min, value));
89 }
90
91 /**
92 * Prueft auf eine endliche Zahl.
93 *
94 * Der Altbestand hat mit parseFloat('') === NaN weitergerechnet; NaN pflanzt
95 * sich durch die gesamte Berechnung fort und erscheint am Ende stumm als leeres
96 * Feld im Plan. Alle Eingaenge der Fachlogik werden deshalb hier geprueft.
97 */
98 export function isFiniteNumber(value: unknown): value is number {
99 return typeof value === 'number' && Number.isFinite(value);
100 }
101
102 /**
103 * Liest eine Zahl aus beliebiger Eingabe (Formularfeld, importiertes JSON).
104 * Akzeptiert deutsches Dezimalkomma. Gibt `undefined` zurueck, wenn kein
105 * gueltiger Zahlenwert vorliegt - nie stillschweigend 0.
106 */
107 export function parseNumber(input: unknown): number | undefined {
108 if (typeof input === 'number') return Number.isFinite(input) ? input : undefined;
109 if (typeof input !== 'string') return undefined;
110 const normalized = input.trim().replace(/\s/g, '').replace(',', '.');
111 if (normalized === '') return undefined;
112 // Number() ist strenger als parseFloat: '12abc' ergibt NaN statt 12.
113 const value = Number(normalized);
114 return Number.isFinite(value) ? value : undefined;
115 }