Projekte • 27. April 2023

Die Erstellung einer React-App mit MapComponents

Hi, ich bin Martin! Was nun folgt, könnte man als eine Geschichte der Selbstoptimierung bezeichnen. Seit Mai 2022 bin ich bei der WhereGroup als angehender Softwareentwickler in der Lehre und von Beginn an im MapComponents-Team involviert.

Kurz nach Beginn meiner Ausbildung kam ein weiterer Kollege ins Azubi-Team und ich bekam die Aufgabe, ihn in die MapComponents-Welt einzuführen. Eine Welt, die ich zu diesem Zeitpunkt selbst noch nicht ganz verstanden hatte. Ich nahm die Herausforderung an und da ich mit meinen Deutschkenntnissen noch nicht in der Lage bin, komplexe und abstrakte Dinge gut zu erklären dachte ich mir, dass der beste Weg, meinen Kollegen einzuarbeiten, "learning by doing" ist. Also beschlossen wir, ein Projekt zu starten, das es uns ermöglichen würde, verschiedene Funktionen der Bibliothek zu erforschen und gleichzeitig interessante Ergebnisse hervorzubringen – und das vor allem Spaß machen würde.

Nach einem intensiven Austausch über Interessen und Ideen, sind wir auf das Global Powerplants Database des World-Resources-Instituts gestoßen. Hierbei handelt es sich um eine große Datenbank mit Informationen über Kraftwerke auf der ganzen Welt. In Zeiten, in denen die Energieerzeugung im Mittelpunkt der globalen Debatte steht, schien uns die Erstellung einer interaktiven Karte, die einen Überblick über den Stand der weltweiten Energieerzeugung geben würde, sehr interessant. Also nahmen wir die Daten und machten uns an die Arbeit.

Erster Schritt: eine React-App erstellen

MapComponents ist ein OpenSource-Framework zur Erstellung von Geo-IT Anwendungen mit einer React-Bibliothek. Also fingen wir erstmal an, eine React-App zu erstellen. Das mag selbstverständlich erscheinen, aber es kann auch sehr schnell kompliziert werden. Es gibt verschiedene Methoden, so eine App zu erstellen, aber MapComponents bietet uns die Möglichkeit, in nur vier einfachen Schritten eine React-App zu erzeugen.

npx create-react-app {your-app-name} --template @mapcomponents/cra-template
cd {your-app-name}
yarn
yarn start

Der erste Befehl erstellt die App unter Verwendung eines Templates mit den grundlegenden MapComponents Einstellungen und die nächsten Befehle installieren die Abhängigkeiten und starten einen lokalen Entwicklungs-Server mit Live-Reloading. Das Ergebnis ist eine funktionierende App, die uns bereits eine Instanz von MapLibre-gl zur Verfügung stellt, mit der wir interagieren können. Einfach gesagt bedeutet das, dass unsere App bereits über eine Karte verfügt, auf der wir unsere Daten visualisieren können.

Zweiter Schritt: Laden der Daten

In diesem Schritt haben wir uns um das Laden und die Visualisierung der Daten gekümmert. Mit der Komponente MlGeoJsonLayer, die MapComponents zur Verfügung stellt, können GeoJSON Daten auf der Karte dargestellt werden.  Die Komponente - MlGeoJsonLayer (Ml am Anfang des Namens steht für "MapLibre") fügt einen Layer hinzu, dessen Quelle ein Objekt im GeoJSON-Format ist:

import { MlGeoJsonLayer } from "@mapcomponents/react-maplibre";
...
return(
	<>
		<MlGeoJsonLayer geojson={unsereGeojson} />
	</>
);

Obwohl die Komponente mehrere Konfigurationen empfangen kann, können die Daten mit nur einem Parameter (dem “geojson” Parameter) bereits dargestellt werden. Die Veränderbarkeit des JSON-Formats gibt unserer App eine große Flexibilität bei der Visualisierung, da das Quellobjekt des Layers sowohl im App-Code als auch im Backend, das uns die Daten zur Verfügung stellt, leicht manipuliert werden kann und die Visualisierung bei jeder Änderung des Objekts aktualisiert wird. In unserem Fall haben wir zum Beispiel eine kleine Suchfunktion eingebaut, die die Daten nach Ländern filtert, und so können wir die Visualisierung der Daten auf das Ergebnis der Suche beschränken.

