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

<syntaxhighlight lang="python">

  1. !/usr/bin/env python3
  2. -*- 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

  1. Ordner, in dem die einzelnen Gemeinde-Dateien liegen.

QUELL_ORDNER = "gemeinden"

  1. 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()

</syntaxhighlight>

Format der fertigen Datei

Zur Veranschaulichung – so sieht eine fertige WikiMap-Gemeinden.json mit zwei Gemeinden im Aufbau aus (Koordinaten gekürzt):

<syntaxhighlight lang="json"> {

 "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": [ ... ] }
   }
 ]

} </syntaxhighlight>

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.

Wichtig: exakte Schreibweise

Die Kategorie im Filter muss zeichengenau mit der Kategorie in der Orte-Datei übereinstimmen:

In der JSON Im Filter Funktioniert?
hausarzt data-filter="hausarzt" ja
hausarzt data-filter="Hausarzt" nein (Großbuchstabe)
hausarzt data-filter="hausärzte" nein (Mehrzahl, Umlaut)

Empfehlung: Kategorien durchgehend kleinschreiben, in der Einzahl, ohne Umlaute und 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>