lsa-planer

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

/ tests domain projektion.test.ts

7,4 KB Rohdatei
tests/domain/projektion.test.ts — 191 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import {
3 ausschnittUm,
4 bboxParameter,
5 epsgFuerZone,
6 kantenlaenge,
7 leseKoordinate,
8 nachGeo,
9 nachUtm,
10 utmZone,
11 } from '@/domain/geometrie/projektion';
12
13 describe('UTM-Projektion', () => {
14 it('bestimmt die Zone aus der geografischen Laenge', () => {
15 // Deutschland liegt ueberwiegend in Zone 32 (6 bis 12 Grad Ost).
16 expect(utmZone(6.86)).toBe(32);
17 expect(utmZone(9.0)).toBe(32);
18 expect(utmZone(11.99)).toBe(32);
19 expect(utmZone(5.9)).toBe(31);
20 expect(utmZone(12.1)).toBe(33);
21 expect(epsgFuerZone(32)).toBe(25832);
22 });
23
24 it('rechnet den Mittelmeridian auf den Rechtswert 500000', () => {
25 // Auf dem Mittelmeridian der Zone 32 (9 Grad Ost) ist der Rechtswert
26 // definitionsgemaess genau 500 000 m.
27 const utm = nachUtm({ breite: 52, laenge: 9 });
28 expect(utm.ost).toBeCloseTo(500000, 3);
29 expect(utm.zone).toBe(32);
30 });
31
32 it('trifft einen bekannten Punkt', () => {
33 // Der Aequator auf dem Mittelmeridian hat den Hochwert 0.
34 expect(nachUtm({ breite: 0, laenge: 9 }).nord).toBeCloseTo(0, 3);
35
36 // Ein oeffentlicher Ort in Nordrhein-Westfalen: der Koelner Dom.
37 const utm = nachUtm({ breite: 50.9412784, laenge: 6.9582814 });
38 expect(utm.zone).toBe(32);
39 // Groessenordnung pruefen: westlich des Mittelmeridians, also unter 500 km.
40 // Unabhaengig nachgerechnet (Snyder, Map Projections, 1987): rund
41 // 356 559 m Ost und 5 645 280 m Nord.
42 expect(utm.ost).toBeGreaterThan(350000);
43 expect(utm.ost).toBeLessThan(360000);
44 expect(utm.nord).toBeGreaterThan(5640000);
45 expect(utm.nord).toBeLessThan(5650000);
46 });
47
48 it('ist umkehrbar', () => {
49 // Vier oeffentliche Orte: Muenster (Dom), Berlin, Muenchen, Koeln.
50 const punkte = [
51 { breite: 51.9629, laenge: 7.6257 },
52 { breite: 52.52, laenge: 13.405 },
53 { breite: 48.1372, laenge: 11.5756 },
54 { breite: 50.9375, laenge: 6.9603 },
55 ];
56
57 for (const punkt of punkte) {
58 const zurueck = nachGeo(nachUtm(punkt));
59 // Millimetergenauigkeit reicht bei Weitem; die siebte Nachkommastelle
60 // eines Grades entspricht rund einem Zentimeter.
61 expect(zurueck.breite).toBeCloseTo(punkt.breite, 7);
62 expect(zurueck.laenge).toBeCloseTo(punkt.laenge, 7);
63 }
64 });
65
66 it('liefert einen Ausschnitt mit echter Kantenlaenge in Metern', () => {
67 const ausschnitt = ausschnittUm({ breite: 50.9412784, laenge: 6.9582814 }, 300);
68 expect(kantenlaenge(ausschnitt)).toBeCloseTo(300, 6);
69 expect(ausschnitt.maxNord - ausschnitt.minNord).toBeCloseTo(300, 6);
70 expect(ausschnitt.zone).toBe(32);
71 });
72
73 it('haelt den Massstabsfehler in Deutschland unter einem Promille', () => {
74 // Zwei Punkte in 300 m Abstand: der in UTM gemessene Abstand darf nur
75 // minimal vom wahren Abstand abweichen. Ein Verzicht auf UTM zugunsten
76 // von Web-Mercator laege bei ueber 60 Prozent daneben.
77 const mitte = { breite: 50.9412784, laenge: 6.9582814 };
78 const a = nachUtm(mitte);
79 // 300 m noerdlich: 1 Grad Breite entspricht rund 111 320 m.
80 const b = nachUtm({ breite: mitte.breite + 300 / 111320, laenge: mitte.laenge });
81
82 const gemessen = Math.hypot(b.ost - a.ost, b.nord - a.nord);
83 expect(Math.abs(gemessen - 300)).toBeLessThan(0.3);
84 });
85
86 it('erzeugt den BBOX-Parameter in der richtigen Reihenfolge', () => {
87 const ausschnitt = ausschnittUm({ breite: 52, laenge: 9 }, 200);
88 const teile = bboxParameter(ausschnitt).split(',').map(Number);
89 expect(teile).toHaveLength(4);
90 expect(teile[0]).toBeLessThan(teile[2]!);
91 expect(teile[1]).toBeLessThan(teile[3]!);
92 expect(teile[2]! - teile[0]!).toBeCloseTo(200, 2);
93 });
94 });
95
96 describe('Koordinaten aus einer Eingabe lesen', () => {
97 it('erkennt eine Google-Maps-Adresse', () => {
98 const punkt = leseKoordinate(
99 'https://www.google.com/maps/@50.9412784,6.9582814,110m/data=!3m1!1e3',
100 )!;
101 expect(punkt.breite).toBeCloseTo(50.9412784, 7);
102 expect(punkt.laenge).toBeCloseTo(6.9582814, 7);
103 });
104
105 it('erkennt blanke Koordinatenpaare', () => {
106 expect(leseKoordinate('50.9412784, 6.9582814')).toEqual({
107 breite: 50.9412784,
108 laenge: 6.9582814,
109 });
110 expect(leseKoordinate('52.52 13.405')).toEqual({ breite: 52.52, laenge: 13.405 });
111 });
112
113 it('versteht das deutsche Dezimalkomma', () => {
114 expect(leseKoordinate('50,9412784; 6,9582814')).toEqual({
115 breite: 50.9412784,
116 laenge: 6.9582814,
117 });
118 });
119
120 it('erkennt geo- und Parameter-Schreibweisen', () => {
121 expect(leseKoordinate('geo:52.52,13.405')).toEqual({ breite: 52.52, laenge: 13.405 });
122 expect(leseKoordinate('https://example.org/karte?q=52.52,13.405')).toEqual({
123 breite: 52.52,
124 laenge: 13.405,
125 });
126 });
127
128 /*
129 * OpenStreetMap fehlte, und das war die schlechteste Luecke von allen: Fuer
130 * eine Anwendung, deren Ergebnisse in behoerdliche Verfahren gehen, ist
131 * gerade das der naheliegende Dienst. Seine Adresse traegt die Koordinate im
132 * Fragment ("#map="), das keines der uebrigen Muster erfasste - der Verweis
133 * wurde klaglos abgewiesen, und der Anwender hielt das Feld fuer kaputt.
134 */
135 it('erkennt einen Verweis aus OpenStreetMap', () => {
136 expect(leseKoordinate('https://www.openstreetmap.org/#map=19/50.9412784/6.9582814')).toEqual({
137 breite: 50.9412784,
138 laenge: 6.9582814,
139 });
140 // Die deutsche Ausgabe unter eigenem Rechnernamen, und mit einer Zahl
141 // OHNE Nachkommastellen - "#map=5/51.5/11" ist eine gueltige Adresse.
142 expect(leseKoordinate('https://karte.openstreetmap.de/#map=5/51.5/11')).toEqual({
143 breite: 51.5,
144 laenge: 11,
145 });
146 });
147
148 it('erkennt einen Verweis mit gesetzter Marke', () => {
149 expect(
150 leseKoordinate(
151 'https://www.openstreetmap.org/?mlat=50.9412784&mlon=6.9582814#map=19/50.9412784/6.9582814',
152 ),
153 ).toEqual({ breite: 50.9412784, laenge: 6.9582814 });
154 });
155
156 it('erkennt einen Verweis aus Bing Maps', () => {
157 expect(leseKoordinate('https://www.bing.com/maps?cp=50.9412784~6.9582814&lvl=19')).toEqual({
158 breite: 50.9412784,
159 laenge: 6.9582814,
160 });
161 });
162
163 it('nimmt aus einem Verweis die Koordinate und nicht die Zoomstufe', () => {
164 /*
165 * Ein Kartenverweis traegt mehrere Zahlenpaare: Zoomstufe, Bildgroesse,
166 * Zeitstempel. Wuerde blind das erste Paar genommen, ergaebe
167 * "#map=19/51.9/6.8" den Punkt 19 Nord, 51,9 Ost - das liegt im Indischen
168 * Ozean, und das Luftbild waere leer, ohne dass jemand den Grund saehe.
169 */
170 const punkt = leseKoordinate('https://www.openstreetmap.org/#map=19/50.9412784/6.9582814')!;
171 expect(punkt.breite).toBeCloseTo(50.9412784, 7);
172 expect(punkt.laenge).toBeCloseTo(6.9582814, 7);
173
174 const google = leseKoordinate(
175 'https://www.google.com/maps/@50.9412784,6.9582814,110m/data=!3m1!1e3',
176 )!;
177 expect(google.breite).toBeCloseTo(50.9412784, 7);
178 // Nicht die 110 aus "110m".
179 expect(google.laenge).toBeCloseTo(6.9582814, 7);
180 });
181
182 it('weist Unsinn ab, statt etwas zu erfinden', () => {
183 expect(leseKoordinate('')).toBeNull();
184 expect(leseKoordinate('Hauptstrasse 5')).toBeNull();
185 expect(leseKoordinate('999, 999')).toBeNull();
186 expect(leseKoordinate('51.9')).toBeNull();
187 // Ein Verweis ohne Koordinate darf nicht irgendein Zahlenpaar liefern.
188 expect(leseKoordinate('https://www.openstreetmap.org/search?query=Musterstadt')).toBeNull();
189 expect(leseKoordinate('https://beispiel.example/x?breite=51.9&laenge=6.8')).toBeNull();
190 });
191 });