Dritter Schritt: Funktionalitäten

Einer der großen Vorteile von MapComponents ist, dass die Anwendung sehr übersichtlich und schlank bleibt, da eine komplexe wiederkehrende Logik bereits in tieferen Ebenen implementiert ist. Das gibt uns die Freiheit uns auf die Funktionalitäten unserer Anwendung zu konzentrieren. Sie können eine extrem schnelle und einfache Datenvisualisierung mit minimalen default Parametern erhalten sowie gleichzeitig alle Optionen anpassen, die in der Mapbox Style Spezifikation definiert sind. Wir wollten eine Funktionalität hinzufügen, die es uns ermöglicht, auf die Informationen eines Kraftwerks zuzugreifen, indem wir sie einfach auf der Karte anklicken. Dazu haben wir im onClick-Attribut der MlGeoJsonLayer-Komponente einen Click-Handler definiert. So kann der geschriebene Code, das vom Benutzer ausgewählte Kraftwerk in eine State-Variable umschreiben, aus der eine andere Komponente die enthaltenen Informationen anzeigt.

Eine weitere Kernfunktionalität unserer App ist die Visualisierung der Daten nach eigenschaftsspezifischen Werten. In der default Konfiguration sehen alle Kraftwerke gleich aus. Eine Windkraftanlage in der patagonischen Steppe, die 3 Megawatt pro Jahr produziert, hatte das gleiche Gewicht auf der Karte wie ein Kernkraftwerk in Korea, das 5900 Megawatt pro Jahr erzeugt. Der Grund dafür ist einfach: Beide Anlagen sind Punkte auf der Karte, die aus Koordinatenpaaren bestehen. Um jedem Kraftwerk eine aussagekräftige Darstellung auf der Karte zu geben, haben wir auf die Eigenschaft "paint" der Komponente MlGeoJsonLayer zurückgegriffen, die paint Properties gemäß der Mapbox Style Spezifikation erwartet. Dort haben wir unsere Punkte so definiert, dass sie eine bestimmte Farbe haben, abhängig von der Ressource, die das jeweilige Werk nutzt, und eine bestimmte Größe, je nach seiner Produktionskapazität.

Die Möglichkeiten der Paint Property sind enorm. Man kann verschiedene Darstellungsvariablen wie Farbe, Umriss und Größe der Features einstellen sowie diese je nach Zoom-Stufe modifizieren. Da die Komponente MlGeojsonLayer auf Mapbox-gl basiert, sind hier alle Mapbox Style Ausdrücke gültig und, ein weiterer großer Vorteil ist, dass es eine umfangreiche Dokumentation und eine Menge Beispiele im Netz gibt.

paint={{
	"circle-color": {
		property: "primary_fuel",
		type: "categorical",
		default: "#fff",
		stops: references,
	},


	"circle-stroke-color": [
		"case",
		["in", ["get", "primary_fuel"],
			["literal", toShow]
		],
		[
			"case",
			["boolean", ["feature-state", "selected"], false],
			"black",
			"silver",
		],
		"rgba(0,0,0,0)",
	],
	"circle-stroke-width": [
		"case",
		["in", ["get", "primary_fuel"],
			["literal", toShow]
		],
		["case", ["boolean", ["feature-state", "selected"], false], 4, 0.2],
		0,
	],


	"circle-radius": [
		"case",
		["in", ["get", "primary_fuel"],
			["literal", toShow]
		],
		[
			"interpolate",
			["linear"],
			["get", "capacity_mw"],
			1,
			2,
			3000,
			10,
			22500,
			23,
		],
		0,
	],
}}

Schritt vier: Die Karte zum Leben erwecken

Zu diesem Zeitpunkt hatten wir eine Datenvisualisierung, die unseren Vorstellungen entsprach, aber unsere App glänzte immer noch nicht. In der Karte konnte zwar navigiert und gezoomt werden, aber sie sah immer noch zu sehr wie eine klassische Karte aus, die starr am Monitor abgebildet wird. Kurz gesagt, es fehlte ihr die Dynamik einer modernen, interaktiven Webkarte. Und hier kommt ein weiteres nützliches Werkzeug von MapComponents ins Spiel: die Hooks. Zusätzlich zu den Komponenten stellt MapComponents dem/der Benutzer*in eine Reihe von vordefinierten Funktionen zur Verfügung, die die Interaktion mit der Karte erleichtern und eine riesige Liste von Funktionalitäten bieten. Insbesondere der "useMap" Hook, der den Abruf einer MapLibre Instanz aus dem MapContext ermöglicht. In unserer Anwendung verwenden wir ihn, um automatisierte Kamerabewegungen hinzuzufügen (automatisches Hinein- und Herauszoomen, Bounding Box-Anpassungen bei Datenänderungen, " Flug"-Effekte bei der Bewegung eines Punkts zum anderen). Jetzt erwachte unsere App zum Leben und das Navigieren darin machte irgendwie "Spaß".

