lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ src domain geometrie arbeitsbereich.ts
| 1 | import type { Meters } from '../units'; |
| 2 | import { roundTo } from '../units'; |
| 3 | import type { Arbeitsbereich, Haltlinie, Lageplan } from './lageplan'; |
| 4 | import { massstabVon } from './lageplan'; |
| 5 | import { abstand, inMeter, type Massstab, type Punkt } from './vermessung'; |
| 6 | |
| 7 | /** |
| 8 | * Die Arbeitsstelle im Lageplan - Laenge und Lage der Haltlinien dazu. |
| 9 | * |
| 10 | * WOFUER (gewuenscht am 11.09.2026 aus dem Betrieb): An einer einstreifigen |
| 11 | * Verkehrsfuehrung steht die Arbeitsstelle zuerst fest, und Haltlinien, |
| 12 | * Aufstellung und Signalgeber richten sich nach ihr. Bisher liess sich das im |
| 13 | * Programm nicht zeichnen; der Bearbeiter setzte die Haltlinien nach Augenmass |
| 14 | * ins Luftbild und trug die Zahlen daneben von Hand ein. |
| 15 | * |
| 16 | * WAS DIESE DATEI AUSDRUECKLICH NICHT TUT: rechnen. Der Arbeitsbereich geht in |
| 17 | * keine Zwischenzeit, keine Freigabezeit und keine Umlaufzeit ein. Er ist eine |
| 18 | * Zeichnung mit zwei abgeleiteten Auskuenften, und die Auskuenfte werden |
| 19 | * angezeigt und gedruckt, nicht uebernommen. Das ist kein Versaeumnis: Die |
| 20 | * Engstellenlaenge ist in diesem Programm der groesste erfasste Raeumweg der |
| 21 | * Konfliktbeziehungen, also der Abstand der Haltlinien - und ein von Hand |
| 22 | * gezeichnetes Band ist keine bessere Auskunft darueber als die Haltlinien |
| 23 | * selbst, die derselbe Bearbeiter in dieselbe Zeichnung gesetzt hat. |
| 24 | * |
| 25 | * WARUM DER ABSTAND ZU EINEM ENDPUNKT UND NICHT ZUR BANDKANTE. Gefragt ist |
| 26 | * "wie weit steht meine Haltlinie vor der Arbeitsstelle". Das ist der Abstand |
| 27 | * zum naechsten ENDE des Bandes, gemessen von der Mitte der Haltlinie. Die |
| 28 | * seitliche Kante des Bandes beantwortet eine andere Frage und ist an einer |
| 29 | * laengs liegenden Arbeitsstelle ohne Aussage. |
| 30 | * |
| 31 | * KEIN ENDE WIRD BENANNT. Liegt eine Haltlinie gleich weit von beiden Enden |
| 32 | * entfernt - an einer Baustellenampel mit mittiger Zufahrt kein seltener |
| 33 | * Fall -, duerfte nicht der Feldindex des Linienzugs entscheiden, welches Ende |
| 34 | * im Bericht steht. Deshalb nennt diese Auskunft nur die Entfernung und ob die |
| 35 | * Haltlinie IM Band liegt; welches Ende naeher ist, ist keine Angabe, die |
| 36 | * jemand braucht. |
| 37 | */ |
| 38 | |
| 39 | /** Wie eine Haltlinie zum Arbeitsbereich liegt. */ |
| 40 | export interface HaltlinienlageZumBereich { |
| 41 | readonly haltlinieId: string; |
| 42 | readonly haltlinienName: string; |
| 43 | /** Abstand der Haltlinienmitte zum naechsten Ende des Bandes. */ |
| 44 | readonly abstandMeter: Meters; |
| 45 | /** |
| 46 | * Liegt die Haltlinienmitte innerhalb des Bandes? |
| 47 | * |
| 48 | * Dann steht sie mitten in der Arbeitsstelle, und der Abstand sagt, wie weit |
| 49 | * es von dort bis zu deren naechstem Ende ist. |
| 50 | */ |
| 51 | readonly imBereich: boolean; |
| 52 | } |
| 53 | |
| 54 | /** Ein vermessener Arbeitsbereich samt der Lage aller Haltlinien dazu. */ |
| 55 | export interface Arbeitsbereichslage { |
| 56 | readonly id: string; |
| 57 | readonly name: string; |
| 58 | readonly laengeMeter: Meters; |
| 59 | readonly breiteMeter: Meters; |
| 60 | readonly haltlinien: readonly HaltlinienlageZumBereich[]; |
| 61 | } |
| 62 | |
| 63 | /** Mitte einer Haltlinie in Bildpunkten. */ |
| 64 | function mitte(h: Haltlinie): Punkt { |
| 65 | return { x: (h.von.x + h.bis.x) / 2, y: (h.von.y + h.bis.y) / 2 }; |
| 66 | } |
| 67 | |
| 68 | /** Laenge eines Linienzugs in Metern. */ |
| 69 | function zugLaenge(punkte: readonly Punkt[], massstab: Massstab): Meters { |
| 70 | let summe = 0; |
| 71 | for (let i = 1; i < punkte.length; i += 1) { |
| 72 | summe += abstand(inMeter(punkte[i - 1]!, massstab), inMeter(punkte[i]!, massstab)); |
| 73 | } |
| 74 | return summe; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Kleinster Abstand eines Punktes zu einem Linienzug - und ob er auf ihm liegt. |
| 79 | * |
| 80 | * Gemessen wird gegen die Strecken, nicht nur gegen die Stuetzpunkte: Eine |
| 81 | * Haltlinie neben der Mitte eines langen Bandes waere sonst weit entfernt. |
| 82 | */ |
| 83 | function abstandZumZug(p: Punkt, punkte: readonly Punkt[]): Meters { |
| 84 | let kleinster = Number.POSITIVE_INFINITY; |
| 85 | for (let i = 1; i < punkte.length; i += 1) { |
| 86 | const a = punkte[i - 1]!; |
| 87 | const b = punkte[i]!; |
| 88 | const dx = b.x - a.x; |
| 89 | const dy = b.y - a.y; |
| 90 | const quadrat = dx * dx + dy * dy; |
| 91 | // Entartete Strecke: Dann zaehlt der Stuetzpunkt. |
| 92 | const t = |
| 93 | quadrat === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / quadrat)); |
| 94 | const fuss = { x: a.x + dx * t, y: a.y + dy * t }; |
| 95 | const d = abstand(p, fuss); |
| 96 | if (d < kleinster) kleinster = d; |
| 97 | } |
| 98 | return kleinster; |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Vermisst einen Arbeitsbereich. `null`, solange der Massstab fehlt oder das |
| 103 | * Band aus weniger als zwei Punkten besteht - dann gibt es nichts zu messen, |
| 104 | * und eine Null waere eine Zahl, die niemand erhoben hat. |
| 105 | */ |
| 106 | export function arbeitsbereichslage( |
| 107 | lageplan: Lageplan, |
| 108 | bereich: Arbeitsbereich, |
| 109 | ): Arbeitsbereichslage | null { |
| 110 | const massstab = massstabVon(lageplan); |
| 111 | if (massstab === null) return null; |
| 112 | if (bereich.punkte.length < 2) return null; |
| 113 | |
| 114 | const erster = bereich.punkte[0]!; |
| 115 | const letzter = bereich.punkte[bereich.punkte.length - 1]!; |
| 116 | const halbeBreite = bereich.breiteMeter / 2; |
| 117 | |
| 118 | const haltlinien = lageplan.haltlinien.map((h) => { |
| 119 | const m = mitte(h); |
| 120 | const inMetern = inMeter(m, massstab); |
| 121 | const zuErsterm = abstand(inMetern, inMeter(erster, massstab)); |
| 122 | const zuLetztem = abstand(inMetern, inMeter(letzter, massstab)); |
| 123 | const seitlich = abstandZumZug(m, bereich.punkte) * massstab.meterJeBildpunkt; |
| 124 | return { |
| 125 | haltlinieId: h.id, |
| 126 | haltlinienName: h.name, |
| 127 | abstandMeter: roundTo(Math.min(zuErsterm, zuLetztem), 2), |
| 128 | // Im Band liegt, wer seitlich innerhalb der halben Breite liegt und |
| 129 | // dabei naeher am Zug als an beiden Enden ist. |
| 130 | imBereich: seitlich <= halbeBreite, |
| 131 | }; |
| 132 | }); |
| 133 | |
| 134 | return { |
| 135 | id: bereich.id, |
| 136 | name: bereich.name, |
| 137 | laengeMeter: roundTo(zugLaenge(bereich.punkte, massstab), 2), |
| 138 | breiteMeter: bereich.breiteMeter, |
| 139 | haltlinien, |
| 140 | }; |
| 141 | } |
| 142 | |
| 143 | /** Alle vermessenen Arbeitsbereiche eines Lageplans, in der Reihenfolge der Zeichnung. */ |
| 144 | export function arbeitsbereichslagen(lageplan: Lageplan): Arbeitsbereichslage[] { |
| 145 | const ergebnis: Arbeitsbereichslage[] = []; |
| 146 | for (const bereich of lageplan.arbeitsbereiche) { |
| 147 | const lage = arbeitsbereichslage(lageplan, bereich); |
| 148 | if (lage !== null) ergebnis.push(lage); |
| 149 | } |
| 150 | return ergebnis; |
| 151 | } |