import type { Meters, Seconds } from '../units'; import { roundTo } from '../units'; import { RILSA_DEFAULTS, type RilsaDefaults } from '../rilsa/constants'; import type { Movement, TrafficMode } from '../rilsa/types'; import { REGELBREITE, abstand, linieInMeter, massstabAusStrecke, type Fahrlinie, type Massstab, type Punkt, } from './vermessung'; /** * Lageplan als Grundlage der Vermessung. * * Gedanke dahinter: Raeum- und Einfahrweg sind heute die einzige Groesse, die * der Anwender ausserhalb des Programms ermitteln und abtippen muss. Zeichnet * er stattdessen die Fahrlinien in einen massstaeblichen Plan, lassen sich * beide Wege - und darueber hinaus die Vertraeglichkeit - unmittelbar ableiten. * * KOORDINATEN * * Alle gezeichneten Punkte werden in BILDPUNKTEN des Hintergrundbilds * gespeichert, nicht in Metern. Grund: Der Anwender zeichnet auf dem Bild, und * die Zeichnung muss deckungsgleich bleiben, auch wenn der Massstab spaeter * korrigiert wird. Die Umrechnung in Meter geschieht bei jeder Auswertung neu. */ /** Hintergrundbild des Lageplans. */ export interface LageplanBild { /** Bilddaten als Daten-URL - das Projekt bleibt dadurch in sich geschlossen. */ readonly datenUrl: string; readonly breite: number; readonly hoehe: number; /** Woher das Bild stammt: Dateiname oder Bezeichnung des Kartendienstes. */ readonly herkunft: string; readonly geladenAm: string; } /** * Festlegung des Massstabs. * * Bei einem eingelesenen Bild zieht der Anwender eine Strecke bekannter Laenge. * Bei einem georeferenzierten Luftbild ist der Massstab bekannt und wird * unmittelbar uebernommen. */ export interface Kalibrierung { readonly von: Punkt; readonly bis: Punkt; readonly laengeMeter: Meters; readonly herkunft: 'gemessen' | 'georeferenziert'; } /** Art einer gezeichneten Halt- oder Aufstelllinie. */ export type HaltlinienArt = 'zufahrt' | 'querung'; /** * Halt- oder Aufstelllinie: der Querschnitt, an dem Stroeme beginnen. * * WARUM EIN EIGENES OBJEKT * * Aus einer Zufahrt fahren mehrere Stroeme ab - geradeaus, links, rechts. Sie * werden als je eine Fahrlinie gezeichnet und beginnen alle an derselben * Haltlinie. Ob zwei Stroeme am selben Querschnitt beginnen, entscheidet * darueber, ob sie feindlich sind: Stroeme derselben Zufahrt muessen * gleichzeitig Freigabe erhalten koennen. * * Waere die Haltlinie je Strom neu gezeichnet, liesse sich diese Frage nur * ueber Abstaende beantworten - und ein Millimeter entschiede dann ueber eine * sicherheitsrelevante Angabe. Als eigenes Objekt wird daraus ein Vergleich * von Kennungen. * * MEHRERE Haltlinien je Arm sind ausdruecklich zugelassen: versetzte * Haltlinien, aufgeweiteter Radaufstellstreifen, vorgelagerter Teilknoten. * * `art` trennt zwei fachlich verschiedene Faelle: * 'zufahrt' einfahrender Fahrverkehr; `armId` ist der Quellarm. * 'querung' Furt oder Radquerung; `armId` ist der GEQUERTE Arm. Solche * Stroeme gehoeren zum Arm, aber nicht zu dessen Zufahrt - eine * Furt ist gegenueber den Stroemen ihres eigenen Arms feindlich. */ export interface Haltlinie { readonly id: string; readonly name: string; readonly art: HaltlinienArt; /** Zugehoeriger Knotenpunktarm, sofern zugeordnet. */ readonly armId: string | null; /** Endpunkte in Bildpunkten. */ readonly von: Punkt; readonly bis: Punkt; } /** Eine gezeichnete Fahrlinie im Lageplan. Sie beschreibt genau EINEN Strom. */ export interface Planlinie { readonly id: string; readonly name: string; readonly mode: TrafficMode; readonly movement: Movement; /** Breite des beanspruchten Korridors in Metern. */ readonly breiteMeter: Meters; /** * Punkte in Bildpunkten; der erste liegt auf der Haltlinie. * * Der Linienzug bleibt vollstaendig. Ist eine Haltlinie zugeordnet, wird der * erste Punkt aus ihr und `startT` abgeleitet und von `normalisiere` gerade * gehalten. Die Redundanz ist gewollt: Sie haelt die Linie zeichenbar, auch * wenn der Verweis einmal nicht aufloesbar ist. */ readonly punkte: readonly Punkt[]; /** * Haltlinie, an der der Strom beginnt. * * `null` heisst "nicht zugeordnet". Dann entscheidet allein die Zeichnung - * die sichere Seite, und der Zustand aller uebernommenen Projekte. */ readonly haltlinieId: string | null; /** Lage des Startpunkts auf der Haltlinie: 0 bei `von`, 1 bei `bis`. */ readonly startT: number; /** * Verknuepfte Signalgruppe. * * MEHRERE Linien duerfen auf dieselbe Gruppe zeigen. Eine Signalgruppe ist * die Menge gleich schaltender Signalgeber, kein Verkehrsstrom - ein * Signalgeber bedient haeufig geradeaus und rechts zugleich. */ readonly signalGroupId: string | null; } /** * Ein Arbeitsbereich: die Arbeitsstelle als Band laengs der Fahrbahn. * * WOFUER (gewuenscht am 11.09.2026 aus dem Betrieb): An einer einstreifigen * Verkehrsfuehrung steht die Arbeitsstelle zuerst fest; Haltlinien, * Aufstellung und Signalgeber richten sich nach ihr. Wer sie zeichnen kann, * setzt die Haltlinien danach - und der Pruefer sieht auf dem Lageplanblatt, * warum sie dort liegen. * * ER IST KEIN VERKEHRSSTROM, und das ist der Grund fuer ein eigenes Objekt * statt eines Merkmals an `Planlinie`. Eine Planlinie traegt Verkehrsart, * Fahrbeziehung und Signalgruppe und geht ueber `fahrlinienVon` in die * Vermessung; ein Band, das dort mitliefe, erzeugte Kreuzungspunkte, * Konfliktbereiche und am Ende Zwischenzeiten gegen eine Baustelle. Der * Arbeitsbereich traegt deshalb keines dieser drei Felder und erscheint in * `fahrlinienVon` nie. * * ER RECHNET NICHT MIT. Weder Zwischenzeit noch Freigabezeit noch Umlaufzeit * kennen ihn. Abgeleitet werden aus ihm zwei Auskuenfte - seine Laenge und der * Abstand jeder Haltlinie zu seinem naechsten Ende (siehe * `geometrie/arbeitsbereich.ts`); beide werden angezeigt und gedruckt, nicht * uebernommen. */ export interface Arbeitsbereich { readonly id: string; readonly name: string; /** Breite des Bandes in Metern. */ readonly breiteMeter: Meters; /** Punkte in Bildpunkten; ab zwei Punkten ergibt sich ein Band. */ readonly punkte: readonly Punkt[]; } /** * Ein Signalgeber: Standort und Blickrichtung eines Ampelkopfs. * * WOFUER (Stufe 3 des Signallageplan-Entwurfs vom 12.09.2026): Zur * verkehrstechnischen Unterlage gehoert ein bemasster Signallageplan, und * dieses Programm wies ihn bisher als fehlend aus. Ihm fehlte genau ein * Gegenstand - der Signalgeber mit Ort und Ausrichtung; Haltlinien, * Fahrlinien, Massstab und Blatt gab es laengst. * * ER RECHNET NICHT MIT, und das ist keine vorlaeufige Einschraenkung, sondern * die Bedingung, unter der es ihn gibt. Wie der Arbeitsbereich traegt er * weder `mode` noch `movement`, erscheint in `fahrlinienVon` nie und erzeugt * keine Konfliktbeziehung. Der Grund ist schaerfer als dort: Aus einem * Signalgeberstandort LIESSE sich rechnen - der Abstand zur Haltlinie, eine * Sichtweite, ein Raeumweganfang -, und jede dieser Groessen beruehrt die * Zwischenzeit. Was Zwischenzeiten verkuerzt, ist die gefaehrliche Richtung. * Solange die Regelwerke dazu nicht im Original vorliegen, wird hier * gezeichnet und nicht gerechnet. * * DIE KENNUNG TRAEGT DER ANWENDER EIN. Ein Schema K1, K2, K3 nach Anlagenform * waere bequem und an einer Engstelle mit zwei gleichwertigen Zulaeufen * falsch: Welcher Zulauf K1 wird, entschiede der Feldindex, und ein * Gleichstand darf nicht die Feldreihenfolge entscheiden. Vorbelegt wird aus * dem Namen der verknuepften Signalgruppe, geaendert wird von Hand. */ export interface Signalgeber { readonly id: string; /** Kennung auf dem Blatt, z. B. "K1" - vom Anwender vergeben. */ readonly name: string; /** Standort in Bildpunkten. */ readonly punkt: Punkt; /** * Blickrichtung in Grad, 0 = nach rechts, im Uhrzeigersinn wachsend. * * DIESELBE ZAEHLUNG WIE IN DEN BILDPUNKTEN, in denen y nach unten waechst - * ein Pfeil mit 90 Grad zeigt auf dem Bildschirm nach unten. Eine zweite * Zaehlung fuer die Anzeige waere eine Umrechnung, die auseinanderlaufen * kann; wer die Richtung lesen will, liest sie am gezeichneten Pfeil. */ readonly ausrichtung: number; /** * Verknuepfte Signalgruppe, oder null. * * MEHRERE Signalgeber duerfen auf dieselbe Gruppe zeigen - eine Signalgruppe * IST die Menge gleich schaltender Geber. Die Verknuepfung dient der * Beschriftung und der Pruefung, nicht der Rechnung. */ readonly signalGroupId: string | null; } export interface Lageplan { readonly bild: LageplanBild | null; readonly kalibrierung: Kalibrierung | null; readonly haltlinien: readonly Haltlinie[]; readonly linien: readonly Planlinie[]; /** * Gezeichnete Arbeitsstellen. * * Pflichtfeld wie die vier darueber: Ein wahlfreies Feld waere an jeder der * 37 Stellen, die einen Lageplan bilden, stillschweigend leer - und an einer * davon waere es dann ein Versehen statt einer Aussage. */ readonly arbeitsbereiche: readonly Arbeitsbereich[]; /** Gezeichnete Signalgeberstandorte. Pflichtfeld aus demselben Grund. */ readonly signalgeber: readonly Signalgeber[]; } export const LEERER_LAGEPLAN: Lageplan = { bild: null, kalibrierung: null, haltlinien: [], linien: [], arbeitsbereiche: [], signalgeber: [], }; /** Punkt auf einer Haltlinie; `t` wird auf 0..1 begrenzt. */ export function punktAufHaltlinie(h: Haltlinie, t: number): Punkt { const g = Number.isFinite(t) ? Math.min(1, Math.max(0, t)) : 0.5; return { x: h.von.x + (h.bis.x - h.von.x) * g, y: h.von.y + (h.bis.y - h.von.y) * g, }; } /** * Lage eines Punktes auf einer Haltlinie, als `t` zwischen 0 und 1. * * Gebraucht beim nachtraeglichen Zuordnen: Wird einer bereits gezeichneten * Fahrlinie eine Haltlinie zugewiesen, muss ihr Anfangspunkt auf diese Linie * wandern. Genommen wird der Lotfusspunkt - die naechstgelegene Stelle, also * die, die der Anwender gemeint hat. */ export function lotAuf(h: Haltlinie, punkt: Punkt | undefined): number { if (punkt === undefined) return 0.5; const dx = h.bis.x - h.von.x; const dy = h.bis.y - h.von.y; const laengeQuadrat = dx * dx + dy * dy; // Entartete Haltlinie: Es gibt keine Richtung, also auch keine Lage darauf. if (laengeQuadrat < 1e-9) return 0.5; const t = ((punkt.x - h.von.x) * dx + (punkt.y - h.von.y) * dy) / laengeQuadrat; return Math.min(1, Math.max(0, t)); } /** Haltlinie einer Fahrlinie, sofern zugeordnet und vorhanden. */ export function haltlinieVon(lageplan: Lageplan, linie: Planlinie): Haltlinie | null { if (linie.haltlinieId === null) return null; return lageplan.haltlinien.find((h) => h.id === linie.haltlinieId) ?? null; } /** Stroeme einer Haltlinie, nach `startT` sortiert - ihre Reihenfolge im Querschnitt. */ export function stroemeAn(lageplan: Lageplan, haltlinieId: string): Planlinie[] { return lageplan.linien .filter((l) => l.haltlinieId === haltlinieId) .sort((a, b) => a.startT - b.startT); } /** Alle Linien, die von dieser Signalgruppe geschaltet werden. */ export function linienDerGruppe(lageplan: Lageplan, gruppeId: string): Planlinie[] { return lageplan.linien.filter((l) => l.signalGroupId === gruppeId); } /** * Haelt die abgeleiteten Groessen gerade. * * Der erste Punkt einer Fahrlinie folgt aus ihrer Haltlinie und `startT`. Wird * die Haltlinie verschoben, wandern alle ihre Stroeme mit - das ist der * eigentliche Gewinn des eigenen Objekts: Die Haltlinie einer Zufahrt wird * einmal gesetzt und einmal korrigiert, nicht je Strom. * * Ueberschrieben wird dabei keine Anwenderangabe: `punkte[0]` ist bei * zugeordneter Haltlinie eine Rechengroesse. Verweise ins Leere bleiben * unangetastet - sie werden beim Einlesen gemeldet und geloest, nicht hier * stillschweigend. */ export function normalisiere(lageplan: Lageplan): Lageplan { if (lageplan.haltlinien.length === 0) return lageplan; return { ...lageplan, linien: lageplan.linien.map((linie) => { const h = haltlinieVon(lageplan, linie); if (h === null || linie.punkte.length === 0) return linie; return { ...linie, punkte: [punktAufHaltlinie(h, linie.startT), ...linie.punkte.slice(1)] }; }), }; } /** Regelbreite je Verkehrsart - Vorbelegung beim Zeichnen. */ export function regelbreite(mode: TrafficMode): Meters { switch (mode) { case 'kfz': return REGELBREITE.kfz; case 'rad': return REGELBREITE.rad; case 'fuss': return REGELBREITE.fuss; case 'oepnv': return REGELBREITE.oepnv; } } /** Ist der Lageplan auswertbar? */ export function istAuswertbar(lageplan: Lageplan): boolean { return massstabVon(lageplan) !== null && lageplan.linien.some((l) => l.punkte.length >= 2); } /** Massstab aus der Kalibrierung. */ export function massstabVon(lageplan: Lageplan): Massstab | null { const k = lageplan.kalibrierung; if (k === null) return null; return massstabAusStrecke(k.von, k.bis, k.laengeMeter); } /** * Nachweis der Planungsgrundlage. * * Fuer die Pruefung der Unterlagen ist entscheidend, worauf die Raeum- und * Einfahrwege beruhen: Dieselbe Zahl kann aus einem massstaeblichen Luftbild * stammen oder von Hand geschaetzt sein. Ohne diesen Nachweis liess sich eine * Zwischenzeit im Ausdruck nicht nachrechnen. Gebuendelt wird das hier und * nicht in der Ausgabe - die Ausgabe rechnet nicht. */ export interface Planungsgrundlage { /** Liegt ueberhaupt eine Bildunterlage vor? */ readonly hatBild: boolean; /** Herausgeber bzw. Dateiname; leer, wenn kein Bild vorliegt. */ readonly herkunft: string; /** ISO-Zeitstempel des Ladens; leer, wenn kein Bild vorliegt. */ readonly geladenAm: string; /** Fehlt das Ladedatum, stuende in der Ausgabe der 1.1.1970 - das ist kein Datum. */ readonly ladedatumBekannt: boolean; readonly breiteBildpunkte: number; readonly hoeheBildpunkte: number; readonly massstabHerkunft: 'gemessen' | 'georeferenziert' | null; /** Meter je Bildpunkt; null, solange kein Massstab festgelegt ist. */ readonly meterJeBildpunkt: number | null; /** Laenge der Bezugsstrecke in Metern; null ohne Kalibrierung. */ readonly bezugslaengeMeter: Meters | null; /** Dieselbe Strecke in Bildpunkten - daraus folgt der Massstab. */ readonly bezugslaengeBildpunkte: number | null; /** Kantenlaenge des Ausschnitts am Boden: Bildbreite mal Massstab. */ readonly kantenlaengeMeter: Meters | null; readonly anzahlHaltlinien: number; readonly anzahlFahrlinien: number; /** Sind daraus ueberhaupt Wege ableitbar? */ readonly auswertbar: boolean; /** Wofuer die Aufloesung ausreicht; null, solange kein Massstab feststeht. */ readonly messgenauigkeit: Messgenauigkeit | null; } /** * Stellt die Angaben zur Planungsgrundlage fuer Anzeige und Ausdruck zusammen. * * `defaults` wird bis zur Messgenauigkeit durchgereicht: Der Anwender darf die * Raeum- und Einfahrgeschwindigkeiten anpassen, und mit ihnen verschiebt sich, * welche Aufloesung noch ausreicht. Wer die Vorgaben des Projekts hat, gibt sie * mit; sonst gilt das Regelwerk. */ export function planungsgrundlage( lageplan: Lageplan, defaults: RilsaDefaults = RILSA_DEFAULTS, ): Planungsgrundlage { const bild = lageplan.bild; const kalibrierung = lageplan.kalibrierung; const massstab = massstabVon(lageplan); return { hatBild: bild !== null, herkunft: bild?.herkunft ?? '', geladenAm: bild?.geladenAm ?? '', ladedatumBekannt: bild !== null && Date.parse(bild.geladenAm) > 0, breiteBildpunkte: bild?.breite ?? 0, hoeheBildpunkte: bild?.hoehe ?? 0, massstabHerkunft: kalibrierung?.herkunft ?? null, meterJeBildpunkt: massstab?.meterJeBildpunkt ?? null, bezugslaengeMeter: kalibrierung?.laengeMeter ?? null, bezugslaengeBildpunkte: kalibrierung === null ? null : abstand(kalibrierung.von, kalibrierung.bis), kantenlaengeMeter: bild === null || massstab === null ? null : bild.breite * massstab.meterJeBildpunkt, anzahlHaltlinien: lageplan.haltlinien.length, anzahlFahrlinien: lageplan.linien.length, auswertbar: istAuswertbar(lageplan), messgenauigkeit: messgenauigkeit(lageplan, defaults), }; } // --- Messgenauigkeit -------------------------------------------------------- /** * Wofuer die Aufloesung der Unterlage ausreicht. * * 'voll' auch Fussgaengerfurten sind abgreifbar * 'nur-kfz' nur Kraftfahrzeugstroeme; eine Furt waere zu grob vermessen * 'uebersicht' zum Betrachten und Einordnen, nicht zum Vermessen */ export type Messeignung = 'voll' | 'nur-kfz' | 'uebersicht'; /** Was ein Zeichenfehler von zwei Bildpunkten in der Zwischenzeit anrichtet. */ export interface Messgenauigkeit { readonly meterJeBildpunkt: number; readonly eignung: Messeignung; /** Fehler der Zwischenzeit bei einer Fussgaengerfurt, in Sekunden. */ readonly fehlerFussSekunden: Seconds; /** Fehler der Zwischenzeit bei einem abbiegenden Kraftfahrzeugstrom, in Sekunden. */ readonly fehlerKfzSekunden: Seconds; } /** * Zeichenfehler, der der Beurteilung zugrunde liegt - in Bildpunkten. * * Zwei Bildpunkte sind das, was beim Zeichnen zusammenkommt: einer fuer das * Treffen der Kante, einer fuer das Erkennen, wo die Kante im Bild ueberhaupt * liegt. Weiter zu unterstellen waere schoengerechnet. * * Die Annahme gilt fuer beide Wege, auf denen gezeichnet wird. Der feinste * Schritt des Tastaturzeigers ist ausdruecklich auf einen Bildpunkt der * Unterlage begrenzt (siehe zeigerSchritt() in src/ui/lageplan/leinwand.ts) und * bleibt damit innerhalb dessen, was hier unterstellt ist - die Einstufung im * Ausdruck gilt also fuer Maus wie Tastatur. */ export const ZEICHENFEHLER_BILDPUNKTE = 2; /** * Groesster hinnehmbarer Fehler der Zwischenzeit aus dem Zeichnen - in Sekunden. * * Ein Viertel der Rundungsstufe: Die Zwischenzeit wird auf ganze Sekunden * AUFGERUNDET. Bleibt der Zeichenfehler darunter, kann er das ausgegebene * Ergebnis nur dann um eine Sekunde kippen, wenn der rechnerische Wert ohnehin * dicht an einer Stufengrenze liegt. Wird die Schranke ueberschritten, verliert * das Aufrunden seine Sicherheitswirkung - der Fehler frisst sie auf. */ export const MESSFEHLER_SCHRANKE: Seconds = 0.25; /** * Was ein Zeichenfehler von zwei Bildpunkten in der Zwischenzeit kostet. * * WARUM DAS EINE SICHERHEITSGROESSE IST * * Aus dem Lageplan werden Raeum- und Einfahrweg VERMESSEN, und beide gehen * unmittelbar in die Zwischenzeit ein: * * tz = tue + (sr + Fahrzeuglaenge) / vr - se / ve * * Die Uebertragung ist linear und damit vollstaendig ausrechenbar: Ein Fehler * von d Metern auf dem Raeumweg wirkt mit d/vr Sekunden, einer auf dem * Einfahrweg mit d/ve - und zwar mit umgekehrtem Vorzeichen, weshalb sich die * beiden im unguenstigen Fall addieren statt aufzuheben. Beide Wege werden * getrennt gezeichnet; der Zeichenfehler faellt deshalb zweimal an. * * Entscheidend ist, dass die Geschwindigkeit im Nenner steht. Derselbe * Zeichenfehler kostet bei einem Fussgaenger fast das Zehnfache dessen, was er * bei einem Kraftfahrzeug kostet. Ein Ausschnitt, auf dem sich Fahrspuren noch * bequem abgreifen lassen, ist fuer eine Fussgaengerfurt laengst zu grob - und * ein Luftbild sieht in beiden Faellen gleich aus. Genau davor warnt diese * Groesse: Der eintretende Schaden ist eine zu KURZE Zwischenzeit an einer * Furt, also die unsichere Richtung. * * Die Geschwindigkeiten stammen aus den Kennwerten und werden hier bewusst * nicht wiederholt: Der Anwender darf sie anpassen, und eine zweite Fassung * liefe still auseinander. * * Ohne Massstab gibt es keine Genauigkeit - dann null statt einer erfundenen * Zahl. */ export function messgenauigkeit( lageplan: Lageplan, defaults: RilsaDefaults = RILSA_DEFAULTS, ): Messgenauigkeit | null { const massstab = massstabVon(lageplan); if (massstab === null) return null; const g = ZEICHENFEHLER_BILDPUNKTE * massstab.meterJeBildpunkt; /* * WER RAEUMT UND WER FAEHRT EIN - die Paarung entscheidet ueber die Zahl. * * Zuvor stand hier die Einfahrgeschwindigkeit der Fussgaenger. Das beschreibt * einen Konflikt, in dem ein Fussgaenger raeumt und ein FUSSGAENGER einfaehrt * - eine Beziehung, die als signalisierter Konflikt praktisch nicht vorkommt. * Die Zahl war dadurch um zwei Drittel zu gross: 1/1,2 + 1/1,5 = 1,50 statt * 1/1,0 + 1/11,1 = 1,09. Kein einziges amtliches Luftbild erreichte damit die * Stufe "voll" - auch das beste Angebot mit 10 cm je Bildpunkt nicht. Eine * Warnung, die immer steht, wird nicht mehr gelesen. * * Der gefaehrliche Konflikt ist der umgekehrte und haeufige: Ein Fussgaenger * raeumt die Furt, waehrend ein Kraftfahrzeug einfaehrt. Deshalb die * Raeumgeschwindigkeit der Fussgaenger gegen die Einfahrgeschwindigkeit der * Kraftfahrzeuge - dieselbe Paarung, die der Kfz-Wert unten schon benutzt. * * Angesetzt wird dabei der mobilitaetseingeschraenkte Fussgaenger mit 1,0 m/s. * Er ist der Bemessungsfall, wo solche Stroeme vorkommen, und er ist die * sichere Seite: Wer langsamer raeumt, bezahlt denselben Wegfehler mit mehr * Sekunden. Der Unterschied zum Regelfall betraegt 11 Prozent und verschiebt * keine der Stufen fuer die Dienste, die es tatsaechlich gibt. */ const fehlerFussSekunden = zeitfehler( g, defaults.clearingSpeed.fussReducedMobility, defaults.enteringSpeed.kfz, ); // Abbiegende Kraftfahrzeuge raeumen langsamer als geradeausfahrende; von den // Kfz-Werten ist das der unguenstigere und damit der massgebende. const fehlerKfzSekunden = zeitfehler( g, defaults.clearingSpeed.kfzTurning, defaults.enteringSpeed.kfz, ); return { meterJeBildpunkt: massstab.meterJeBildpunkt, eignung: haeltSchranke(fehlerFussSekunden) ? 'voll' : haeltSchranke(fehlerKfzSekunden) ? 'nur-kfz' : 'uebersicht', fehlerFussSekunden, fehlerKfzSekunden, }; } /** * Zeitfehler aus einem Wegfehler `g`, der auf Raeum- UND Einfahrweg anfaellt. * * Gerundet wird auf sechs Stellen, damit angezeigter Wert und Einstufung an * genau derselben Zahl haengen. Ohne das ergaebe die Rechnung an der Schranke * 0,24999999999999997 s: angezeigt stuende dort "0,25 s", eingestuft waere aber * die schlechtere Stufe - ein Widerspruch, den niemand aufloesen koennte. * * Eine Geschwindigkeit von 0 ergibt einen unendlichen Fehler und damit die * schlechteste Einstufung. Das ist die sichere Richtung; die Kennwertpruefung * laesst solche Werte ohnehin nicht durch. */ function zeitfehler( g: Meters, raeumgeschwindigkeit: number, einfahrgeschwindigkeit: number, ): Seconds { return roundTo(g * (1 / raeumgeschwindigkeit + 1 / einfahrgeschwindigkeit), 6); } /** Genau auf der Schranke gilt als eingehalten - sie ist eine Obergrenze. */ function haeltSchranke(fehler: Seconds): boolean { return fehler <= MESSFEHLER_SCHRANKE; } /** * Wandelt die gezeichneten Linien in Fahrlinien mit Metermassen um. * Linien mit weniger als zwei Punkten entfallen - sie beschreiben keinen Weg. * * Mitgegeben wird der Strombezug: Ohne ihn kann die Vermassung nicht * unterscheiden, ob eine Beruehrung kurz hinter dem Start eine Kreuzung ist * oder das Auffaechern zweier Stroeme aus derselben Zufahrt. */ export function fahrlinienVon(lageplan: Lageplan): Fahrlinie[] { const massstab = massstabVon(lageplan); if (massstab === null) return []; return lageplan.linien .filter((linie) => linie.punkte.length >= 2) .map((linie) => { const h = haltlinieVon(lageplan, linie); return linieInMeter( { id: linie.id, punkte: linie.punkte, breite: linie.breiteMeter, bezug: { haltlinieId: linie.haltlinieId, haltlinienArt: h?.art ?? null, mode: linie.mode, // Entscheidet mit, ob eine Radlinie als Querung in beiden Richtungen // vermessen wird (`querungsart` in vermessung.ts). movement: linie.movement, startT: linie.startT, }, }, massstab, ); }); } /** Sucht die gezeichnete Linie zu einer Fahrlinie. */ export function planlinieVon(lageplan: Lageplan, id: string): Planlinie | undefined { return lageplan.linien.find((l) => l.id === id); } /** Sucht einen Arbeitsbereich nach seiner Kennung. */ export function arbeitsbereichVon(lageplan: Lageplan, id: string): Arbeitsbereich | undefined { return lageplan.arbeitsbereiche.find((a) => a.id === id); }