lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain unterAusschnitt.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { |
| 3 | ausschnittUm, |
| 4 | kantenlaenge, |
| 5 | nachGeo, |
| 6 | unterAusschnitt, |
| 7 | type Ausschnitt, |
| 8 | } from '@/domain/geometrie/projektion'; |
| 9 | |
| 10 | /** |
| 11 | * Teilausschnitte fuer den kachelweisen Abruf. |
| 12 | * |
| 13 | * Der Fehler, um den es hier geht, ist die senkrechte Spiegelung: Bildzeile 0 |
| 14 | * liegt OBEN, also am noerdlichen Rand; die Nordwerte laufen der Bildzeile damit |
| 15 | * entgegen. Wird das vertauscht, ist jede Kachel fuer sich richtig und das |
| 16 | * Mosaik als Ganzes verkehrt. Ein Luftbild hat keine Leserichtung - es faellt |
| 17 | * niemandem auf, und vermessen wird danach die falsche Zufahrt. |
| 18 | */ |
| 19 | |
| 20 | /** Ausschnitt mit runden Zahlen: 900 m Kante, damit sich Drittel glatt teilen. */ |
| 21 | const GESAMT: Ausschnitt = { |
| 22 | minOst: 350000, |
| 23 | minNord: 5750000, |
| 24 | maxOst: 350900, |
| 25 | maxNord: 5750900, |
| 26 | zone: 32, |
| 27 | }; |
| 28 | |
| 29 | describe('Teilausschnitt einer Kachel', () => { |
| 30 | it('legt Zeile 0 an den NOERDLICHEN Rand', () => { |
| 31 | // Der eigentliche Pruefpunkt. Eine vertauschte Achse liefert hier |
| 32 | // minNord === GESAMT.minNord - das Mosaik waere senkrecht gespiegelt. |
| 33 | const obenLinks = unterAusschnitt(GESAMT, 0, 0, 3, 3); |
| 34 | |
| 35 | expect(obenLinks.maxNord).toBe(GESAMT.maxNord); |
| 36 | expect(obenLinks.minNord).toBeCloseTo(GESAMT.maxNord - 300, 9); |
| 37 | expect(obenLinks.minNord).not.toBe(GESAMT.minNord); |
| 38 | }); |
| 39 | |
| 40 | it('legt die letzte Zeile an den suedlichen Rand', () => { |
| 41 | const untenLinks = unterAusschnitt(GESAMT, 0, 2, 3, 3); |
| 42 | |
| 43 | expect(untenLinks.minNord).toBe(GESAMT.minNord); |
| 44 | expect(untenLinks.maxNord).toBeCloseTo(GESAMT.minNord + 300, 9); |
| 45 | }); |
| 46 | |
| 47 | it('laesst den Nordwert mit wachsender Zeile fallen', () => { |
| 48 | // Die Aussage in einem Satz: weiter unten im Bild heisst weiter suedlich. |
| 49 | const nordwerte = [0, 1, 2, 3, 4].map( |
| 50 | (zeile) => unterAusschnitt(GESAMT, 0, zeile, 5, 5).maxNord, |
| 51 | ); |
| 52 | |
| 53 | for (let i = 1; i < nordwerte.length; i += 1) { |
| 54 | expect(nordwerte[i]!).toBeLessThan(nordwerte[i - 1]!); |
| 55 | } |
| 56 | }); |
| 57 | |
| 58 | it('laesst den Ostwert mit wachsender Spalte steigen', () => { |
| 59 | // Waagerecht laeuft es gleichsinnig: Spalte 0 liegt links, also im Westen. |
| 60 | const ostwerte = [0, 1, 2, 3, 4].map( |
| 61 | (spalte) => unterAusschnitt(GESAMT, spalte, 0, 5, 5).minOst, |
| 62 | ); |
| 63 | |
| 64 | expect(ostwerte[0]).toBe(GESAMT.minOst); |
| 65 | for (let i = 1; i < ostwerte.length; i += 1) { |
| 66 | expect(ostwerte[i]!).toBeGreaterThan(ostwerte[i - 1]!); |
| 67 | } |
| 68 | }); |
| 69 | |
| 70 | it('faengt die Spiegelung auch geografisch', () => { |
| 71 | // Dieselbe Pruefung ohne Kartenkoordinaten: Die obere Kachel muss eine |
| 72 | // groessere geografische Breite haben als die untere - sie liegt noerdlicher. |
| 73 | const gesamt = ausschnittUm({ breite: 50.9412784, laenge: 6.9582814 }, 800); |
| 74 | const oben = unterAusschnitt(gesamt, 0, 0, 2, 2); |
| 75 | const unten = unterAusschnitt(gesamt, 0, 1, 2, 2); |
| 76 | |
| 77 | const mitteOben = nachGeo({ |
| 78 | ost: (oben.minOst + oben.maxOst) / 2, |
| 79 | nord: (oben.minNord + oben.maxNord) / 2, |
| 80 | zone: oben.zone, |
| 81 | }); |
| 82 | const mitteUnten = nachGeo({ |
| 83 | ost: (unten.minOst + unten.maxOst) / 2, |
| 84 | nord: (unten.minNord + unten.maxNord) / 2, |
| 85 | zone: unten.zone, |
| 86 | }); |
| 87 | |
| 88 | expect(mitteOben.breite).toBeGreaterThan(mitteUnten.breite); |
| 89 | // Die beiden Mitten liegen eine halbe Kantenlaenge auseinander. |
| 90 | expect(oben.minNord - unten.minNord).toBeCloseTo(400, 6); |
| 91 | }); |
| 92 | |
| 93 | it('teilt in gleich grosse Kacheln', () => { |
| 94 | for (let zeile = 0; zeile < 3; zeile += 1) { |
| 95 | for (let spalte = 0; spalte < 3; spalte += 1) { |
| 96 | const kachel = unterAusschnitt(GESAMT, spalte, zeile, 3, 3); |
| 97 | expect(kantenlaenge(kachel)).toBeCloseTo(300, 9); |
| 98 | expect(kachel.maxNord - kachel.minNord).toBeCloseTo(300, 9); |
| 99 | } |
| 100 | } |
| 101 | }); |
| 102 | |
| 103 | it('setzt sich lueckenlos und ueberschneidungsfrei wieder zusammen', () => { |
| 104 | // Ein Spalt zwischen zwei Kacheln waere im Mosaik ein Versatz - und der |
| 105 | // legt sich als scheinbarer Zeichenfehler auf jeden dort gemessenen Weg. |
| 106 | const spalten = 4; |
| 107 | const zeilen = 3; |
| 108 | |
| 109 | for (let zeile = 0; zeile < zeilen; zeile += 1) { |
| 110 | for (let spalte = 0; spalte < spalten; spalte += 1) { |
| 111 | const kachel = unterAusschnitt(GESAMT, spalte, zeile, spalten, zeilen); |
| 112 | |
| 113 | const linkerNachbar = |
| 114 | spalte === 0 ? null : unterAusschnitt(GESAMT, spalte - 1, zeile, spalten, zeilen); |
| 115 | if (linkerNachbar === null) expect(kachel.minOst).toBe(GESAMT.minOst); |
| 116 | else expect(kachel.minOst).toBe(linkerNachbar.maxOst); |
| 117 | |
| 118 | const obererNachbar = |
| 119 | zeile === 0 ? null : unterAusschnitt(GESAMT, spalte, zeile - 1, spalten, zeilen); |
| 120 | if (obererNachbar === null) expect(kachel.maxNord).toBe(GESAMT.maxNord); |
| 121 | else expect(kachel.maxNord).toBe(obererNachbar.minNord); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | // Die aeusseren Kanten treffen den Gesamtausschnitt genau. |
| 126 | const letzte = unterAusschnitt(GESAMT, spalten - 1, zeilen - 1, spalten, zeilen); |
| 127 | expect(letzte.maxOst).toBe(GESAMT.maxOst); |
| 128 | expect(letzte.minNord).toBe(GESAMT.minNord); |
| 129 | }); |
| 130 | |
| 131 | it('trifft die Aussenkanten auch bei unglatter Teilung genau', () => { |
| 132 | // 1000 m durch 7 Kacheln geht nicht auf. Aufsummierte Kachelbreiten liessen |
| 133 | // am Rand einen Rundungsspalt stehen; als Anteil gerechnet trifft die |
| 134 | // letzte Kante den Rand. |
| 135 | const gesamt = ausschnittUm({ breite: 50.9412784, laenge: 6.9582814 }, 1000); |
| 136 | const letzte = unterAusschnitt(gesamt, 6, 6, 7, 7); |
| 137 | |
| 138 | expect(letzte.maxOst).toBe(gesamt.maxOst); |
| 139 | expect(letzte.minNord).toBe(gesamt.minNord); |
| 140 | expect(unterAusschnitt(gesamt, 0, 0, 7, 7).minOst).toBe(gesamt.minOst); |
| 141 | expect(unterAusschnitt(gesamt, 0, 0, 7, 7).maxNord).toBe(gesamt.maxNord); |
| 142 | }); |
| 143 | |
| 144 | it('gibt bei einer einzigen Kachel den Gesamtausschnitt zurueck', () => { |
| 145 | expect(unterAusschnitt(GESAMT, 0, 0, 1, 1)).toEqual(GESAMT); |
| 146 | }); |
| 147 | |
| 148 | it('reicht die Zone durch', () => { |
| 149 | const inZone33 = unterAusschnitt({ ...GESAMT, zone: 33 }, 1, 1, 2, 2); |
| 150 | expect(inZone33.zone).toBe(33); |
| 151 | }); |
| 152 | |
| 153 | it('begrenzt unsinnige Angaben auf das vorhandene Raster', () => { |
| 154 | // Eine Kachel ausserhalb waere ein Bildteil aus einer fremden Gegend - im |
| 155 | // Luftbild ebenso wenig zu erkennen wie die Spiegelung. Deshalb wird |
| 156 | // begrenzt und nicht extrapoliert. |
| 157 | const rasterErsteKachel = unterAusschnitt(GESAMT, 0, 0, 2, 2); |
| 158 | const rasterLetzteKachel = unterAusschnitt(GESAMT, 1, 1, 2, 2); |
| 159 | |
| 160 | expect(unterAusschnitt(GESAMT, -3, -3, 2, 2)).toEqual(rasterErsteKachel); |
| 161 | expect(unterAusschnitt(GESAMT, 9, 9, 2, 2)).toEqual(rasterLetzteKachel); |
| 162 | expect(unterAusschnitt(GESAMT, Number.NaN, Number.NaN, 2, 2)).toEqual(rasterErsteKachel); |
| 163 | |
| 164 | // Weniger als eine Kachel je Richtung gibt es nicht. |
| 165 | expect(unterAusschnitt(GESAMT, 0, 0, 0, 0)).toEqual(GESAMT); |
| 166 | expect(unterAusschnitt(GESAMT, 0, 0, -5, Number.NaN)).toEqual(GESAMT); |
| 167 | |
| 168 | // Bruchteile werden abgeschnitten, nicht gerundet - 2,7 Kacheln gibt es nicht. |
| 169 | expect(unterAusschnitt(GESAMT, 1.9, 0.4, 2.7, 3.8)).toEqual( |
| 170 | unterAusschnitt(GESAMT, 1, 0, 2, 3), |
| 171 | ); |
| 172 | }); |
| 173 | }); |