lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ src domain geometrie masskette.ts
| 1 | import type { Meters } from '../units'; |
| 2 | import { roundTo } from '../units'; |
| 3 | import type { Lageplan } from './lageplan'; |
| 4 | import { massstabVon } from './lageplan'; |
| 5 | import type { Planblatt } from './planblatt'; |
| 6 | import { abstand, inMeter, type Punkt } from './vermessung'; |
| 7 | |
| 8 | /** |
| 9 | * Massketten fuer das Lageplanblatt - was das Blatt nachmessbar macht. |
| 10 | * |
| 11 | * WOZU. Das Blatt traegt seit 5.32.0 einen Massstabsbalken; wer eine Laenge |
| 12 | * wissen will, greift sie damit ab. Das ist der Nachweis, aber es ist Arbeit, |
| 13 | * und es ist ungenau: Ein Abgriff am Balken traegt den Fehler des Ausdrucks |
| 14 | * und den des Lineals. Eine angeschriebene Masskette nennt die Zahl, die das |
| 15 | * Programm ohnehin kennt, an der Stelle, an der sie gilt. |
| 16 | * |
| 17 | * DIE REGEL DAHINTER, UND SIE IST DIE WICHTIGERE: Eine Masszahl auf diesem |
| 18 | * Blatt darf keine zweite Rechnung sein. Sie kommt aus derselben Vermessung |
| 19 | * wie jede andere Laenge des Programms (`inMeter`/`abstand` aus vermessung.ts, |
| 20 | * ueber den Massstab des Lageplans) und wird hier nur beschriftet. Gaebe es |
| 21 | * zwei Wege zu derselben Zahl, glaubte ein Pruefer der gezeichneten - und die |
| 22 | * waere die ungeprueftere. |
| 23 | * |
| 24 | * WAS HIER AUSDRUECKLICH NICHT BEMASST WIRD, und warum: |
| 25 | * |
| 26 | * - DER ABSTAND EINER HALTLINIE ZUM ARBEITSBEREICH. Die Auskunft gibt es |
| 27 | * (`arbeitsbereichslage` in arbeitsbereich.ts), und sie steht am Bildschirm |
| 28 | * und im Ausdruck. Sie nennt aber bewusst nur die Entfernung zum NAECHSTEN |
| 29 | * Ende und nicht, zu welchem - liegt eine Haltlinie gleich weit von beiden |
| 30 | * Enden, duerfte nicht der Feldindex des Linienzugs entscheiden. Eine |
| 31 | * gezeichnete Kette entscheidet es doch, denn sie zeigt auf ein Ende. Die |
| 32 | * Zahl bleibt deshalb in der Tabelle, wo sie nichts zeigt, was sie nicht |
| 33 | * weiss. |
| 34 | * - DER ABSTAND ZWEIER HALTLINIEN. Er ist an der einstreifigen |
| 35 | * Verkehrsfuehrung die Engstellenlaenge, und die ist in diesem Programm der |
| 36 | * groesste erfasste RAEUMWEG der Konfliktbeziehungen. Beide Zahlen |
| 37 | * beschreiben dasselbe und werden verschieden gebildet; nebeneinander auf |
| 38 | * einem Blatt waeren sie eine Einladung, der falschen zu glauben. |
| 39 | * - DIE LAENGE EINER HALTLINIE, und das ist die Berichtigung von 5.42.1. |
| 40 | * Sie stand hier zuerst, mit der Begruendung, sie sei eindeutig messbar. |
| 41 | * Eindeutig messbar heisst aber nicht gedeckt. AUS DEM BETRIEB GEMELDET: |
| 42 | * Eine Haltlinie quert die Fahrbahn, ihre Laenge liest sich als |
| 43 | * FAHRBAHNBREITE - und das Programm fuehrt eine zweite Breite, die wirklich |
| 44 | * rechnet (`SignalGroup.fahrstreifenbreite`, ueber den Faktor fb in den |
| 45 | * Zeitbedarfswert, unterhalb von 3,00 m). Auf dem Blatt stand damit eine |
| 46 | * Breite neben einer anderen, verschieden gebildet, und die gezeichnete sah |
| 47 | * nach Messung aus. An einer Arbeitsstelle kommt hinzu, dass die |
| 48 | * verbleibende Fahrbahnbreite ein Anordnungskriterium ist: Wer "2,6 m" an |
| 49 | * einer Haltlinie liest, zieht einen Schluss, den dieses Programm nicht |
| 50 | * treffen wollte und nicht decken kann. |
| 51 | * |
| 52 | * Die Laenge gibt es weiterhin - aber dort, wo sie hingehoert: in der |
| 53 | * Haltlinientabelle der Ansicht *Lageplan* und im Fenster, das die |
| 54 | * Haltlinie anlegt. Dort ist sie eine Auskunft beim Zeichnen und keine |
| 55 | * Zusicherung in einer Anordnungsunterlage. |
| 56 | * - ALLES, WAS DAS PROGRAMM NICHT FUEHRT: Restfahrbahnbreiten, Abstaende zu |
| 57 | * Signalgebern, Aufstellentfernungen von Verkehrszeichen. Wo es keine Zahl |
| 58 | * hat, zeichnet es keine Kette. |
| 59 | */ |
| 60 | |
| 61 | /** Eine Masskette auf dem Blatt - Endpunkte in Blattkoordinaten. */ |
| 62 | export interface Masskette { |
| 63 | /** Wofuer die Kette steht; die Zeichnung unterscheidet danach nicht. */ |
| 64 | readonly art: 'bereich-laenge' | 'bereich-breite'; |
| 65 | /** Der Zug, dem die Kette folgt. Mindestens zwei Punkte. */ |
| 66 | readonly punkte: readonly Punkt[]; |
| 67 | /** Die angeschriebene Laenge in Metern. */ |
| 68 | readonly meter: Meters; |
| 69 | /** Wohin die Beschriftung gehoert - die Mitte des Zuges. */ |
| 70 | readonly beschriftung: Punkt; |
| 71 | } |
| 72 | |
| 73 | /** Laenge eines Zuges in Blattpunkten. */ |
| 74 | function zugLaengeBlatt(punkte: readonly Punkt[]): number { |
| 75 | let summe = 0; |
| 76 | for (let i = 1; i < punkte.length; i += 1) { |
| 77 | const a = punkte[i - 1]!; |
| 78 | const b = punkte[i]!; |
| 79 | summe += Math.hypot(b.x - a.x, b.y - a.y); |
| 80 | } |
| 81 | return summe; |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * Der Punkt auf halber Zuglaenge - dorthin gehoert die Beschriftung. |
| 86 | * |
| 87 | * Nicht die Mitte zwischen Anfang und Ende: Bei einem geknickten Band laege |
| 88 | * die neben dem Band, und die Zahl stuende dann auf der Fahrbahn statt auf der |
| 89 | * Arbeitsstelle. |
| 90 | */ |
| 91 | function mitteAufZug(punkte: readonly Punkt[]): Punkt { |
| 92 | const halbe = zugLaengeBlatt(punkte) / 2; |
| 93 | let gegangen = 0; |
| 94 | for (let i = 1; i < punkte.length; i += 1) { |
| 95 | const a = punkte[i - 1]!; |
| 96 | const b = punkte[i]!; |
| 97 | const stueck = Math.hypot(b.x - a.x, b.y - a.y); |
| 98 | if (gegangen + stueck >= halbe && stueck > 0) { |
| 99 | const anteil = (halbe - gegangen) / stueck; |
| 100 | return { x: a.x + (b.x - a.x) * anteil, y: a.y + (b.y - a.y) * anteil }; |
| 101 | } |
| 102 | gegangen += stueck; |
| 103 | } |
| 104 | return punkte[0] ?? { x: 0, y: 0 }; |
| 105 | } |
| 106 | |
| 107 | /** Die Einheitsnormale des ersten Segments - fuer die Querkette der Breite. */ |
| 108 | function normaleAmAnfang(punkte: readonly Punkt[]): Punkt | null { |
| 109 | const a = punkte[0]; |
| 110 | const b = punkte[1]; |
| 111 | if (a === undefined || b === undefined) return null; |
| 112 | const dx = b.x - a.x; |
| 113 | const dy = b.y - a.y; |
| 114 | const laenge = Math.hypot(dx, dy); |
| 115 | if (laenge <= 1e-9) return null; |
| 116 | return { x: -dy / laenge, y: dx / laenge }; |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Die Massketten zu einem Blatt. |
| 121 | * |
| 122 | * Leer, wo der Lageplan keinen Massstab hat - dann gibt es auch kein Blatt. |
| 123 | * Die Zuordnung laeuft ueber die Kennungen: Das Blatt fuehrt dieselben |
| 124 | * Objekte in Blattkoordinaten, und die Laenge wird an den URSPRUNGSpunkten in |
| 125 | * Metern gemessen, nicht an den abgebildeten. Sonst haenge die Masszahl an der |
| 126 | * Blattgroesse. |
| 127 | */ |
| 128 | export function massketten(lageplan: Lageplan, blatt: Planblatt): readonly Masskette[] { |
| 129 | const massstab = massstabVon(lageplan); |
| 130 | if (massstab === null) return []; |
| 131 | const ketten: Masskette[] = []; |
| 132 | |
| 133 | for (const gezeichnet of blatt.arbeitsbereiche) { |
| 134 | const quelle = lageplan.arbeitsbereiche.find((b) => b.id === gezeichnet.id); |
| 135 | if (quelle === undefined) continue; |
| 136 | |
| 137 | let summe = 0; |
| 138 | for (let i = 1; i < quelle.punkte.length; i += 1) { |
| 139 | summe += abstand( |
| 140 | inMeter(quelle.punkte[i - 1]!, massstab), |
| 141 | inMeter(quelle.punkte[i]!, massstab), |
| 142 | ); |
| 143 | } |
| 144 | const laenge = roundTo(summe, 1); |
| 145 | if (laenge > 0 && gezeichnet.punkte.length >= 2) { |
| 146 | ketten.push({ |
| 147 | art: 'bereich-laenge', |
| 148 | punkte: gezeichnet.punkte, |
| 149 | meter: laenge, |
| 150 | beschriftung: mitteAufZug(gezeichnet.punkte), |
| 151 | }); |
| 152 | } |
| 153 | |
| 154 | // Die Breite quer zum ersten Segment, auf die halbe Bandbreite nach beiden |
| 155 | // Seiten: Sie IST die Ausdehnung des Bandes an dieser Stelle. |
| 156 | const normale = normaleAmAnfang(gezeichnet.punkte); |
| 157 | const anfang = gezeichnet.punkte[0]; |
| 158 | const breite = roundTo(quelle.breiteMeter, 1); |
| 159 | if (normale !== null && anfang !== undefined && breite > 0) { |
| 160 | const halb = gezeichnet.breite / 2; |
| 161 | const punkte = [ |
| 162 | { x: anfang.x - normale.x * halb, y: anfang.y - normale.y * halb }, |
| 163 | { x: anfang.x + normale.x * halb, y: anfang.y + normale.y * halb }, |
| 164 | ]; |
| 165 | ketten.push({ |
| 166 | art: 'bereich-breite', |
| 167 | punkte, |
| 168 | meter: breite, |
| 169 | beschriftung: mitteAufZug(punkte), |
| 170 | }); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | return ketten; |
| 175 | } |