lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain projektion.test.ts
| 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 | }); |