Minimalistische Offline-Leaflet-Karte mit Node.js & Hono
Anleitung für eine minimalistische, vollständig offline nutzbare Leaflet-Karte mit Node.js & Hono – inklusive GeoJSON, POIs und Ländergrenzen.
Für ein Projekt benötigte ich eine einfache, offlinefähige Leaflet-Karte, die nur Ländergrenzen und dynamisch injizierte Punkte anzeigt. Hier ist eine Schritt-für-Schritt-Anleitung, wie du so etwas mit Node.js und Hono umsetzen kannst.
Hono ist ein leichtgewichtiger Web-Framework für Node.js, das sich hervorragend für einfache APIs eignet. Wenn wir fertig sind, haben wir eine minimalistische Leaflet-Karte, die offline funktioniert und die Ländergrenzen sowie dynamisch geladene Punkte anzeigt.
Schritt 1: Projekt initialisieren
Erstelle ein neues Hono-Projekt:
1npm create hono@latestFolge den Anweisungen im Terminal und wähle die folgenden Optionen:
1create-hono version 0.19.2
2✔ Target directory leaflet-hono
3✔ Which template do you want to use? nodejs
4✔ Do you want to install project dependencies? Yes
5✔ Which package manager do you want to use? npm
6✔ Cloning the template
7✔ Installing project dependencies
8🎉 Copied project files
9Get started with: cd leaflet-honoWir installieren hier auch Leaflet um aus dem node_modules Verzeichnis auf die Leaflet-Bibliothek zugreifen zu können.
1cd leaflet-hono
2npm install leafletSchritt 2: Projektstruktur anlegen
leaflet-hono/
├── data/
├── public/
│ ├── leaflet/
│ │ ├── images/ ( << siehe unten für Details >> )
│ │ ├── leaflet.css
│ │ ├── leaflet.js
│ │ └── leaflet.js.map
├── src/
│ ├── index.ts- data/ enthält heruntergeladene GeoJSON-Dateien (z. B. Natural Earth 50m).
- public/ enthält Frontend-Dateien.
- server.js enthält den Hono-Server und API-Endpunkte.
public/leaflet/imagesOrdner mit Leaflet-Bildern aus dem node_modules/leaflet/dist/images Verzeichnis)
Kopiere die Leaflet Dateien aus node_modules/leaflet/dist/ in den public/leaflet/ Ordner:
1mkdir -p public/leaflet/images
2cp node_modules/leaflet/dist/images/* public/leaflet/images/
3cp node_modules/leaflet/dist/leaflet.css public/leaflet/
4cp node_modules/leaflet/dist/leaflet.js public/leaflet/
5cp node_modules/leaflet/dist/leaflet.js.map public/leaflet/Schritt 3: App starten
Bevor wir weiter machen, stellen wir sicher, dass wir unsere App einmal starten können. Starte dazu den Hono-Server:
1npm run devWenn alles richtig eingerichtet ist, solltest du die Standard-Hono-Seite unter http://localhost:3000 sehen.
Schritt 4: src/index.ts
Nun erweitern wir die src/index.ts Datei, um die Daten aus dem public und data Ordner bereitzustellen.
1import { serve } from "@hono/node-server";
2import { Hono } from "hono";
3import { serveStatic } from "@hono/node-server/serve-static";
4
5const app = new Hono();
6
7// Serve static files from 'public'
8app.use("/public/*", serveStatic({ root: "./" }));
9
10// Serve static files from 'data'
11app.use("/data/*", serveStatic({ root: "./" }));
12
13app.get("/", (c) => {
14 return c.text("Hello Hono!");
15});
16
17serve(
18 {
19 fetch: app.fetch,
20 port: 3000,
21 },
22 (info) => {
23 console.log(`Server is running on http://localhost:${info.port}`);
24 },
25);Zum Testen erstellen wir eine einfache Test-Datei, die wir später in der Karte verwenden werden. Erstelle dazu eine Datei data/poi.geo.json mit folgendem Inhalt:
1{
2 "type": "FeatureCollection",
3 "features": [
4 {
5 "type": "Feature",
6 "properties": { "name": "Den Haag" },
7 "geometry": {
8 "type": "Point",
9 "coordinates": [4.3007, 52.0705]
10 }
11 },
12 {
13 "type": "Feature",
14 "properties": { "name": "Utrecht" },
15 "geometry": {
16 "type": "Point",
17 "coordinates": [5.09, 52.0907]
18 }
19 }
20 ]
21}Wenn nun <https://localhost:3000/data/poi.geo.json> aufgerufen wird, sollte die GeoJSON-Datei angezeigt werden.
Schritt 5: public/index.html und public/styles.css
In die index.html Datei fügen wir die grundlegende Struktur für die Leaflet-Karte ein. Erstelle die Datei public/index.html mit folgendem Inhalt:
1<!DOCTYPE html>
2<html lang="de">
3 <head>
4 <meta charset="UTF-8" />
5 <title>Minimal Leaflet Karte</title>
6 <link rel="stylesheet" href="styles.css" />
7 <link rel="stylesheet" href="/public/leaflet/leaflet.css" crossorigin="" />
8 </head>
9 <body>
10 <header>
11 <h1>Leaflet Demo Map</h1>
12 <p class="subtitle">A simple demonstration of a Leaflet map</p>
13 </header>
14
15 <div id="map"></div>
16 <script src="/public/leaflet/leaflet.js" crossorigin=""></script>
17 <script src="map.js"></script>
18 </body>
19</html>Um das nicht ganz so langweilig zu gestalten, fügen wir noch etwas CSS hinzu. Erstelle die Datei public/styles.css mit folgendem Inhalt:
1html,
2body {
3 margin: 0;
4 padding: 0;
5 width: 100%;
6 height: 100%;
7}
8body {
9 font-family: Arial, sans-serif;
10 height: 100vh;
11 box-sizing: border-box;
12 display: flex;
13 flex-direction: column;
14}
15
16h1,
17.subtitle {
18 margin: 0 0 0.5em 0;
19 text-align: center;
20}
21.subtitle {
22 font-size: 1.2em;
23 color: #666;
24 margin-bottom: 1em;
25}
26#map {
27 flex: 1;
28 margin: 0 2vw 2vw 2vw; /* margin around the map */
29 background-color: #f0f0f0;
30 border: 1px solid #ccc;
31 border-radius: 8px;
32}Schritt 6: public/map.js
Was nun noch fehlt, ist die Logik, um die Karte zu initialisieren und die GeoJSON-Daten zu laden. Erstelle die Datei public/map.js mit folgendem Inhalt:
1const map = L.map("map").setView([52.0705, 4.707], 11);
2
3L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
4 maxZoom: 19,
5}).addTo(map);
6
7fetch("/data/poi.geo.json")
8 .then((response) => response.json())
9 .then((data) => {
10 L.geoJSON(data).addTo(map);
11 });Hiermit initialisieren wir die Karte auf den Koordinaten zwischen Den Haag und Utrecht und laden die GeoJSON-Daten von unserem Hono-Server. Die Punkte werden dann als Layer zur Karte hinzugefügt.
Wenn alles geklappt hat, sollte es ungefähr so aussehen:
Schritt 7: OSM ersetzen und Ländergrenzen hinzufügen
Da wir aber eine offlinefähige Karte erstellen wollen, müssen wir die Ländergrenzen und andere Geodaten lokal bereitstellen. Wir verwenden dafür GeoJSON-Dateien von Natural Earth die sich gut für kleinere Karten eignen.
Um die Ländergrenzen herunterzuladen, navigiere zu geojson-maps.kyd.au, wähle “Medium Resolution” und aktiviere die Checkbox “All”. Die mittlere Auflösung ist für unsere Zwecke ausreichend und hat mit nur 4,5 MB eine handhabbare Größe. Die Option “All” stellt sicher, dass alle Ländergrenzen enthalten sind.
Klicke anschließend auf “Build Custom GeoJSON” in der rechten unteren Ecke, um die Datei herunterzuladen. Diese Datei enthält alle Ländergrenzen im GeoJSON-Format, das mit Leaflet kompatibel ist. Speichere die Datei im data-Ordner unter dem Namen vm_medium_countries.geo.json.
Anschließend erweitern wir die public/map.js Datei, um die Ländergrenzen zu laden und anzuzeigen:
1const map = L.map("map").setView([52.0705, 4.707], 5);
2L.tileLayer("", {}).addTo(map);
3
4fetch("/data/vm_medium_countries.geo.json")
5 .then((response) => response.json())
6 .then((data) => {
7 L.geoJSON(data, {
8 style: (feature) => ({
9 color: feature.properties.color || "#333",
10 weight: 1,
11 opacity: 1,
12 fillColor: feature.properties.fillColor || "#eee",
13 fillOpacity: 1,
14 }),
15 }).addTo(map);
16 });Der Code initialisiert eine Leaflet-Karte ohne Hintergrund-Layer (also ohne Basiskarte) und zeigt die Ländergrenzen aus einer lokalen GeoJSON-Datei an.
- Die Karte wird auf Europa zentriert und auf Zoomstufe 5 gesetzt.
- Es wird ein leerer Tile-Layer hinzugefügt, damit Leaflet korrekt funktioniert, aber keine Online-Kartenkacheln geladen werden.
- Die Ländergrenzen werden per
fetchaus der Datei/data/vm_medium_countries.geo.jsongeladen. - Mit
L.geoJSONwerden die Länder als Layer zur Karte hinzugefügt. Die Darstellung (Linienfarbe, Füllfarbe) kann für jedes Land individuell über die Eigenschaften im GeoJSON gesteuert werden (feature.properties.colorundfeature.properties.fillColor). Gibt es keine Angabe, werden Standardwerte verwendet.
Um das ganze noch etwas abzurunden können wir die Hintergrundfarbe der Karte in der public/styles.css Datei anpassen:
1#map {
2 flex: 1;
3 margin: 0 2vw 2vw 2vw; /* margin around the map */
4 background-color: #87ceeb; /* light blue, resembling the sea */
5 border: 1px solid #ccc;
6 border-radius: 8px;
7}Damit erhält die Karte eine hellblaue Hintergrundfarbe, die visuell das Meer andeuten soll. Die Länder werden in einem neutralen Grau dargestellt.
Schritt 8: Ländergrenzen und POI kombinieren
Nun wollen wir noch die POI-Punkte aus der data/poi.geo.json Datei auf der Karte anzeigen. Dazu erweitern wir die public/map.js Datei:
1fetch("/data/poi.geo.json")
2 .then((response) => response.json())
3 .then((data) => {
4 L.geoJSON(data, {
5 onEachFeature: (feature, layer) => {
6 layer.bindPopup(feature.properties.name);
7 },
8 }).addTo(map);
9 });Nach dem Neuladen der Seite sollten nun die POI-Punkte (Den Haag und Utrecht) auf der Karte angezeigt werden. Wenn du auf einen Punkt klickst, öffnet sich ein Popup mit dem Namen des Ortes.
Bonus: Fancy Leaflet Funktionen
Um die Karte ein wenig interaktiver zu gestalten, können wir noch einige zusätzliche Features hinzufügen. Leaflet bietet viele Möglichkeiten, um die Benutzererfahrung zu verbessern. Ersetze die public/map.js Datei mit folgendem Inhalt:
1const map = L.map("map").setView([52.0705, 4.707], 6);
2// Leaflet benötigt mindestens einen Tile-Layer, auch wenn er leer ist.
3// Deshalb wird hier ein leerer Tile-Layer eingebunden, um die Engine korrekt zu initialisieren.
4L.tileLayer("", {}).addTo(map);
5
6// Highlight-Funktionen
7function highlightFeature(e) {
8 const layer = e.target;
9 layer.setStyle({
10 weight: 2,
11 color: "#ff0000",
12 fillOpacity: 0.7,
13 });
14 layer.bringToFront();
15}
16
17function resetHighlight(e) {
18 geojson.resetStyle(e.target);
19}
20
21// Country boundaries with outlines and optional fill
22let geojson; // Global variable to hold the geojson layer
23fetch("/data/vm_medium_countries.geo.json")
24 .then((res) => res.json())
25 .then((data) => {
26 geojson = L.geoJSON(data, {
27 style: (feature) => ({
28 color: feature.properties.color || "#333",
29 weight: 1,
30 opacity: 1,
31 fillColor: feature.properties.fillColor || "#eee",
32 fillOpacity: 1,
33 }),
34 onEachFeature: (feature, layer) => {
35 layer.bindPopup(feature.properties.name);
36 layer.on({
37 mouseover: highlightFeature,
38 mouseout: resetHighlight,
39 });
40 },
41 }).addTo(map);
42 })
43 .catch(console.error);
44
45// POI data with popups
46fetch("/data/poi.geo.json")
47 .then((response) => response.json())
48 .then((data) => {
49 L.geoJSON(data, {
50 onEachFeature: (feature, layer) => {
51 layer.bindPopup(feature.properties.name);
52 },
53 }).addTo(map);
54 });Dieser Code fügt folgende Features hinzu:
- Hover-Effekte: Länder werden hervorgehoben, wenn der Mauszeiger darüber schwebt.
- Popups: Beim Klicken auf Länder oder POI-Punkte öffnet sich ein Popup mit dem Namen.
- Popup für Länder: Beim Klicken auf ein Land öffnet sich ein Popup mit dem Namen des Landes.
- Popup für POI-Punkte: Beim Klicken auf einen POI-Punkt öffnet sich ein Popup mit dem Namen des Ortes.
🧭 Fazit
Mit weniger als 100 Zeilen Code lässt sich eine minimalistische, offlinefähige Leaflet-Karte mit Hono realisieren. Ideal für Prototyping oder einfache Anwendungen – ohne externe Abhängigkeiten und voll anpassbar.
Die Karte kann leicht erweitert werden, um weitere Features hinzuzufügen, wie z. B. Marker, Polygone oder andere GeoJSON-Daten. Leaflet bietet eine Vielzahl von Möglichkeiten, um Karten interaktiv und ansprechend zu gestalten.
Das Projekt in Version 1 kannst du hier herunterladen: https://codeberg.org/cblte/leaflet-hono-offline/releases/tag/v1




