lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ src services kartendienste.ts
| 1 | import type { Meters } from '@/domain/units'; |
| 2 | import { |
| 3 | ausschnittUm, |
| 4 | bboxParameter, |
| 5 | epsgFuerZone, |
| 6 | unterAusschnitt, |
| 7 | type Ausschnitt, |
| 8 | type GeoPunkt, |
| 9 | } from '@/domain/geometrie/projektion'; |
| 10 | |
| 11 | /** |
| 12 | * Amtliche Kartendienste als Grundlage des Lageplans. |
| 13 | * |
| 14 | * WARUM AMTLICHE DIENSTE UND KEIN BEKANNTER KARTENANBIETER |
| 15 | * |
| 16 | * Die Kacheln der grossen Kartenanbieter duerfen ausserhalb ihrer eigenen |
| 17 | * kostenpflichtigen Schnittstellen nicht verwendet, nicht zwischengespeichert |
| 18 | * und erst recht nicht in eine fremde Datei eingebettet werden. Genau das waere |
| 19 | * hier aber noetig: Ein Signalzeitenplan muss Jahre spaeter noch aus der |
| 20 | * Projektdatei allein nachvollziehbar sein. |
| 21 | * |
| 22 | * Die Landesvermessungsverwaltungen stellen dieselbe Grundlage in besserer |
| 23 | * Qualitaet und mit klarer Rechtslage bereit: digitale Orthophotos, also |
| 24 | * entzerrte und lagerichtige Luftbilder mit 20 cm Bodenaufloesung oder besser. |
| 25 | * "Lagerichtig" ist der entscheidende Punkt - in einem Orthophoto darf gemessen |
| 26 | * werden, in einer Schraegaufnahme nicht. |
| 27 | * |
| 28 | * PRUEFSTAND DER EINTRAEGE |
| 29 | * |
| 30 | * Jeder Eintrag wurde mit einem echten Bildabruf ueber der jeweiligen |
| 31 | * Landeshauptstadt geprueft. Die Angaben zu Nutzungsrecht und Quellenvermerk |
| 32 | * stammen aus der Selbstauskunft des Dienstes (GetCapabilities, Felder Fees und |
| 33 | * AccessConstraints). Ein Quellenvermerk ist nur dort eingetragen, wo der |
| 34 | * Dienst ihn woertlich vorgibt; sonst bleibt das Feld leer, und es wird der |
| 35 | * Herausgeber genannt. |
| 36 | * |
| 37 | * BEZUGSSYSTEM |
| 38 | * |
| 39 | * Angefordert wird durchweg in ETRS89/UTM (EPSG:258xx) - dem Bezugssystem der |
| 40 | * deutschen Landesvermessung. Dessen Karteneinheiten sind Meter; ein daraus |
| 41 | * abgegriffener Raeumweg ist damit unmittelbar der Raeumweg in der Wirklichkeit. |
| 42 | * Die ausfuehrliche Begruendung steht in domain/geometrie/projektion.ts. |
| 43 | */ |
| 44 | |
| 45 | export type Kartenart = 'luftbild' | 'karte'; |
| 46 | |
| 47 | export interface Kartendienst { |
| 48 | readonly id: string; |
| 49 | /** Bezeichnung in der Auswahlliste. */ |
| 50 | readonly name: string; |
| 51 | readonly art: Kartenart; |
| 52 | /** Herausgeber - erscheint als Quellenangabe im Plan und im Ausdruck. */ |
| 53 | readonly herausgeber: string; |
| 54 | /** Gebiet, fuer das der Dienst Daten liefert. */ |
| 55 | readonly gebiet: string; |
| 56 | readonly basisUrl: string; |
| 57 | readonly ebene: string; |
| 58 | readonly bildformat: 'image/jpeg' | 'image/png'; |
| 59 | /** Bodenaufloesung in Metern je Bildpunkt. */ |
| 60 | readonly aufloesung: Meters; |
| 61 | /** Groesste vom Dienst zugelassene Bildkante. */ |
| 62 | readonly maxKante: number; |
| 63 | /** Nutzungsrecht laut Selbstauskunft des Dienstes. */ |
| 64 | readonly nutzungsrecht: string; |
| 65 | /** Woertlich vorgegebener Quellenvermerk, sofern der Dienst einen nennt. */ |
| 66 | readonly namensnennung: string | null; |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Bodenaufloesung: 0,1 m bedeutet, dass ein Bildpunkt 10 cm am Boden |
| 71 | * entspricht. Feiner anzufordern bringt nichts - das Bild wird dadurch nur |
| 72 | * groesser, nicht schaerfer. |
| 73 | * |
| 74 | * Die Liste ist nach Gebiet geordnet; der Bundesdienst steht vorn, weil er |
| 75 | * ueberall gilt. |
| 76 | */ |
| 77 | export const KARTENDIENSTE: readonly Kartendienst[] = [ |
| 78 | { |
| 79 | id: 'de-topplus', |
| 80 | name: 'Deutschland · Amtliche Stadtkarte (TopPlusOpen)', |
| 81 | art: 'karte', |
| 82 | herausgeber: 'Bundesamt für Kartographie und Geodäsie', |
| 83 | gebiet: 'Deutschland und Europa', |
| 84 | basisUrl: 'https://sgx.geodatenzentrum.de/wms_topplus_open', |
| 85 | ebene: 'web', |
| 86 | bildformat: 'image/png', |
| 87 | aufloesung: 0.2, |
| 88 | maxKante: 6000, |
| 89 | nutzungsrecht: 'Datenlizenz Deutschland Namensnennung 2.0, geldleistungsfrei', |
| 90 | namensnennung: null, |
| 91 | }, |
| 92 | { |
| 93 | id: 'bw-dop', |
| 94 | name: 'Baden-Württemberg · Luftbild 20 cm', |
| 95 | art: 'luftbild', |
| 96 | herausgeber: 'Landesamt für Geoinformation und Landentwicklung Baden-Württemberg', |
| 97 | gebiet: 'Baden-Württemberg', |
| 98 | basisUrl: 'https://owsproxy.lgl-bw.de/owsproxy/ows/WMS_LGL-BW_ATKIS_DOP_20_C', |
| 99 | ebene: 'IMAGES_DOP_20_RGB', |
| 100 | bildformat: 'image/jpeg', |
| 101 | aufloesung: 0.2, |
| 102 | maxKante: 15000, |
| 103 | nutzungsrecht: 'Datenlizenz Deutschland Namensnennung 2.0, unentgeltlich', |
| 104 | namensnennung: 'LGL-BW (Jahr des Datenbezugs), dl-de/by-2-0, www.lgl-bw.de', |
| 105 | }, |
| 106 | { |
| 107 | id: 'by-dop', |
| 108 | name: 'Bayern · Luftbild 40 cm', |
| 109 | art: 'luftbild', |
| 110 | herausgeber: 'Bayerische Vermessungsverwaltung', |
| 111 | gebiet: 'Bayern', |
| 112 | basisUrl: 'https://geoservices.bayern.de/od/wms/dop/v1/dop40', |
| 113 | ebene: 'by_dop40c', |
| 114 | bildformat: 'image/jpeg', |
| 115 | aufloesung: 0.4, |
| 116 | maxKante: 6000, |
| 117 | nutzungsrecht: 'Creative Commons Namensnennung 4.0 (CC BY 4.0), kostenfrei', |
| 118 | namensnennung: null, |
| 119 | }, |
| 120 | { |
| 121 | id: 'bebb-dop', |
| 122 | name: 'Berlin und Brandenburg · Luftbild 20 cm', |
| 123 | art: 'luftbild', |
| 124 | herausgeber: 'Landesvermessung und Geobasisinformation Brandenburg', |
| 125 | gebiet: 'Berlin und Brandenburg', |
| 126 | basisUrl: 'https://isk.geobasis-bb.de/mapproxy/dop20c/service/wms', |
| 127 | ebene: 'bebb_dop20c', |
| 128 | bildformat: 'image/jpeg', |
| 129 | aufloesung: 0.2, |
| 130 | maxKante: 4000, |
| 131 | nutzungsrecht: 'Datenlizenz Deutschland Namensnennung 2.0, kostenfrei', |
| 132 | namensnennung: '© GeoBasis-DE/LGB, dl-de/by-2-0; © Geoportal Berlin, dl-de/by-2-0', |
| 133 | }, |
| 134 | { |
| 135 | id: 'he-dop', |
| 136 | name: 'Hessen · Luftbild 20 cm', |
| 137 | art: 'luftbild', |
| 138 | herausgeber: 'Hessische Verwaltung für Bodenmanagement und Geoinformation', |
| 139 | gebiet: 'Hessen', |
| 140 | basisUrl: 'https://www.gds-srv.hessen.de/cgi-bin/lika-services/ogc-free-images.ows', |
| 141 | ebene: 'he_dop20_rgb', |
| 142 | bildformat: 'image/jpeg', |
| 143 | aufloesung: 0.2, |
| 144 | maxKante: 3000, |
| 145 | nutzungsrecht: 'Ohne Einschränkung nutzbar, kostenfrei (§ 24 HVGG)', |
| 146 | namensnennung: null, |
| 147 | }, |
| 148 | { |
| 149 | id: 'mv-dop', |
| 150 | name: 'Mecklenburg-Vorpommern · Luftbild 20 cm', |
| 151 | art: 'luftbild', |
| 152 | herausgeber: 'Amt für Geoinformation, Vermessungs- und Katasterwesen M-V', |
| 153 | gebiet: 'Mecklenburg-Vorpommern', |
| 154 | basisUrl: 'https://www.geodaten-mv.de/dienste/adv_dop', |
| 155 | ebene: 'mv_dop', |
| 156 | bildformat: 'image/jpeg', |
| 157 | aufloesung: 0.2, |
| 158 | maxKante: 4192, |
| 159 | nutzungsrecht: 'Es gelten keine Bedingungen; sichtbarer Quellenvermerk verlangt', |
| 160 | namensnennung: '© GeoBasis-DE/M-V (Jahr der Datenlieferung)', |
| 161 | }, |
| 162 | { |
| 163 | id: 'ni-dop', |
| 164 | name: 'Niedersachsen · Luftbild 20 cm', |
| 165 | art: 'luftbild', |
| 166 | herausgeber: 'Landesamt für Geoinformation und Landesvermessung Niedersachsen', |
| 167 | gebiet: 'Niedersachsen', |
| 168 | basisUrl: 'https://opendata.lgln.niedersachsen.de/doorman/noauth/dop_wms', |
| 169 | ebene: 'ni_dop20', |
| 170 | bildformat: 'image/jpeg', |
| 171 | aufloesung: 0.2, |
| 172 | maxKante: 15000, |
| 173 | nutzungsrecht: 'Creative Commons Namensnennung 4.0 (CC BY 4.0)', |
| 174 | namensnennung: 'LGLN (Jahr des Datenbezugs), CC BY 4.0', |
| 175 | }, |
| 176 | { |
| 177 | id: 'nw-dop', |
| 178 | name: 'Nordrhein-Westfalen · Luftbild 10 cm', |
| 179 | art: 'luftbild', |
| 180 | herausgeber: 'Land NRW, Bezirksregierung Köln - Geobasis NRW', |
| 181 | gebiet: 'Nordrhein-Westfalen', |
| 182 | basisUrl: 'https://www.wms.nrw.de/geobasis/wms_nw_dop', |
| 183 | ebene: 'nw_dop_rgb', |
| 184 | bildformat: 'image/jpeg', |
| 185 | aufloesung: 0.1, |
| 186 | maxKante: 5000, |
| 187 | nutzungsrecht: 'Datenlizenz Deutschland Zero 2.0 - Nutzung ohne Einschränkungen', |
| 188 | namensnennung: null, |
| 189 | }, |
| 190 | { |
| 191 | id: 'rp-dop', |
| 192 | name: 'Rheinland-Pfalz · Luftbild 20 cm', |
| 193 | art: 'luftbild', |
| 194 | herausgeber: 'Vermessungs- und Katasterverwaltung Rheinland-Pfalz', |
| 195 | gebiet: 'Rheinland-Pfalz', |
| 196 | basisUrl: 'https://geo4.service24.rlp.de/wms/rp_dop20.fcgi', |
| 197 | ebene: 'rp_dop20', |
| 198 | bildformat: 'image/jpeg', |
| 199 | aufloesung: 0.2, |
| 200 | maxKante: 10000, |
| 201 | nutzungsrecht: 'Datenlizenz Deutschland Namensnennung 2.0, geldleistungsfrei', |
| 202 | namensnennung: '© GeoBasis-DE / LVermGeoRP (Jahr des Datenbezugs), dl-de/by-2-0', |
| 203 | }, |
| 204 | { |
| 205 | id: 'sn-dop', |
| 206 | name: 'Sachsen · Luftbild 20 cm', |
| 207 | art: 'luftbild', |
| 208 | herausgeber: 'Staatsbetrieb Geobasisinformation und Vermessung Sachsen', |
| 209 | gebiet: 'Sachsen', |
| 210 | basisUrl: 'https://geodienste.sachsen.de/wms_geosn_dop-rgb/guest', |
| 211 | ebene: 'sn_dop_020', |
| 212 | bildformat: 'image/jpeg', |
| 213 | aufloesung: 0.2, |
| 214 | maxKante: 4096, |
| 215 | nutzungsrecht: 'Kostenfrei nach den Nutzungsbedingungen des Geoportals Sachsen', |
| 216 | namensnennung: null, |
| 217 | }, |
| 218 | { |
| 219 | id: 'st-dop', |
| 220 | name: 'Sachsen-Anhalt · Luftbild 20 cm', |
| 221 | art: 'luftbild', |
| 222 | herausgeber: 'Landesamt für Vermessung und Geoinformation Sachsen-Anhalt', |
| 223 | gebiet: 'Sachsen-Anhalt', |
| 224 | basisUrl: |
| 225 | 'https://www.geodatenportal.sachsen-anhalt.de/wss/service/ST_LVermGeo_DOP_WMS_OpenData/guest', |
| 226 | ebene: 'lsa_lvermgeo_dop20_2', |
| 227 | bildformat: 'image/jpeg', |
| 228 | aufloesung: 0.2, |
| 229 | maxKante: 4096, |
| 230 | nutzungsrecht: 'Offene Daten des Landes Sachsen-Anhalt', |
| 231 | namensnennung: null, |
| 232 | }, |
| 233 | { |
| 234 | id: 'sh-dop', |
| 235 | name: 'Schleswig-Holstein · Luftbild 20 cm', |
| 236 | art: 'luftbild', |
| 237 | herausgeber: 'Landesamt für Vermessung und Geoinformation Schleswig-Holstein', |
| 238 | gebiet: 'Schleswig-Holstein', |
| 239 | basisUrl: 'https://dienste.gdi-sh.de/WMS_SH_DOP20col_OpenGBD', |
| 240 | ebene: 'sh_dop20_rgb', |
| 241 | bildformat: 'image/jpeg', |
| 242 | aufloesung: 0.2, |
| 243 | maxKante: 5000, |
| 244 | nutzungsrecht: 'Creative Commons Namensnennung 4.0 (CC BY 4.0)', |
| 245 | namensnennung: '© GeoBasis-DE/LVermGeo SH/CC BY 4.0', |
| 246 | }, |
| 247 | ]; |
| 248 | |
| 249 | /** Vorbelegung der Auswahl. */ |
| 250 | export const STANDARD_DIENST = 'nw-dop'; |
| 251 | |
| 252 | /** Dienst zu einer Kennung; ohne Treffer der erste Eintrag. */ |
| 253 | export function dienstMit(id: string): Kartendienst { |
| 254 | return KARTENDIENSTE.find((d) => d.id === id) ?? (KARTENDIENSTE[0] as Kartendienst); |
| 255 | } |
| 256 | |
| 257 | /** Rechnernamen aller eingetragenen Dienste - fuer die Freigabeliste im Hauptprozess. */ |
| 258 | export function rechnernamen(): readonly string[] { |
| 259 | return [...new Set(KARTENDIENSTE.map((d) => new URL(d.basisUrl).hostname))]; |
| 260 | } |
| 261 | |
| 262 | // --- Bildanforderung --------------------------------------------------------- |
| 263 | |
| 264 | /* |
| 265 | * Hier stand ein zweiter Weg: baueAbruf() holte den Ausschnitt in EINEM Bild, |
| 266 | * begrenzt auf 1600 Bildpunkte (MAX_ANGEFORDERTE_KANTE). Seit der Kachelung |
| 267 | * ging ihn niemand mehr - der Luftbilddialog ruft ausschliesslich |
| 268 | * baueKachelplan -, nur die Pruefungen hielten ihn noch am Leben. Ein Weg, den |
| 269 | * das Programm nicht geht, driftet unbemerkt von dem ab, der benutzt wird: |
| 270 | * Beide bauten dieselbe Adresse, rechneten dieselbe Bodenaufloesung und |
| 271 | * schrieben denselben Herkunftstext, aber nur einer davon wurde beim naechsten |
| 272 | * Fehler mitgeaendert. Entfernt, mitsamt seinen Pruefungen. |
| 273 | */ |
| 274 | |
| 275 | /** Kleinste sinnvolle Bildkante. */ |
| 276 | const MIN_KANTE = 512; |
| 277 | |
| 278 | export interface Kartenausschnitt { |
| 279 | readonly dienst: Kartendienst; |
| 280 | readonly mitte: GeoPunkt; |
| 281 | /** Kantenlaenge des quadratischen Ausschnitts am Boden. */ |
| 282 | readonly kantenlaenge: Meters; |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * Baut die Adresse EINES Bildabrufs. |
| 287 | * |
| 288 | * Einziger Aufrufer ist der Kachelplan. Bis 5.3.0 ging auch ein zweiter Weg |
| 289 | * hier durch - ein Einzelabruf des ganzen Ausschnitts -, und die gemeinsame |
| 290 | * Stelle war der Grund: Wichen die beiden Wege voneinander ab, saehe man das |
| 291 | * dem Bild nicht an, aber jede daraus abgegriffene Laenge waere falsch. Der |
| 292 | * zweite Weg ist entfallen (siehe oben); die Funktion bleibt getrennt, weil |
| 293 | * sie die Achsfolge und die Zonenwahl an EINER Stelle festhaelt. |
| 294 | * |
| 295 | * Die Zone wird dem Ausschnitt entnommen und nicht erneut aus der geografischen |
| 296 | * Laenge bestimmt: Eine Kachel liegt in derselben Zone wie das Gesamtbild, auch |
| 297 | * wenn ihr Rand rechnerisch knapp in die Nachbarzone reichte. Zwei Kacheln in |
| 298 | * verschiedenen Zonen liessen sich nicht zusammensetzen. |
| 299 | */ |
| 300 | function abrufAdresse(dienst: Kartendienst, ausschnitt: Ausschnitt, kante: number): string { |
| 301 | // WMS 1.3.0: Bei den UTM-Systemen ist die Achsfolge Ost/Nord, die BBOX also |
| 302 | // minOst,minNord,maxOst,maxNord. (Nur bei EPSG:4326 waere sie vertauscht - |
| 303 | // ein haeufiger Grund fuer scheinbar leere Bilder.) |
| 304 | const parameter = new URLSearchParams({ |
| 305 | SERVICE: 'WMS', |
| 306 | VERSION: '1.3.0', |
| 307 | REQUEST: 'GetMap', |
| 308 | LAYERS: dienst.ebene, |
| 309 | STYLES: '', |
| 310 | CRS: `EPSG:${epsgFuerZone(ausschnitt.zone)}`, |
| 311 | BBOX: bboxParameter(ausschnitt), |
| 312 | WIDTH: String(kante), |
| 313 | HEIGHT: String(kante), |
| 314 | FORMAT: dienst.bildformat, |
| 315 | TRANSPARENT: 'FALSE', |
| 316 | }); |
| 317 | return `${dienst.basisUrl}?${parameter.toString()}`; |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * Quellenangabe, die im Plan und im Ausdruck erscheint. |
| 322 | * |
| 323 | * Sie enthaelt Herausgeber, Ausschnittsmitte und Kantenlaenge - damit laesst |
| 324 | * sich der Ausschnitt spaeter Punkt fuer Punkt nachstellen. Ein Pruefer kann |
| 325 | * die Grundlage der Vermassung dadurch selbst nachvollziehen. |
| 326 | */ |
| 327 | export function herkunftstext(dienst: Kartendienst, mitte: GeoPunkt, kantenlaenge: Meters): string { |
| 328 | const koordinate = `${mitte.breite.toFixed(6)}, ${mitte.laenge.toFixed(6)}`; |
| 329 | const vermerk = dienst.namensnennung === null ? '' : ` · ${dienst.namensnennung}`; |
| 330 | return `${dienst.name} · ${dienst.herausgeber} · Mitte ${koordinate} · Kante ${Math.round(kantenlaenge)} m${vermerk}`; |
| 331 | } |
| 332 | |
| 333 | // --- Kachelplan -------------------------------------------------------------- |
| 334 | |
| 335 | /** |
| 336 | * Groesste Kante EINER Kachel. |
| 337 | * |
| 338 | * Begrenzt, was einem Dienst in einem Zug zugemutet wird: Der Kartendienst |
| 339 | * rechnet das Bild erst aus, und der Aufwand waechst mit der Flaeche. Gemessen |
| 340 | * am Dienst Nordrhein-Westfalens dauerte ein Bild mit 2000 Bildpunkten ueber |
| 341 | * 40 Sekunden, eines mit 1600 rund zwei Drittel davon. |
| 342 | * |
| 343 | * Fachlich kostet die Grenze nichts, weil groessere Ausschnitte aus mehreren |
| 344 | * Kacheln entstehen: Die Bodenaufloesung des fertigen Bildes haengt an |
| 345 | * MAX_MOSAIK_KANTE, nicht hieran. |
| 346 | */ |
| 347 | export const KACHEL_KANTE = 1600; |
| 348 | |
| 349 | /** |
| 350 | * Groesste Kante des zusammengesetzten Bildes. |
| 351 | * |
| 352 | * Daran haengt die Genauigkeit, mit der spaeter gemessen wird: 7000 Bildpunkte |
| 353 | * reichen, damit ein 20-cm-Dienst bis 1400 m Kantenlaenge seine volle |
| 354 | * Bodenaufloesung liefert. Der feinste eingetragene Dienst (Nordrhein-Westfalen, |
| 355 | * 10 cm) schoepft sie nur bis 700 m aus; bei 1000 m liefert er 14,3 cm je |
| 356 | * Bildpunkt statt 10. Bei 2000 m sind es 28,6 cm - ein Uebersichtsbild. |
| 357 | * |
| 358 | * Hier stand "damit JEDER Dienst bis 1000 m seine volle Bodenaufloesung |
| 359 | * liefert". Das trifft ausgerechnet auf den voreingestellten Dienst nicht zu. |
| 360 | * Die Kappung ist trotzdem richtig: Ein Bild mit 10000 Bildpunkten Kante |
| 361 | * sprengt die Projektdatei, und die gewonnene Genauigkeit braucht bei einem |
| 362 | * 1000-m-Ausschnitt niemand. Ein Bildpunkt Zeichenfehler kostet dort bereits 0,029 s |
| 363 | * Zwischenzeit beim Kfz und 0,286 s beim mobilitaetseingeschraenkten |
| 364 | * Fussgaenger. Die Bodenaufloesung muss der Anwendung deshalb angezeigt werden, |
| 365 | * bevor er auf einem solchen Bild eine Furt abgreift. |
| 366 | * |
| 367 | * Nach oben begrenzt sie ausserdem die Projektdatei - das Mosaik wird |
| 368 | * eingebettet, damit der Plan Jahre spaeter aus der Datei allein |
| 369 | * nachvollziehbar bleibt. |
| 370 | */ |
| 371 | export const MAX_MOSAIK_KANTE = 7000; |
| 372 | |
| 373 | /** Ein einzelner Abruf innerhalb des Mosaiks. */ |
| 374 | export interface Kachel { |
| 375 | readonly url: string; |
| 376 | /** Spalte im Mosaik, 0 ist die westlichste. */ |
| 377 | readonly spalte: number; |
| 378 | /** Zeile im Mosaik, 0 ist die oberste Bildzeile - also die noerdlichste. */ |
| 379 | readonly zeile: number; |
| 380 | /** Einsetzpunkt im Mosaik in Bildpunkten, von links. */ |
| 381 | readonly x: number; |
| 382 | /** Einsetzpunkt im Mosaik in Bildpunkten, von oben. */ |
| 383 | readonly y: number; |
| 384 | /** Kantenlaenge der Kachel in Bildpunkten - fuer jede Kachel dieselbe. */ |
| 385 | readonly kante: number; |
| 386 | } |
| 387 | |
| 388 | export interface Kachelplan { |
| 389 | /** Kantenlaenge des fertigen Mosaiks in Bildpunkten. */ |
| 390 | readonly bildkante: number; |
| 391 | readonly spalten: number; |
| 392 | readonly zeilen: number; |
| 393 | /** Bodenaufloesung des Mosaiks - in jeder Kachel dieselbe. */ |
| 394 | readonly meterJeBildpunkt: Meters; |
| 395 | /** Quellenangabe fuer den Plan. */ |
| 396 | readonly herkunft: string; |
| 397 | readonly kacheln: readonly Kachel[]; |
| 398 | } |
| 399 | |
| 400 | /** |
| 401 | * Zerlegt einen Ausschnitt in Kacheln, die einzeln abgerufen und zu einem Bild |
| 402 | * zusammengesetzt werden. |
| 403 | * |
| 404 | * WARUM UEBERHAUPT KACHELN |
| 405 | * |
| 406 | * Eine Ortsdurchfahrt mit mehreren Knotenpunkten braucht bis zu 2000 m |
| 407 | * Kantenlaenge. Als ein Abruf waeren das 7000 Bildpunkte - das rechnet ein |
| 408 | * Kartendienst entweder gar nicht oder so lange, dass der Abruf in eine |
| 409 | * Zeitgrenze laeuft. Mehrere kleine Abrufe liefern dasselbe Bild und lassen |
| 410 | * sich nebenbei einzeln wiederholen und im Fortschritt anzeigen. |
| 411 | * |
| 412 | * WARUM ALLE KACHELN GLEICH GROSS SIND |
| 413 | * |
| 414 | * Die Bildkante wird nach oben auf ein Vielfaches der Kachelkante angeglichen. |
| 415 | * Die Alternative - eine schmalere Randkachel - erzeugt zwei Fehler: eine |
| 416 | * sichtbare Nahtstelle und, ungleich schlimmer, einen ortsabhaengigen Massstab. |
| 417 | * Ein Raeumweg, der ueber eine solche Naht hinweg abgegriffen wird, waere je |
| 418 | * nach Lage im Bild zu lang oder zu kurz - und die Zwischenzeit damit falsch, |
| 419 | * ohne dass irgendetwas darauf hindeutete. Der Preis sind hoechstens |
| 420 | * (spalten - 1) Bildpunkte mehr als angefordert; das sind bei 7000 vier. |
| 421 | */ |
| 422 | export function baueKachelplan(anfrage: Kartenausschnitt): Kachelplan { |
| 423 | const { dienst, mitte } = anfrage; |
| 424 | const kantenlaenge = Math.max(20, Math.min(2000, anfrage.kantenlaenge)); |
| 425 | |
| 426 | // Die Bodenaufloesung des Dienstes ist die Untergrenze: Bayern liefert 40 cm, |
| 427 | // ein feiner angefordertes Bild waere nur leeres Vergroessern - es wiegt eine |
| 428 | // Genauigkeit vor, die in den Daten nicht steckt, und blaeht die Projektdatei. |
| 429 | const gewuenscht = Math.round( |
| 430 | Math.max(MIN_KANTE, Math.min(MAX_MOSAIK_KANTE, kantenlaenge / dienst.aufloesung)), |
| 431 | ); |
| 432 | |
| 433 | const kachelKante = Math.min(KACHEL_KANTE, dienst.maxKante); |
| 434 | const spalten = Math.max(1, Math.ceil(gewuenscht / kachelKante)); |
| 435 | const zeilen = spalten; // Der Ausschnitt ist quadratisch. |
| 436 | const kachelPixel = Math.min( |
| 437 | Math.ceil(gewuenscht / spalten), |
| 438 | // Das Aufrunden darf die Obergrenze des Mosaiks nicht ueberschreiten ... |
| 439 | Math.floor(MAX_MOSAIK_KANTE / spalten), |
| 440 | // ... und keine Kachel darf groesser werden, als der Dienst zulaesst. |
| 441 | kachelKante, |
| 442 | ); |
| 443 | const bildkante = kachelPixel * spalten; |
| 444 | |
| 445 | const ausschnitt = ausschnittUm(mitte, kantenlaenge); |
| 446 | const kacheln: Kachel[] = []; |
| 447 | for (let zeile = 0; zeile < zeilen; zeile += 1) { |
| 448 | for (let spalte = 0; spalte < spalten; spalte += 1) { |
| 449 | const teil = unterAusschnitt(ausschnitt, spalte, zeile, spalten, zeilen); |
| 450 | kacheln.push({ |
| 451 | url: abrufAdresse(dienst, teil, kachelPixel), |
| 452 | spalte, |
| 453 | zeile, |
| 454 | x: spalte * kachelPixel, |
| 455 | y: zeile * kachelPixel, |
| 456 | kante: kachelPixel, |
| 457 | }); |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | return { |
| 462 | bildkante, |
| 463 | spalten, |
| 464 | zeilen, |
| 465 | meterJeBildpunkt: kantenlaenge / bildkante, |
| 466 | herkunft: herkunftstext(dienst, mitte, kantenlaenge), |
| 467 | kacheln, |
| 468 | }; |
| 469 | } |