import { useMap } from "@mapcomponents/react-maplibre";
…
const mapHook = useMap({ mapId: "map_1" });
mapHook.map.flyTo({center: [7.0851268, 50.73884], zoom: 8})

In diesem Beispiel haben wir die Funktion "flyTo" aufgerufen, um die Ansicht des/der Benutzers/Benutzerin mit einer "fliegenden" Animation zu einem neuen Punkt zu bewegen. Die Parameter, die wir der Funktion übergeben haben, stellen die Zielkoordinaten und die gewünschte Zoomstufe dar.

Letzter Schritt: einige Erweiterungen

Da das ursprüngliche Ziel unserer Anwendung didaktisch war, wollten wir andere MapComponents-Funktionen einbauen, um an ihnen zu üben. Aus diesem Grund haben wir ein kleines Menü mit optionalen Hintergrundebenen in die App integriert. Im Nachhinein ist uns klar, dass nicht alle diese Ebenen für diese spezielle Anwendung am besten geeignet sind, aber sie vermitteln einen guten Eindruck von den Möglichkeiten, die MapComponents bietet, und von der Vielfalt der Quellen, die wir damit bearbeiten können. Diese Hintergrundlayer werden der Karte über WMS-Dienste zur Verfügung gestellt und der App über die Komponente MlWmsLayer hinzugefügt. Die Funktionsweise dieser Komponente ist der des Geojson-Layers sehr ähnlich, mit dem offensichtlichen Unterschied, dass die Quelle nicht ein Javascript-Objekt mit Vektordaten, sondern die URL des Dienstes ist, der Pixel-Grafiken ausliefert.

import { MlWmsLayer } from "@mapcomponents/react-maplibre";
...
return(
	<>
		<MlWmsLayer
		url= "https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png",
		urlParameters={{ layers: "" }} 
		/>
	</>
);

Und so ging unsere Übungs-App online...

In diesem Artikel habe ich die Geschichte der Anwendung in fünf Schritten erzählt. Vielleicht waren es weniger, vielleicht waren es mehr, es kommt darauf an, wie man zählt. Klar ist aber, wie einfach der Prozess ist.

Mit der modularen Struktur von MapComponents war es nur notwendig, einen Stein nach dem anderen hinzuzufügen, als ob man eine Mauer bauen würde, um eine funktionale Anwendung aus einem einzigen Datensatz zu erhalten. Das Ergebnis war so kompakt, dass wir uns entschlossen haben, die Anwendung als Teil der Beispielanwendungen in unserem Katalog zu veröffentlichen. Für uns, zwei Azubis, die gerade erst in die Welt des Programmierens eingestiegen waren, war die Veröffentlichung unserer Beispielanwendung ein schöner Erfolgsmoment und ein großer Impuls, weiter zu lernen. Für mich persönlich war es auch eine Herausforderung, einem Kollegen Wissen zu vermitteln von dem ich noch nicht wusste, dass ich es habe. Das Wissen hat sich zudem zweifelsohne verfestigt.

Die Geschichte beinhaltet meinen persönlichen Weg zwischen Herausforderung, Zweifeln, aber letztendlich einem schönen Erfolg. Und doch muss ich zugeben, dass das Erfolgsgeheimnis dieser Geschichte das Werkzeug ist, das wir verwendet haben. MapComponents macht es möglich, dass zwei Azubis mit anfänglicher Erfahrung in React, eine App dieses Umfangs erstellen können: Powerplants App im Katalog

Martin Alzueta

Martin Alzueta begann seine Ausbildung 2021 bei der WhereGroup und schloss diese 2024 ab. Von unserem Freiburger Standort aus ist er unter anderem an der Entwicklung von MapComponents und Projekten auf Basis dieser Bibliothek beteiligt.

Artikel teilen: