webmapping

Session 5: Vienna Sightseeing - GeoJSON, Layer & Leaflet Docs

Neues Repo wien erstellen, clonen und online bringen

Template auspacken, einchecken, pushen und online stellen

Wien Template erkunden

Das vorbereitete Template besteht aus index.html, main.css und main.js und beinhaltet (fast) nichts Neues. In index.html sind Font Awesome, die Leaflet Bibliothek, unser eigenes Stylesheet und Script eingebunden. Das Stylesheet main.css sieht etwas anders aus und bewirkt das neue Layout der Seite. In main.js finden wir den bereits bekannten Javascript-Code für eine Karte mit Marker und geöffnetem Popup. Nur der Hintergrundlayer ist anders und kommt von der frei verfügbaren Verwaltungsgrundkarte von Österreich (https://basemap.at)

L.tileLayer('https://mapsneu.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png', {
    maxZoom: 19,
    attribution: 'Datenquelle: <a href="https://www.basemap.at">basemap.at</a>'
}).addTo(map);

Ein Besuch bei der Verwaltungsgrundkarte von Österreich (https://basemap.at) zeigt, dass es dort noch mehr Hintergrundlayer gibt. Neben der von uns verwendeten Variant Grau gibt es noch Standard, Overlay, HighDPI, Oberfläche und Gelände als Raster-Layer. Wir werden später sehen, wie wir diese Layer einbauen können - für jetzt genügt uns die Variante Grau.

Leaflet Dokumentation erkunden

Nach dem Javascript Crashkurs der letzten Session, widmen wir uns heute der Leaflet Bibliothek. Hilfe zur Verwendung von Leaflet finden wir auf der Leaflet Homepage (https:leafletjs.com) unter Docs (https://leafletjs.com/reference.html). Dort finden wir die Leaflet API reference, die uns zeigt, was wir mit der Leaflet Bibliothek alles umsetzen können. Der Begriff API steht dabei für application programming interface und bedeutet übersetzt Programmierschnittstelle.

Ein Blick in den Javascript Code von main.js zeigt uns drei Aufrufe der Leaflet Bibliothek in der Variablen L: L.map, L.tileLayer und L.marker - wir verwenden die Leaflet Dokumentation um uns diese Features näher anzusehen

L.map - Karte initialisieren

Gleich unterhalb des Inhaltsverzeichnisses der Docs finden wir die Dokumentation für L.map - The central class of the API — it is used to create a map on a page and manipulate it.

L.tileLayer - Hintergrundkarte definieren

Mit L.tileLayer können wir eine Hintergrundkarte definieren. Versuchen wir unseren Eintrag aufzulösen:

L.tileLayer('https://mapsneu.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png', {
    maxZoom: 19,
    attribution: 'Hintergrundkarte: <a href="https://www.basemap.at">basemap.at</a>'
}).addTo(map);

L.marker

let marker = L.marker([stephansdom.lat, stephansdom.lng]).addTo(map);
marker.bindPopup(stephansdom.title).openPopup();

Der Code zum Erstellen des Markers ist ein gutes Beispiel für das sogenannte Chaining in Javascript, bei dem vereinfacht gesagt, eine Methode nach der anderen aufgerufen wird. Den Marker Code könnten wir ohne Variablendeklaration auch so schreiben:

L.marker([stephansdom.lat, stephansdom.lng])
    .addTo(map);
    .bindPopup(stephansdom.title)
    .openPopup();

Übersichtlicher ist aber unsere Variante und in der Dokumentation sind die einzelnen Methoden hier zu finden:

Damit sollten wir genügend Wissen über die Leaflet Dokumentation haben, um ein erstes neues Feature zu implementieren - fügt der Karte einen metrischen Maßstab hinzu

L.scale - Kartenmaßstab

Dokumentation ist hier zu finden: https://leafletjs.com/reference.html#control-scale

L.control.scale({
    imperial: false
}).addTo(map);

COMMIT: https://github.com/webmapping/wien/commit/43e7df79d7a96c654901dcce6279286d3f843f0e

Die kürzeste Variante bei den Optionen nützt den Umstand, dass per Default Einstellungen immer metrisch und imperial angezeigt werden. Es reicht damit, den englischen Maßstab auszuschalten. Ein Blick auf die Default-Werte lohnt sich immer!

Ein Real World Beispiel: Sehenswürdigkeiten Wien

Sidestep: Javascript Funktionen

Eine Funktion ist vereinfacht gesagt ein Codeblock mit einem Namen, der “irgend etwas tut” wenn wir ihn aufrufen. Beim Aufruf können wir Parameter für “das Tun” innerhalb der Funktion mitschicken und wenn der Codeblock abgearbeitet ist, kann die Funktion auch wieder einen einzelnen Wert, einen Array oder eine Objekt zurückliefern.

GeoJSON Daten asynchron mit einer Funktion laden

Übungsaufgabe: wir erstellen drei weitere Layer

Über die Suche bei https://www.data.gv.at/ suchen wir Layer mit JSON-Daten und implementieren sie mit den jeweiligen Vorgaben:

  1. Funktion loadLines
  2. Funktion loadStops
  3. Funktion loadZones

COMMIT: https://github.com/webmapping/wien/commit/e0f3fac7b0a8a8ee36722793f97c3efb0681fe26

Unsere Karte ist jetzt voll mit Markern, Linien und Flächen und es wird Zeit, etwas aufzuräumen

Code-Cosmetics bevor es weiter geht …

L.control.layers für Hintergrundlayer

Mit L.control.layers (https://leafletjs.com/reference.html#control-layers) können wir Hintergrundlayer und Overlays für unsere Karte erzeugen. Das Prinzip der Implementierung ist recht einfach - Hintergrundlayer und Overlays werden nacheinander als Javascript Objekte definiert deren Keys die Label der Einträge sind und die Values den jeweiligen Hintergrundlayer, bzw. das jeweilige Overlay bestimmen.

Im Code sehen wir zweimal .addTo(map), warum? Das erste addTo bewirkt, dass der Layer BasemapAT grau per Default angezeigt wird, das zweite addTo hängt die Layer control an die Karte. Die fertige Layer control wird Rechts Oben angezeigt und ermöglicht uns, zwischen den einzelnen Hintergrundlayer über Radio buttons zu wechseln.

L.control.layers für Overlays mit L.featureGroup

Overlays können als Objekt im zweiten Argument (bzw. Objekt) des Aufrufs von L.control.layers angegeben werden. Die Keys sind wieder Label für den Eintrag und die Values sind sogenannte L.featureGroup Objekte (siehe https://leafletjs.com/reference.html#featuregroup). Wir können sie uns als Container für die Geometrien vorstellen:

// Layer control
L.control.layers({
    // Baselayer Code
}, {
    "Sehenswürdigkeiten": L.featureGroup().addTo(map),
    "Vienna Sightseeing Linien": L.featureGroup().addTo(map),
    "Vienna Sightseeing Haltestellen": L.featureGroup().addTo(map),
    "Fußgängerzonen": L.featureGroup().addTo(map),
}).addTo(map);

COMMIT: https://github.com/webmapping/wien/commit/1a2f4af092d66eb4b449995308506f52c2448653

Das .addTo(map) beim Overlays-Objekt kommt jetzt vier Mal, weil wir alle Layer per Default anzeigen wollen. Was noch nicht funktioniert, ist das Ein-, und Ausschalten der Layer. Wir können die Kontrollkästchen zwar verändern, am Karteninhalt ändert das allerdings nichts. Warum? Weil die GeoJSON Objekte noch nicht in den dafür vorgesehen L.featureGroup Layern landen. Damit wir das noch schaffen, müssen wir vor der Layer control ein weiteres Objekt hinzufügen, in dem wir die L.featureGroup Einträge definieren. Dann können wir sie nicht nur in der Layer control verwenden, sondern können beim Hinzufügen der GeoJSON-Daten in den Funktionen, die Geometrie mit .addTo() auch an die Layer hängen und nicht an die map.

Wir definieren deshalb vor der Layer control ein Objekt overlays mit vier Einträgen. Die Keys benennen wir ähnlich wie unsere Funktionen, die Values sind identisch mit den Einträgen in der Layer control

// Overlays definieren
let overlays = {
    sights: L.featureGroup().addTo(map),
    lines: L.featureGroup().addTo(map),
    stops: L.featureGroup().addTo(map),
    zones: L.featureGroup().addTo(map),
}

COMMIT: https://github.com/webmapping/wien/commit/2053406b5fa302c4a5bb36d3402c80a624eb5710

Dann ersetzen wir die Einträge in der Layer control mit Verweisen auf unser overlays Objekt - aus L.featureGroup().addTo(map) bei den Sehenswürdigkeiten wird overlays.sights usw.

// Layer control
L.control.layers({
    // Baselayer Code
}, {
    "Sehenswürdigkeiten": overlays.sights,
    "Vienna Sightseeing Linien": overlays.lines,
    "Vienna Sightseeing Haltestellen": overlays.stops,
    "Fußgängerzonen": overlays.zones,
}).addTo(map);

COMMIT: https://github.com/webmapping/wien/commit/c9015544b93edc4671bbcb55e67f8004ede5eb75

Damit sind wir fast fertig, denn jetzt müssen wir die GeoJSON-Daten auch noch in die Overlays schreiben. In jeder der vier Funktionen ändern wir den .addTo(map) Aufruf zu .addTo(overlays.sights) usw.

L.geoJSON(jsondata, {
    attribution: "Datenquelle: <a href='https://data.wien.gv.at'>Stadt Wien</a>"
}).addTo(overlays.sights);

COMMIT: https://github.com/webmapping/wien/commit/730e9c177194d9da01edde97317b09273089482e

Leaflet.providers Plugin für Hintergrundkarten

Der Code für unseren Baselayer mit der basemap.at ist recht kompliziert und die Frage ist: geht das nicht einfacher? Die Antwort: Ja, mit einem Leaflet Plugin, das uns viele Hintergrundlayer zur Verfügung stellen kann - es heißt Leaflet.providers.

Auf der Leaflet Homepage finden wir es bei Plugins und dort bei Basemap providers (https://leafletjs.com/plugins.html#basemap-providers) / leaflet-providers. Das Demo (https://leaflet-extras.github.io/leaflet-providers/preview/) zeigt alle verfügbaren Layer, unter anderem auch die Layer der Verwaltungsgrundkarte Österreichs.

Hilfe zur Installation des Plugins finden wir beim Link leaflet-providers (https://github.com/leaflet-extras/leaflet-providers) unter dem Abschnitt CDN. Fügen wir deshalb in der index.html Datei unter dem Leaflet Skript, das Leaflet providers Skript hinzu:

<!-- Leaflet providers -->
<script src="https://unpkg.com/leaflet-providers@latest/leaflet-providers.js"></script>

Dann müssen wir nur noch den TileLayer Code in main.js mit einem Aufruf der Leaflet providers Bibliothek ersetzen - das Usage Beispiel zeigt wie. Wir kopieren den Code unterhalb des TileLayers und bekommen eine neue “Wasserfarben” Hintergrundkarte. Ändern wir das Leaflet providers Keyword Stadia.StamenWatercolor in BasemapAT.grau, sieht die Karte wieder aus wie zuvor.

L.tileLayer.provider('BasemapAT.grau').addTo(map);

Wie bereits erwähnt, gibt es die Verwaltungsgrundkarte Österreichs in mehreren Varianten, deshalb können wir im nächsten Schritt alle Varianten mit Hilfe des Leaflet providers Plugins als Layer control verfügbar machen.

// Layer control
L.control.layers({
    "BasemapAT": L.tileLayer.provider('BasemapAT.basemap'),
    "BasemapAT grau": L.tileLayer.provider('BasemapAT.grau').addTo(map),
    "BasemapAT HighDPI": L.tileLayer.provider('BasemapAT.highdpi'),
    "BasemapAT Orthofoto": L.tileLayer.provider('BasemapAT.orthofoto'),
    "BasemapAT Overlay": L.tileLayer.provider('BasemapAT.overlay'),
    "BasemapAT Relief": L.tileLayer.provider('BasemapAT.terrain'),
    "BasemapAT Oberfläche": L.tileLayer.provider('BasemapAT.surface'),
}).addTo(map);

COMMIT: https://github.com/webmapping/wien/commit/9623821f0c52095b96d1bda0b7c90c89c10448c6

Workload

  1. Layer: Funktion loadHotels

    COMMIT: <#>