Technische Dokumentation: Kartenprojekt (WikiMap)
Das Kartenprojekt bindet interaktive Leaflet-Karten in Wiki-Seiten ein. Diese Dokumentation erklärt alle beteiligten Dateien, ihren Speicherort und ihre Funktion.
Die zwei Speicherorte
Das Projekt lebt an zwei getrennten Orten. Diese nicht zu verwechseln ist wichtig:
- Server (FTP) unter
/resources/lib/leaflet/– hier liegen
die fertigen Programmbibliotheken, die nicht bearbeitet werden.
- Wiki-Seiten im
MediaWiki:-Namespace – hier liegt der
eigene Code und die Daten, versioniert und bearbeitbar.
Server-Dateien (FTP)
Einmal hochgeladen, danach unverändert. Es sind die unveränderten Leaflet-Bibliotheksdateien:
| Datei | Funktion |
|---|---|
leaflet.js |
Die eigentliche Kartenbibliothek – zeichnet
Karten, reagiert auf Zoom und Klick, setzt Pins. |
leaflet.css |
Das Aussehen von Leaflet. |
Control.FullScreen.umd.js |
Das Vollbild-Plugin. |
Control.FullScreen.css |
Aussehen des Vollbild-Plugins. |
images/ |
Grafiken, die Leaflet benötigt (Marker-Schatten etc.). |
Diese Dateien sind „fremder" Code, der nur lokal gehostet wird, damit der Browser sie laden kann, ohne von externen Servern abhängig zu sein (DSGVO, keine CDN-Abhängigkeit).
Wiki-Seiten
Das eigentliche Projekt. Jede Seite hat eine klare Aufgabe:
| Seite | Funktion |
|---|---|
MediaWiki:Gadget-WikiMap.js |
Herzstück. Lädt Leaflet von
den Server-Dateien, definiert die fünf Kartenansichten, liest die Ortsdaten, zeichnet die Pins und hebt den aktiven hervor. Änderungen am Kartenverhalten passieren fast immer hier. |
MediaWiki:Gadget-WikiMap.css |
Bestimmt das Aussehen der Karten
– Größe des Kartenfensters und Optik der Pins (inkl. des pulsierenden roten aktiven Pins). |
MediaWiki:WikiMap-Orte.json |
Zentrale Datenquelle für alle
Stecknadeln. Jeder Ort steht hier genau einmal, mit Name, Koordinaten, Ziel-Wikiseite und Kategorie. Kernidee: eine Stelle für alle Pins, überall im Wiki automatisch aktuell. |
MediaWiki:WikiMap-Gemeinden.json |
Datenquelle für die
Gemeinde-Umrisse. Enthält die GeoJSON-Polygone aller Gemeinden; beim Klick auf einen Umriss springt man zur jeweiligen Gemeinde-Seite. |
MediaWiki:Gadgets-definition |
Registrierung. Eine Zeile
sagt MediaWiki, dass das Gadget existiert und aus welchen Seiten es besteht. Ohne diese Zeile passiert nichts. |
Einbindung auf einer Seite
Auf einer normalen Wiki-Seite genügt ein leeres Element:
<div class="wikimap" data-aktiv-ort="gaestehaus-mustertal"></div>
Das class="wikimap" ist das Signal „hier soll eine Karte hin".
Das data-aktiv-ort nennt die ID des hervorgehobenen Ortes. Die
Karte holt sich Position und alle anderen Pins selbst aus der zentralen JSON.
Ablauf beim Seitenaufruf
- Jemand öffnet eine Wiki-Seite mit Karte.
- MediaWiki liefert die Seite plus das Gadget (weil in der Gadgets-Definition
registriert).
- Das Gadget erkennt das
.wikimap-Element und lädt Leaflet von den
Server-Dateien nach.
- Es holt die Ortsdaten aus
WikiMap-Orte.json. - Es zeichnet die Karte mit allen Pins und zoomt auf den aktiven Ort.
Zusammenhang (Hierarchie)
- Fundament: die Server-Dateien (Leaflet selbst).
- Darauf sitzt der Gadget-Code (
.js+.css),
der das Verhalten bestimmt.
- Der Code zieht seine Daten aus den zwei JSON-Seiten (Orte + Gemeinden).
- Einzelne Wiki-Seiten lösen mit einem einzeiligen
<div>
die Karte aus.
- Die Gadgets-Definition ist der Schalter, der das Ganze aktiviert.
Gemeinde-Umrisse
Zusätzlich zu den Stecknadeln kann die Karte die Umrisse von Gemeinden anzeigen. Klickt man auf einen Umriss, springt man zur jeweiligen Gemeinde-Wikiseite. Diese Funktion ist offen angelegt – es können laufend neue Gemeinden ergänzt werden.
Wie es technisch funktioniert
Eine Gemeindegrenze ist eine Kette aus tausenden Koordinaten-Punkten, die
zusammen den Umriss ergeben. Diese Umrisse liegen gebündelt in der Wiki-Seite
MediaWiki:WikiMap-Gemeinden.json im sogenannten
GeoJSON-Format.
Jede Gemeinde ist dort ein „Feature" mit zwei Teilen:
- Geometrie – die Koordinatenkette des Umrisses
- Eigenschaften –
name(Anzeigename) undseite
(Ziel-Wikiseite beim Klick)
Damit eine Karte die Umrisse zeigt, muss das Element auf der Seite das Attribut
data-gemeinden="ja" tragen:
<div class="wikimap" data-gemeinden="ja"></div>
Eine neue Gemeinde hinzufügen
Der Vorgang besteht aus zwei Schritten: erst die Umrisse von OpenStreetMap holen, dann mit dem Skript zur fertigen Datei zusammenbauen.
Schritt 1: Umriss von OpenStreetMap holen
- Die Gemeinde auf OpenStreetMap suchen und die
Relation-ID der Gemeindegrenze herausfinden (z.B. über die Suche oder die Detailansicht der Grenze).
- Den Umriss als GeoJSON beziehen. Zwei bewährte Wege:
- polygons.openstreetmap.fr – Relation-ID
eingeben, GeoJSON herunterladen. Einfachster Weg.
- Alternativ:
https://www.openstreetmap.org/api/0.6/relation/ID/full
- Alternativ:
aufrufen (das /full am Ende lädt die Koordinaten mit!) und die
XML-Datei durch osmtogeojson in GeoJSON
umwandeln.
- Wichtig: Eine XML ohne
/fullenthält keine Koordinaten und
ist unbrauchbar.
Schritt 2: Datei richtig benennen
Die GeoJSON-Datei MUSS exakt so heißen wie die Gemeinde und die
zugehörige Wiki-Seite. Erster Buchstabe groß, Endung .geojson:
- Gemeinde „Ternitz", Wiki-Seite „Ternitz" → Datei
Ternitz.geojson - Gemeinde „Altendorf", Wiki-Seite „Altendorf" → Datei
Altendorf.geojson
Der Dateiname bestimmt automatisch den Anzeigenamen und das Klick-Ziel. Stimmt er nicht mit der Wiki-Seite überein, führt der Klick ins Leere.
Schritt 3: Zusammenbauen mit dem Skript
- Alle Gemeinde-Dateien in einen Ordner namens
gemeindenlegen. - Das Skript
gemeinden-bauen.py(siehe unten) daneben legen. - Ausführen:
python3 gemeinden-bauen.py(unter Windows:
py gemeinden-bauen.py)
- Es entsteht die Datei
WikiMap-Gemeinden.json. - Deren kompletten Inhalt in die Wiki-Seite
MediaWiki:WikiMap-Gemeinden.json kopieren und speichern.
Das Skript verarbeitet immer alle Dateien im Ordner. Zum Ergänzen also einfach die neue Datei dazulegen und das Skript erneut laufen lassen – die bestehenden Gemeinden bleiben erhalten.
Das Skript: gemeinden-bauen.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
gemeinden-bauen.py
==============================================================================
Baut aus einzelnen Gemeinde-GeoJSON-Dateien EINE WikiMap-Gemeinden.json,
die das WikiMap-Gadget einlesen kann.
WIE BENUTZEN
------------
1. Lege fuer jede Gemeinde eine GeoJSON-Datei in den Ordner "gemeinden".
Der Dateiname MUSS dem Gemeinde- und Wiki-Seitennamen entsprechen:
Ternitz.geojson -> Gemeinde "Ternitz", Wiki-Seite "Ternitz"
Altendorf.geojson -> Gemeinde "Altendorf", Wiki-Seite "Altendorf"
(Endung .geojson oder .json. Umlaute im Namen sind erlaubt.)
2. Skript ausfuehren:
python3 gemeinden-bauen.py
3. Es entsteht die Datei "WikiMap-Gemeinden.json". Deren kompletten Inhalt
in die Wiki-Seite MediaWiki:WikiMap-Gemeinden.json kopieren.
WAS DAS SKRIPT MACHT
--------------------
Jede Einzeldatei enthaelt nur nackte Geometrie (type + coordinates).
Das Gadget braucht aber pro Gemeinde zusaetzlich "name" und "seite".
Das Skript verpackt deshalb jede Geometrie in ein GeoJSON-"Feature" mit
diesen Eigenschaften und fuegt alle zu einer FeatureCollection zusammen.
Der Gemeindename wird aus dem Dateinamen abgeleitet (ohne Endung).
name und seite werden gleich gesetzt (Wiki-Seite = Gemeindename).
==============================================================================
"""
import json
import os
import sys
# Ordner, in dem die einzelnen Gemeinde-Dateien liegen.
QUELL_ORDNER = "gemeinden"
# Name der Ausgabedatei.
ZIEL_DATEI = "WikiMap-Gemeinden.json"
def lade_geometrie(pfad):
"""Liest eine GeoJSON-Datei und gibt deren Geometrie zurueck.
Akzeptiert zwei Formen:
a) Nackte Geometrie: {"type": "MultiPolygon", "coordinates": [...]}
b) Bereits ein Feature: {"type": "Feature", "geometry": {...}, ...}
In beiden Faellen wird das Geometrie-Objekt zurueckgegeben.
"""
with open(pfad, "r", encoding="utf-8") as f:
daten = json.load(f)
typ = daten.get("type")
if typ in ("Polygon", "MultiPolygon", "GeometryCollection"):
# Form a) nackte Geometrie
return daten
if typ == "Feature":
# Form b) bereits ein Feature -> nur die Geometrie nehmen
return daten.get("geometry")
if typ == "FeatureCollection":
# Ungewoehnlich: eine Sammlung in einer Einzeldatei.
# Wir nehmen die erste Geometrie.
feats = daten.get("features", [])
if feats:
return feats[0].get("geometry")
raise ValueError("FeatureCollection ohne features")
raise ValueError("Unbekannter GeoJSON-Typ: %r" % typ)
def main():
if not os.path.isdir(QUELL_ORDNER):
print("FEHLER: Ordner '%s' existiert nicht." % QUELL_ORDNER)
print("Lege ihn an und lege die Gemeinde-GeoJSON-Dateien hinein.")
sys.exit(1)
features = []
dateien = sorted(os.listdir(QUELL_ORDNER))
verarbeitet = 0
for dateiname in dateien:
if not (dateiname.endswith(".geojson") or dateiname.endswith(".json")):
continue
# Gemeindename = Dateiname ohne Endung.
name = os.path.splitext(dateiname)[0]
pfad = os.path.join(QUELL_ORDNER, dateiname)
try:
geometrie = lade_geometrie(pfad)
except Exception as e:
print(" UEBERSPRUNGEN: %s (%s)" % (dateiname, e))
continue
if not geometrie:
print(" UEBERSPRUNGEN: %s (keine Geometrie gefunden)" % dateiname)
continue
features.append({
"type": "Feature",
"properties": {
"name": name,
"seite": name
},
"geometry": geometrie
})
verarbeitet += 1
print(" + %s" % name)
if not features:
print("FEHLER: Keine gueltigen GeoJSON-Dateien gefunden.")
sys.exit(1)
sammlung = {
"type": "FeatureCollection",
"features": features
}
with open(ZIEL_DATEI, "w", encoding="utf-8") as f:
json.dump(sammlung, f, ensure_ascii=False, separators=(",", ":"))
print("")
print("Fertig: %d Gemeinde(n) in '%s' geschrieben." % (verarbeitet, ZIEL_DATEI))
print("Inhalt dieser Datei in MediaWiki:WikiMap-Gemeinden.json kopieren.")
if __name__ == "__main__":
main()
Format der fertigen Datei
Zur Veranschaulichung – so sieht eine fertige WikiMap-Gemeinden.json
mit zwei Gemeinden im Aufbau aus (Koordinaten gekürzt):
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "name": "Altendorf", "seite": "Altendorf" },
"geometry": { "type": "MultiPolygon", "coordinates": [ ... ] }
},
{
"type": "Feature",
"properties": { "name": "Ternitz", "seite": "Ternitz" },
"geometry": { "type": "MultiPolygon", "coordinates": [ ... ] }
}
]
}
Tipp: große Dateien
Detaillierte Umrisse können sehr groß werden. Wird die Karte dadurch träge, lassen sich die Polygone vorab vereinfachen – z.B. mit der „Simplify"-Funktion auf mapshaper.org oder direkt beim Export auf polygons.openstreetmap.fr. Eine leichte Vereinfachung verkleinert die Datei deutlich, ohne dass die Umrisse sichtbar eckig werden.
Kategorien und Filter
Jeder Ort in MediaWiki:WikiMap-Orte.json hat ein Feld
kategorie. Damit lassen sich Karten bauen, die nur bestimmte
Arten von Orten zeigen – zum Beispiel eine Karte, die ausschließlich
Hausärzte anzeigt.
Kategorie einem Ort zuweisen
In der Orte-Datei bekommt jeder Eintrag eine kategorie. Beispiel:
{
"id": "dr-mustermann",
"name": "Dr. Mustermann",
"seite": "Dr. Mustermann",
"lat": 47.7,
"lon": 16.0,
"kategorie": "hausarzt"
}
Alle Orte mit derselben Kategorie gehören zur selben Gruppe. Man kann beliebige
Kategorien erfinden – etwa hausarzt, bahnhof,
wasserfall, gasthaus.
Nur eine Kategorie auf einer Karte zeigen
Auf einer Themenseite (z.B. „Hausärzte") bindet man die Karte mit dem Attribut
data-filter ein:
<div class="wikimap" data-filter="hausarzt"></div>
Die Karte zeigt dann nur die Pins mit der Kategorie hausarzt.
Alle anderen Orte (Bahnhöfe, Wasserfälle usw.) bleiben unsichtbar.
Ohne data-filter zeigt die Karte alle Orte, unabhängig von
der Kategorie.
Mehrere Kategorien gleichzeitig zeigen
Im Filter können mehrere Kategorien kommagetrennt angegeben werden. Die Karte zeigt dann alle Orte, deren Kategorie eine der genannten ist – praktisch z.B. für „alle Essensmöglichkeiten in Gehweite":
<div class="wikimap" data-filter="asiatisch,gasthaus,imbiss"></div>
Leerzeichen nach den Kommas sind erlaubt. Zur Erinnerung: ganz ohne
data-filter werden weiterhin alle Orte gezeigt.
Wichtig: exakte Schreibweise
Die Kategorie im Filter muss zeichengenau mit der Kategorie in der Orte-Datei übereinstimmen – Groß-/Kleinschreibung zählt:
| In der JSON | Im Filter | Funktioniert? |
|---|---|---|
hausarzt |
data-filter="hausarzt" |
ja |
hausarzt |
data-filter="Hausarzt" |
nein |
Empfehlung: Kategorien durchgehend kleinschreiben, ohne Leerzeichen. So bleibt es einheitlich und fehlerfrei.
Kombination mit anderen Attributen
data-filter lässt sich mit den übrigen Attributen kombinieren.
Beispiel – nur Hausärzte zeigen und einen bestimmten hervorheben:
<div class="wikimap" data-filter="hausarzt" data-aktiv-ort="dr-mustermann"></div>
Kategorien eine Farbe zuweisen
{ "kategorien": { "bahnhof": "#c0392b", "schwarza": "#3498db" },