lsa-planer

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

/ src domain geometrie masskette.ts

7,3 KB Rohdatei
src/domain/geometrie/masskette.ts — 175 Zeilen
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 }