Hauptseite > Schwarzatal.org

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

  1. Jemand öffnet eine Wiki-Seite mit Karte.
  2. MediaWiki liefert die Seite plus das Gadget (weil in der Gadgets-Definition

registriert).

  1. Das Gadget erkennt das .wikimap-Element und lädt Leaflet von den

Server-Dateien nach.

  1. Es holt die Ortsdaten aus WikiMap-Orte.json.
  2. Es zeichnet die Karte mit allen Pins und zoomt auf den aktiven Ort.

Zusammenhang (Hierarchie)

  1. Fundament: die Server-Dateien (Leaflet selbst).
  2. Darauf sitzt der Gadget-Code (.js + .css),

der das Verhalten bestimmt.

  1. Der Code zieht seine Daten aus den zwei JSON-Seiten (Orte + Gemeinden).
  2. Einzelne Wiki-Seiten lösen mit einem einzeiligen <div>

die Karte aus.

  1. 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
  • Eigenschaftenname (Anzeigename) und seite

(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

  1. Die Gemeinde auf OpenStreetMap suchen und die

Relation-ID der Gemeindegrenze herausfinden (z.B. über die Suche oder die Detailansicht der Grenze).

  1. Den Umriss als GeoJSON beziehen. Zwei bewährte Wege:

eingeben, GeoJSON herunterladen. Einfachster Weg.

    • Alternativ: https://www.openstreetmap.org/api/0.6/relation/ID/full

aufrufen (das /full am Ende lädt die Koordinaten mit!) und die XML-Datei durch osmtogeojson in GeoJSON umwandeln.

  1. Wichtig: Eine XML ohne /full enthä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

  1. Alle Gemeinde-Dateien in einen Ordner namens gemeinden legen.
  2. Das Skript gemeinden-bauen.py (siehe unten) daneben legen.
  3. Ausführen: python3 gemeinden-bauen.py (unter Windows:

py gemeinden-bauen.py)

  1. Es entsteht die Datei WikiMap-Gemeinden.json.
  2. 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

MediaWiki:WikiMap-Orte.json

{
	"kategorien": {
		"bahnhof": "#c0392b",
		"schwarza": "#3498db"
	},