import { describe, expect, it } from 'vitest'; import { ausschnittUm, bboxParameter, epsgFuerZone, kantenlaenge, leseKoordinate, nachGeo, nachUtm, utmZone, } from '@/domain/geometrie/projektion'; describe('UTM-Projektion', () => { it('bestimmt die Zone aus der geografischen Laenge', () => { // Deutschland liegt ueberwiegend in Zone 32 (6 bis 12 Grad Ost). expect(utmZone(6.86)).toBe(32); expect(utmZone(9.0)).toBe(32); expect(utmZone(11.99)).toBe(32); expect(utmZone(5.9)).toBe(31); expect(utmZone(12.1)).toBe(33); expect(epsgFuerZone(32)).toBe(25832); }); it('rechnet den Mittelmeridian auf den Rechtswert 500000', () => { // Auf dem Mittelmeridian der Zone 32 (9 Grad Ost) ist der Rechtswert // definitionsgemaess genau 500 000 m. const utm = nachUtm({ breite: 52, laenge: 9 }); expect(utm.ost).toBeCloseTo(500000, 3); expect(utm.zone).toBe(32); }); it('trifft einen bekannten Punkt', () => { // Der Aequator auf dem Mittelmeridian hat den Hochwert 0. expect(nachUtm({ breite: 0, laenge: 9 }).nord).toBeCloseTo(0, 3); // Ein oeffentlicher Ort in Nordrhein-Westfalen: der Koelner Dom. const utm = nachUtm({ breite: 50.9412784, laenge: 6.9582814 }); expect(utm.zone).toBe(32); // Groessenordnung pruefen: westlich des Mittelmeridians, also unter 500 km. // Unabhaengig nachgerechnet (Snyder, Map Projections, 1987): rund // 356 559 m Ost und 5 645 280 m Nord. expect(utm.ost).toBeGreaterThan(350000); expect(utm.ost).toBeLessThan(360000); expect(utm.nord).toBeGreaterThan(5640000); expect(utm.nord).toBeLessThan(5650000); }); it('ist umkehrbar', () => { // Vier oeffentliche Orte: Muenster (Dom), Berlin, Muenchen, Koeln. const punkte = [ { breite: 51.9629, laenge: 7.6257 }, { breite: 52.52, laenge: 13.405 }, { breite: 48.1372, laenge: 11.5756 }, { breite: 50.9375, laenge: 6.9603 }, ]; for (const punkt of punkte) { const zurueck = nachGeo(nachUtm(punkt)); // Millimetergenauigkeit reicht bei Weitem; die siebte Nachkommastelle // eines Grades entspricht rund einem Zentimeter. expect(zurueck.breite).toBeCloseTo(punkt.breite, 7); expect(zurueck.laenge).toBeCloseTo(punkt.laenge, 7); } }); it('liefert einen Ausschnitt mit echter Kantenlaenge in Metern', () => { const ausschnitt = ausschnittUm({ breite: 50.9412784, laenge: 6.9582814 }, 300); expect(kantenlaenge(ausschnitt)).toBeCloseTo(300, 6); expect(ausschnitt.maxNord - ausschnitt.minNord).toBeCloseTo(300, 6); expect(ausschnitt.zone).toBe(32); }); it('haelt den Massstabsfehler in Deutschland unter einem Promille', () => { // Zwei Punkte in 300 m Abstand: der in UTM gemessene Abstand darf nur // minimal vom wahren Abstand abweichen. Ein Verzicht auf UTM zugunsten // von Web-Mercator laege bei ueber 60 Prozent daneben. const mitte = { breite: 50.9412784, laenge: 6.9582814 }; const a = nachUtm(mitte); // 300 m noerdlich: 1 Grad Breite entspricht rund 111 320 m. const b = nachUtm({ breite: mitte.breite + 300 / 111320, laenge: mitte.laenge }); const gemessen = Math.hypot(b.ost - a.ost, b.nord - a.nord); expect(Math.abs(gemessen - 300)).toBeLessThan(0.3); }); it('erzeugt den BBOX-Parameter in der richtigen Reihenfolge', () => { const ausschnitt = ausschnittUm({ breite: 52, laenge: 9 }, 200); const teile = bboxParameter(ausschnitt).split(',').map(Number); expect(teile).toHaveLength(4); expect(teile[0]).toBeLessThan(teile[2]!); expect(teile[1]).toBeLessThan(teile[3]!); expect(teile[2]! - teile[0]!).toBeCloseTo(200, 2); }); }); describe('Koordinaten aus einer Eingabe lesen', () => { it('erkennt eine Google-Maps-Adresse', () => { const punkt = leseKoordinate( 'https://www.google.com/maps/@50.9412784,6.9582814,110m/data=!3m1!1e3', )!; expect(punkt.breite).toBeCloseTo(50.9412784, 7); expect(punkt.laenge).toBeCloseTo(6.9582814, 7); }); it('erkennt blanke Koordinatenpaare', () => { expect(leseKoordinate('50.9412784, 6.9582814')).toEqual({ breite: 50.9412784, laenge: 6.9582814, }); expect(leseKoordinate('52.52 13.405')).toEqual({ breite: 52.52, laenge: 13.405 }); }); it('versteht das deutsche Dezimalkomma', () => { expect(leseKoordinate('50,9412784; 6,9582814')).toEqual({ breite: 50.9412784, laenge: 6.9582814, }); }); it('erkennt geo- und Parameter-Schreibweisen', () => { expect(leseKoordinate('geo:52.52,13.405')).toEqual({ breite: 52.52, laenge: 13.405 }); expect(leseKoordinate('https://example.org/karte?q=52.52,13.405')).toEqual({ breite: 52.52, laenge: 13.405, }); }); /* * OpenStreetMap fehlte, und das war die schlechteste Luecke von allen: Fuer * eine Anwendung, deren Ergebnisse in behoerdliche Verfahren gehen, ist * gerade das der naheliegende Dienst. Seine Adresse traegt die Koordinate im * Fragment ("#map="), das keines der uebrigen Muster erfasste - der Verweis * wurde klaglos abgewiesen, und der Anwender hielt das Feld fuer kaputt. */ it('erkennt einen Verweis aus OpenStreetMap', () => { expect(leseKoordinate('https://www.openstreetmap.org/#map=19/50.9412784/6.9582814')).toEqual({ breite: 50.9412784, laenge: 6.9582814, }); // Die deutsche Ausgabe unter eigenem Rechnernamen, und mit einer Zahl // OHNE Nachkommastellen - "#map=5/51.5/11" ist eine gueltige Adresse. expect(leseKoordinate('https://karte.openstreetmap.de/#map=5/51.5/11')).toEqual({ breite: 51.5, laenge: 11, }); }); it('erkennt einen Verweis mit gesetzter Marke', () => { expect( leseKoordinate( 'https://www.openstreetmap.org/?mlat=50.9412784&mlon=6.9582814#map=19/50.9412784/6.9582814', ), ).toEqual({ breite: 50.9412784, laenge: 6.9582814 }); }); it('erkennt einen Verweis aus Bing Maps', () => { expect(leseKoordinate('https://www.bing.com/maps?cp=50.9412784~6.9582814&lvl=19')).toEqual({ breite: 50.9412784, laenge: 6.9582814, }); }); it('nimmt aus einem Verweis die Koordinate und nicht die Zoomstufe', () => { /* * Ein Kartenverweis traegt mehrere Zahlenpaare: Zoomstufe, Bildgroesse, * Zeitstempel. Wuerde blind das erste Paar genommen, ergaebe * "#map=19/51.9/6.8" den Punkt 19 Nord, 51,9 Ost - das liegt im Indischen * Ozean, und das Luftbild waere leer, ohne dass jemand den Grund saehe. */ const punkt = leseKoordinate('https://www.openstreetmap.org/#map=19/50.9412784/6.9582814')!; expect(punkt.breite).toBeCloseTo(50.9412784, 7); expect(punkt.laenge).toBeCloseTo(6.9582814, 7); const google = leseKoordinate( 'https://www.google.com/maps/@50.9412784,6.9582814,110m/data=!3m1!1e3', )!; expect(google.breite).toBeCloseTo(50.9412784, 7); // Nicht die 110 aus "110m". expect(google.laenge).toBeCloseTo(6.9582814, 7); }); it('weist Unsinn ab, statt etwas zu erfinden', () => { expect(leseKoordinate('')).toBeNull(); expect(leseKoordinate('Hauptstrasse 5')).toBeNull(); expect(leseKoordinate('999, 999')).toBeNull(); expect(leseKoordinate('51.9')).toBeNull(); // Ein Verweis ohne Koordinate darf nicht irgendein Zahlenpaar liefern. expect(leseKoordinate('https://www.openstreetmap.org/search?query=Musterstadt')).toBeNull(); expect(leseKoordinate('https://beispiel.example/x?breite=51.9&laenge=6.8')).toBeNull(); }); });