MediaWiki:Gadget-WikiMap.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| (25 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 20: | Zeile 20: | ||
* data-Attribute: | * data-Attribute: | ||
* data-aktiv-ort ID des hervorgehobenen Ortes (rot, zentriert, Popup auf) | * data-aktiv-ort ID des hervorgehobenen Ortes (rot, zentriert, Popup auf) | ||
* data-filter nur Orte dieser "kategorie" zeigen | * data-filter nur Orte dieser "kategorie" zeigen. Mehrere Kategorien | ||
* kommagetrennt moeglich, z.B. "asiatisch,gasthaus,imbiss" | |||
* data-pin Ad-hoc-Pin: "lat,lon" zeigt GENAU EINEN Pin an diesen | |||
* Koordinaten und KEINE Orte aus der zentralen JSON. | |||
* Fuer Veranstaltungsseiten (Eventform) gedacht. | |||
* data-pin-name Optional zum Pin: Text im Popup. | |||
* data-gemeinden "ja" => Gemeinde-Umrisse einblenden (Klick => Seite) | * data-gemeinden "ja" => Gemeinde-Umrisse einblenden (Klick => Seite) | ||
* data-nur-aktiv "ja" => nur den aktiven Ort zeigen, alle anderen Pins | |||
* ausblenden (braucht data-aktiv-ort) | |||
* data-layer Start-Ansicht per Menü-Name, z.B. "Zug" oder | |||
* "Zug + Luftbild". Unbekannter Name => normaler Default. | |||
* data-hoehe Pixel-Höhe der Karte (optional, sonst CSS-Default) | * data-hoehe Pixel-Höhe der Karte (optional, sonst CSS-Default) | ||
* | |||
* ORTE-FELDER (WikiMap-Orte.json) | |||
* ------------------------------- | |||
* id Pflicht. Anzeigename im Popup UND Ziel-Wikiseite beim Klick. | |||
* geo Koordinaten als String "lat,lon" – direkt aus OSM.org oder | |||
* OrganicMaps kopierbar, Leerzeichen nach dem Komma erlaubt. | |||
* Beispiel: "47.7073509,15.9933643" | |||
* lat, lon Alternative zu geo: Koordinaten als zwei Zahlenfelder. | |||
* kategorie Optional. Für Filter und Farben. | |||
* seite Optional. Übersteuert das Klick-Ziel. Wiki-Seitenname ODER | |||
* externe Adresse (beginnend mit http:// oder https://). | |||
* | * | ||
* ABHÄNGIGKEIT: Leaflet 1.9.x + Leaflet.fullscreen. Dieses Gadget lädt sie | * ABHÄNGIGKEIT: Leaflet 1.9.x + Leaflet.fullscreen. Dieses Gadget lädt sie | ||
| Zeile 37: | Zeile 57: | ||
orteSeite: 'MediaWiki:WikiMap-Orte.json', | orteSeite: 'MediaWiki:WikiMap-Orte.json', | ||
gemeindenSeite: 'MediaWiki:WikiMap-Gemeinden.json', | gemeindenSeite: 'MediaWiki:WikiMap-Gemeinden.json', | ||
defaultCenter: [ 47. | defaultCenter: [ 47.6, 16.1 ], | ||
defaultZoom: 7, | defaultZoom: 7, | ||
focusZoom: 14 | focusZoom: 14 | ||
| Zeile 70: | Zeile 90: | ||
/* ==================================================================== | /* ==================================================================== | ||
* 2) HINTERGRUNDKARTEN | * 2) HINTERGRUNDKARTEN | ||
* | |||
* basemap.at: seit 2023 neue Service-URLs unter mapsneu.wien.gv.at. | |||
* Quelle: https://cdn.basemap.at/basemap.at_URL_Umstellung_2023.pdf | |||
* | |||
* Tracestrack: API-Schlüssel auf Referer schwarzatal.org beschränkt. | |||
* Quelle: https://console.tracestrack.com/ | |||
* ==================================================================== */ | * ==================================================================== */ | ||
function baueLayer() { | function baueLayer() { | ||
var osmStandard = L.tileLayer( | |||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', | |||
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' } | |||
); | |||
var osmRelief = L.tileLayer( | var osmRelief = L.tileLayer( | ||
'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', | 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', | ||
| Zeile 80: | Zeile 110: | ||
} | } | ||
); | ); | ||
var | var tracestrack = L.tileLayer( | ||
'https:// | 'https://tile.tracestrack.com/topo__/{z}/{x}/{y}.png' + | ||
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' } | '?key=bc0464a46d41b1107569bd81112f05ab', | ||
{ | |||
maxZoom: 19, | |||
attribution: '© <a href="https://www.tracestrack.com/">Tracestrack</a>, ' + | |||
'© OpenStreetMap-Mitwirkende' | |||
} | |||
); | ); | ||
var luftbild = L.tileLayer( | var luftbild = L.tileLayer( | ||
'https:// | 'https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/' + | ||
'normal/google3857/{z}/{y}/{x}.jpeg', | 'normal/google3857/{z}/{y}/{x}.jpeg', | ||
{ | { | ||
maxZoom: 19, | maxZoom: 19, | ||
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | ||
} | } | ||
); | ); | ||
var overlay = L.tileLayer( | var overlay = L.tileLayer( | ||
'https:// | 'https://mapsneu.wien.gv.at/basemap/bmapoverlay/' + | ||
'normal/google3857/{z}/{y}/{x}.png', | 'normal/google3857/{z}/{y}/{x}.png', | ||
{ | { | ||
maxZoom: 19, | maxZoom: 19, | ||
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | ||
} | } | ||
); | ); | ||
var gemischt = L.layerGroup( [ | // Zweite Luftbild-Instanz für die Gruppe, damit Layer-Wechsel nicht | ||
// zwischen Gruppe und Solo-Luftbild kollidieren (Race Condition). | |||
var luftbildKopie = L.tileLayer( | |||
'https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/' + | |||
'normal/google3857/{z}/{y}/{x}.jpeg', | |||
{ | |||
maxZoom: 19, | |||
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | |||
} | |||
); | |||
var gemischt = L.layerGroup( [ luftbildKopie, overlay ] ); | |||
// OpenRailwayMap ist ein transparentes Gleis-Overlay und braucht eine | |||
// Basiskarte darunter. Eigene OSM-Instanz für die Gruppe (gleiches | |||
// Race-Condition-Muster wie beim Luftbild). Der Tileserver liefert | |||
// 512px-Kacheln, daher tileSize/zoomOffset. Die a/b/c-Subdomains sind | |||
// deprecated, daher URL ohne {s}. | |||
var osmFuerBahn = L.tileLayer( | |||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', | |||
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' } | |||
); | |||
var bahnOverlay = L.tileLayer( | |||
'https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', | |||
{ | |||
maxZoom: 19, | |||
tileSize: 512, | |||
zoomOffset: -1, | |||
attribution: 'Overlay: <a href="https://www.openrailwaymap.org/">OpenRailwayMap</a> (CC-BY-SA 2.0)' | |||
} | |||
); | |||
var eisenbahn = L.layerGroup( [ osmFuerBahn, bahnOverlay ] ); | |||
// Hilfsfunktionen für frische Instanzen: jede LayerGroup braucht | |||
// EIGENE Tile-Layer, sonst kollidieren die Gruppen beim Umschalten | |||
// (Race Condition, siehe luftbildKopie oben). | |||
function neuesLuftbild() { | |||
return L.tileLayer( | |||
'https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/' + | |||
'normal/google3857/{z}/{y}/{x}.jpeg', | |||
{ | |||
maxZoom: 19, | |||
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | |||
} | |||
); | |||
} | |||
function neuesBmapOverlay() { | |||
return L.tileLayer( | |||
'https://mapsneu.wien.gv.at/basemap/bmapoverlay/' + | |||
'normal/google3857/{z}/{y}/{x}.png', | |||
{ | |||
maxZoom: 19, | |||
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>' | |||
} | |||
); | |||
} | |||
function neuesBahnOverlay() { | |||
return L.tileLayer( | |||
'https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png', | |||
{ | |||
maxZoom: 19, | |||
tileSize: 512, | |||
zoomOffset: -1, | |||
attribution: 'Overlay: <a href="https://www.openrailwaymap.org/">OpenRailwayMap</a> (CC-BY-SA 2.0)' | |||
} | |||
); | |||
} | |||
function neuesOsm() { | |||
return L.tileLayer( | |||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', | |||
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' } | |||
); | |||
} | |||
function neuesOpenTopo() { | |||
return L.tileLayer( | |||
'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', | |||
{ | |||
maxZoom: 17, | |||
attribution: 'Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | ' + | |||
'Darstellung: © OpenTopoMap (CC-BY-SA)' | |||
} | |||
); | |||
} | |||
function neuesTracestrack() { | |||
return L.tileLayer( | |||
'https://tile.tracestrack.com/topo__/{z}/{x}/{y}.png' + | |||
'?key=bc0464a46d41b1107569bd81112f05ab', | |||
{ | |||
maxZoom: 19, | |||
attribution: '© <a href="https://www.tracestrack.com/">Tracestrack</a>, ' + | |||
'© OpenStreetMap-Mitwirkende' | |||
} | |||
); | |||
} | |||
// Zug-Kombis: jeweils Basiskarte(n) unten, Gleise obendrauf. | |||
var zugLuftbildKarte = L.layerGroup( | |||
[ neuesLuftbild(), neuesBmapOverlay(), neuesBahnOverlay() ] | |||
); | |||
var zugLuftbild = L.layerGroup( [ neuesLuftbild(), neuesBahnOverlay() ] ); | |||
var zugKarte = eisenbahn; // OSM + Gleise (bereits oben gebaut) | |||
var zugTopo = L.layerGroup( [ neuesOpenTopo(), neuesBahnOverlay() ] ); | |||
var zugTopo2 = L.layerGroup( [ neuesTracestrack(), neuesBahnOverlay() ] ); | |||
// Bus: ÖPNVKarte (memomaps.de) ist eine VOLLSTÄNDIGE Karte mit | |||
// Buslinien/Haltestellen, KEIN transparentes Overlay. Deshalb gibt | |||
// es keine Bus+Luftbild-Kombis. Hinweis: Datenpflege des Projekts | |||
// ist laut OSM-Wiki unregelmäßig. | |||
var bus = L.tileLayer( | |||
'https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png', | |||
{ | |||
maxZoom: 18, | |||
attribution: 'Karte: <a href="https://memomaps.de/">memomaps.de</a> ' + | |||
'(CC-BY-SA), Daten: © OpenStreetMap-Mitwirkende' | |||
} | |||
); | |||
return { | return { | ||
basis: { | basis: { | ||
' | 'Luftbild + Karte': gemischt, | ||
' | 'Luftbild': luftbild, | ||
'Luftbild | 'Karte': osmStandard, | ||
'Luftbild + Karte': | 'Topokarte': osmRelief, | ||
'Topokarte2': tracestrack, | |||
'Zug + Luftbild + Karte': zugLuftbildKarte, | |||
'Zug + Luftbild': zugLuftbild, | |||
'Zug + Karte': zugKarte, | |||
'Zug + Topokarte': zugTopo, | |||
'Zug + Topokarte2': zugTopo2, | |||
'Bus': bus | |||
}, | }, | ||
standard: | standard: gemischt | ||
}; | }; | ||
} | } | ||
| Zeile 150: | Zeile 305: | ||
* 5) EINE EINZELNE KARTE BAUEN | * 5) EINE EINZELNE KARTE BAUEN | ||
* ==================================================================== */ | * ==================================================================== */ | ||
function baueKarte( container, orte, gemeinden ) { | function baueKarte( container, orte, gemeinden, kategorien ) { | ||
var aktivId = container.getAttribute( 'data-aktiv-ort' ) || null; | var aktivId = container.getAttribute( 'data-aktiv-ort' ) || null; | ||
var | |||
// Ad-hoc-Pin-Modus: data-pin="lat,lon" zeigt GENAU EINEN Pin an | |||
// diesen Koordinaten und KEINE Orte aus der zentralen JSON. | |||
// Gedacht z.B. für Veranstaltungsseiten (Eventform), deren Ort | |||
// nicht in WikiMap-Orte.json gepflegt wird. | |||
// Optional: data-pin-name für den Popup-Text. | |||
var pinAttr = container.getAttribute( 'data-pin' ) || ''; | |||
var pinKoord = null; | |||
if ( pinAttr ) { | |||
var pinTeile = pinAttr.split( ',' ); | |||
if ( pinTeile.length === 2 ) { | |||
var pLat = parseFloat( pinTeile[ 0 ] ); | |||
var pLon = parseFloat( pinTeile[ 1 ] ); | |||
if ( !isNaN( pLat ) && !isNaN( pLon ) ) { | |||
pinKoord = [ pLat, pLon ]; | |||
} | |||
} | |||
} | |||
var pinName = container.getAttribute( 'data-pin-name' ) || ''; | |||
// Nur den aktiven Ort zeigen (alle anderen Pins ausblenden). | |||
var nurAktiv = container.getAttribute( 'data-nur-aktiv' ) === 'ja'; | |||
// Start-Layer per Menü-Name wählen, z.B. data-layer="Eisenbahnkarte". | |||
// Unbekannter Name => normaler Default. | |||
var layerWunsch = container.getAttribute( 'data-layer' ) || ''; | |||
// Filter: eine oder mehrere Kategorien, kommagetrennt. | |||
// "asiatisch,gasthaus" => nur Orte, deren kategorie EINE davon ist. | |||
// Leer/nicht gesetzt => alle Orte. | |||
var filterAttr = container.getAttribute( 'data-filter' ) || ''; | |||
var filterListe = filterAttr | |||
.split( ',' ) | |||
.map( function ( s ) { return s.trim(); } ) | |||
.filter( function ( s ) { return s.length > 0; } ); | |||
var hoehe = container.getAttribute( 'data-hoehe' ); | var hoehe = container.getAttribute( 'data-hoehe' ); | ||
if ( hoehe ) { | if ( hoehe ) { | ||
container.style.height = parseInt( hoehe, 10 ) + 'px'; | container.style.height = parseInt( hoehe, 10 ) + 'px'; | ||
} | } | ||
var hatFullscreen = !!( L.Control && L.Control.FullScreen ); | |||
var layer = baueLayer(); | var layer = baueLayer(); | ||
// Start-Layer: per data-layer gewünschter Menü-Name, sonst Default. | |||
var startLayer = ( layerWunsch && layer.basis[ layerWunsch ] ) ? | |||
layer.basis[ layerWunsch ] : layer.standard; | |||
var map = L.map( container, { | var map = L.map( container, { | ||
center: CONFIG.defaultCenter, | center: CONFIG.defaultCenter, | ||
zoom: CONFIG.defaultZoom, | zoom: CONFIG.defaultZoom, | ||
layers: [ | layers: [ startLayer ], | ||
fullscreenControl: | fullscreenControl: hatFullscreen | ||
} ); | } ); | ||
L.control.layers( layer.basis ).addTo( map ); | L.control.layers( layer.basis ).addTo( map ); | ||
// Sicherheitsnetz: Knopf explizit hinzufügen, falls die Option oben | |||
// nicht gegriffen hat (z.B. Plugin kam minimal verspätet). | |||
if ( hatFullscreen && !map.fullscreenControl ) { | |||
map.addControl( new L.Control.FullScreen() ); | |||
} | |||
if ( container.getAttribute( 'data-gemeinden' ) === 'ja' && gemeinden ) { | if ( container.getAttribute( 'data-gemeinden' ) === 'ja' && gemeinden ) { | ||
| Zeile 190: | Zeile 391: | ||
var aktiverMarker = null; | var aktiverMarker = null; | ||
var sichtbare = []; | var sichtbare = []; | ||
// Ad-hoc-Pin-Modus: nur dieser eine Pin, keine Orte aus der JSON. | |||
if ( pinKoord ) { | |||
var pinMarker = L.marker( pinKoord, { | |||
icon: icon( true ), | |||
zIndexOffset: 1000 | |||
} ); | |||
if ( pinName ) { | |||
pinMarker.bindPopup( '<b>' + mw.html.escape( pinName ) + '</b>' ); | |||
} | |||
pinMarker.addTo( map ); | |||
map.setView( pinKoord, CONFIG.focusZoom ); | |||
if ( pinName ) { | |||
pinMarker.openPopup(); | |||
} | |||
map.on( 'resize fullscreenchange', function () { | |||
setTimeout( function () { | |||
map.invalidateSize(); | |||
}, 100 ); | |||
} ); | |||
return map; | |||
} | |||
orte.forEach( function ( ort ) { | orte.forEach( function ( ort ) { | ||
if ( | // Nur-aktiv-Modus: alles außer dem aktiven Ort überspringen. | ||
if ( nurAktiv && ( !aktivId || ort.id !== aktivId ) ) { | |||
return; | |||
} | |||
// Filter: wenn eine Filterliste gesetzt ist, muss die Kategorie | |||
// des Ortes in der Liste vorkommen. Sonst wird der Ort ausgelassen. | |||
if ( filterListe.length && filterListe.indexOf( ort.kategorie ) === -1 ) { | |||
return; | return; | ||
} | } | ||
if ( typeof ort.lat !== 'number' || typeof | |||
// Koordinaten: bevorzugt aus dem geo-Feld (String "lat,lon", | |||
// direkt aus OSM/OrganicMaps kopierbar, Leerzeichen egal), | |||
// sonst aus den klassischen lat/lon-Zahlenfeldern. | |||
var lat = ort.lat; | |||
var lon = ort.lon; | |||
if ( typeof ort.geo === 'string' ) { | |||
var teile = ort.geo.split( ',' ); | |||
if ( teile.length === 2 ) { | |||
lat = parseFloat( teile[ 0 ] ); | |||
lon = parseFloat( teile[ 1 ] ); | |||
} | |||
} | |||
if ( typeof lat !== 'number' || typeof lon !== 'number' || | |||
isNaN( lat ) || isNaN( lon ) ) { | |||
return; | return; | ||
} | } | ||
var istAktiv = aktivId && ort.id === aktivId; | var istAktiv = aktivId && ort.id === aktivId; | ||
var m = L.marker( [ | // id ist Anzeigename und Ziel. seite (optional) übersteuert das | ||
// Ziel; beginnt sie mit http:// oder https://, wird direkt auf | |||
// die externe Adresse verlinkt statt auf eine Wiki-Seite. | |||
var ziel = ort.seite || ort.id || ''; | |||
var istExtern = /^https?:\/\//i.test( ziel ); | |||
var link = istExtern ? ziel : mw.util.getUrl( ziel ); | |||
var m = L.marker( [ lat, lon ], { | |||
icon: icon( istAktiv ), | icon: icon( istAktiv ), | ||
zIndexOffset: istAktiv ? 1000 : 0 | zIndexOffset: istAktiv ? 1000 : 0 | ||
} ); | } ); | ||
var | |||
// Kategorie-Farbe (nur für nicht-aktive Pins; aktiver bleibt rot). | |||
// Wird gesetzt, sobald das Marker-DOM-Element existiert. | |||
var farbe = kategorien && ort.kategorie ? kategorien[ ort.kategorie ] : null; | |||
if ( farbe && !istAktiv ) { | |||
m.on( 'add', function () { | |||
if ( this._icon ) { | |||
this._icon.style.background = farbe; | |||
} | |||
} ); | |||
} | |||
m.bindPopup( | m.bindPopup( | ||
'<b>' + mw.html.escape( ort. | '<b>' + mw.html.escape( ort.id || '' ) + '</b><br>' + | ||
'<a href="' + link + '">Zur Seite →</a>' | '<a href="' + link + '"' + | ||
( istExtern ? ' target="_blank" rel="noopener"' : '' ) + | |||
'>Zur Seite →</a>' | |||
); | ); | ||
m.addTo( map ); | m.addTo( map ); | ||
sichtbare.push( [ | sichtbare.push( [ lat, lon ] ); | ||
if ( istAktiv ) { | if ( istAktiv ) { | ||
aktiverMarker = m; | aktiverMarker = m; | ||
| Zeile 253: | Zeile 517: | ||
$.when.apply( $, aufgaben ).then( function ( orteData, gemeinden ) { | $.when.apply( $, aufgaben ).then( function ( orteData, gemeinden ) { | ||
var orte = ( orteData && orteData.orte ) || []; | var orte = ( orteData && orteData.orte ) || []; | ||
// Optionaler Farb-Block: Kategorie -> Farbe. Fehlt er, bleiben | |||
// die Pins in der Standardfarbe. | |||
var kategorien = ( orteData && orteData.kategorien ) || {}; | |||
Array.prototype.forEach.call( container, function ( c ) { | Array.prototype.forEach.call( container, function ( c ) { | ||
try { | try { | ||
baueKarte( c, orte, gemeinden ); | baueKarte( c, orte, gemeinden, kategorien ); | ||
} catch ( e ) { | } catch ( e ) { | ||
c.textContent = 'Karte konnte nicht geladen werden.'; | c.textContent = 'Karte konnte nicht geladen werden.'; | ||
| Zeile 272: | Zeile 539: | ||
* 7) LEAFLET SICHERSTELLEN, DANN STARTEN | * 7) LEAFLET SICHERSTELLEN, DANN STARTEN | ||
* ==================================================================== */ | * ==================================================================== */ | ||
// Lädt EIN Skript nach. Wichtig: manche Leaflet-Plugins sind UMD-Module | |||
// und registrieren sich bei vorhandenem AMD-Loader (z.B. ResourceLoader) | |||
// NICHT am globalen L. Wir deaktivieren define.amd kurzzeitig, damit das | |||
// Plugin den globalen-L-Pfad nimmt. | |||
function ladeSkript( src ) { | |||
return $.Deferred( function ( d ) { | |||
var | var amd = window.define; | ||
var hatAmd = amd && amd.amd; | |||
if ( hatAmd ) { | |||
window.define = undefined; // AMD kurz ausblenden | |||
if ( | |||
} | } | ||
var s = document.createElement( 'script' ); | var s = document.createElement( 'script' ); | ||
s.src = | s.src = src; | ||
s.onload = | s.onload = function () { | ||
if ( hatAmd ) { | |||
window.define = amd; // AMD wiederherstellen | |||
} | |||
d.resolve(); | |||
}; | |||
s.onerror = function () { | s.onerror = function () { | ||
mw.log.error( 'WikiMap: | if ( hatAmd ) { | ||
window.define = amd; | |||
} | |||
mw.log.error( 'WikiMap: Datei nicht ladbar: ' + src ); | |||
d.resolve(); // trotzdem weitermachen | |||
}; | }; | ||
document.head.appendChild( s ); | document.head.appendChild( s ); | ||
}() ); | } ).promise(); | ||
} | |||
function ladeCss( href ) { | |||
var l = document.createElement( 'link' ); | |||
l.rel = 'stylesheet'; | |||
l.href = href; | |||
document.head.appendChild( l ); | |||
} | |||
function stelleLeafletBereit( fertig ) { | |||
var q = LEAFLET_QUELLEN[ LEAFLET_MODUS ]; | |||
// CSS immer einhängen (auch wenn L schon da ist, könnte Plugin-CSS fehlen). | |||
q.css.forEach( ladeCss ); | |||
// JS-Dateien strikt nacheinander laden und ERST starten, wenn sowohl | |||
// L.map als auch L.Control.FullScreen existieren. | |||
var kette = $.Deferred().resolve().promise(); | |||
q.js.forEach( function ( src ) { | |||
kette = kette.then( function () { | |||
// Leaflet-Core nur laden, wenn noch nicht da. | |||
if ( /leaflet\.js$/.test( src ) && window.L && window.L.map ) { | |||
return; | |||
} | |||
return ladeSkript( src ); | |||
} ); | |||
} ); | |||
kette.then( function () { | |||
// Sicherheitsnetz: kurz warten, falls Plugin minimal verzögert ist. | |||
var versuche = 0; | |||
( function pruefe() { | |||
if ( window.L && window.L.map ) { | |||
fertig(); | |||
} else if ( versuche++ < 50 ) { | |||
setTimeout( pruefe, 50 ); | |||
} else { | |||
mw.log.error( 'WikiMap: Leaflet nicht verfügbar.' ); | |||
} | |||
}() ); | |||
} ); | |||
} | } | ||
Aktuelle Version vom 3. Juli 2026, 22:07 Uhr
/**
* Gadget-WikiMap.js
* ============================================================================
* Zentrale Leaflet-Karten-Engine für das Wiki.
*
* KONZEPT
* -------
* EINE zentrale Datenquelle (MediaWiki:WikiMap-Orte.json) hält ALLE Orte.
* Jede Wiki-Seite bindet nur einen leeren <div class="wikimap"> ein und sagt
* über data-Attribute, welcher Ort gerade "aktiv" ist. Dieses Gadget zeichnet
* dann die Karte, alle Pins, hebt den aktiven Pin hervor und zentriert ihn.
*
* Verschiebst du einen Ort, änderst du EINE Zeile in der zentralen JSON –
* jede Karte im ganzen Wiki ist sofort aktuell.
*
* EINBINDUNG AUF EINER SEITE
* --------------------------
* <div class="wikimap" data-aktiv-ort="gaestehaus-mustertal"></div>
*
* data-Attribute:
* data-aktiv-ort ID des hervorgehobenen Ortes (rot, zentriert, Popup auf)
* data-filter nur Orte dieser "kategorie" zeigen. Mehrere Kategorien
* kommagetrennt moeglich, z.B. "asiatisch,gasthaus,imbiss"
* data-pin Ad-hoc-Pin: "lat,lon" zeigt GENAU EINEN Pin an diesen
* Koordinaten und KEINE Orte aus der zentralen JSON.
* Fuer Veranstaltungsseiten (Eventform) gedacht.
* data-pin-name Optional zum Pin: Text im Popup.
* data-gemeinden "ja" => Gemeinde-Umrisse einblenden (Klick => Seite)
* data-nur-aktiv "ja" => nur den aktiven Ort zeigen, alle anderen Pins
* ausblenden (braucht data-aktiv-ort)
* data-layer Start-Ansicht per Menü-Name, z.B. "Zug" oder
* "Zug + Luftbild". Unbekannter Name => normaler Default.
* data-hoehe Pixel-Höhe der Karte (optional, sonst CSS-Default)
*
* ORTE-FELDER (WikiMap-Orte.json)
* -------------------------------
* id Pflicht. Anzeigename im Popup UND Ziel-Wikiseite beim Klick.
* geo Koordinaten als String "lat,lon" – direkt aus OSM.org oder
* OrganicMaps kopierbar, Leerzeichen nach dem Komma erlaubt.
* Beispiel: "47.7073509,15.9933643"
* lat, lon Alternative zu geo: Koordinaten als zwei Zahlenfelder.
* kategorie Optional. Für Filter und Farben.
* seite Optional. Übersteuert das Klick-Ziel. Wiki-Seitenname ODER
* externe Adresse (beginnend mit http:// oder https://).
*
* ABHÄNGIGKEIT: Leaflet 1.9.x + Leaflet.fullscreen. Dieses Gadget lädt sie
* bei Bedarf selbst (siehe LEAFLET_QUELLEN unten).
* ============================================================================
*/
( function () {
'use strict';
/* ====================================================================
* 1) KONFIGURATION
* ==================================================================== */
var CONFIG = {
orteSeite: 'MediaWiki:WikiMap-Orte.json',
gemeindenSeite: 'MediaWiki:WikiMap-Gemeinden.json',
defaultCenter: [ 47.6, 16.1 ],
defaultZoom: 7,
focusZoom: 14
};
var LEAFLET_MODUS = 'lokal'; // 'cdn' zum Testen, 'lokal' für Produktion
var LEAFLET_LOKAL_BASIS = '/resources/lib/leaflet/';
var LEAFLET_QUELLEN = {
cdn: {
css: [
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
'https://unpkg.com/leaflet.fullscreen@5.3.1/dist/Control.FullScreen.css'
],
js: [
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
'https://unpkg.com/leaflet.fullscreen@5.3.1/dist/Control.FullScreen.umd.js'
]
},
lokal: {
css: [
LEAFLET_LOKAL_BASIS + 'leaflet.css',
LEAFLET_LOKAL_BASIS + 'Control.FullScreen.css'
],
js: [
LEAFLET_LOKAL_BASIS + 'leaflet.js',
LEAFLET_LOKAL_BASIS + 'Control.FullScreen.umd.js'
]
}
};
/* ====================================================================
* 2) HINTERGRUNDKARTEN
*
* basemap.at: seit 2023 neue Service-URLs unter mapsneu.wien.gv.at.
* Quelle: https://cdn.basemap.at/basemap.at_URL_Umstellung_2023.pdf
*
* Tracestrack: API-Schlüssel auf Referer schwarzatal.org beschränkt.
* Quelle: https://console.tracestrack.com/
* ==================================================================== */
function baueLayer() {
var osmStandard = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }
);
var osmRelief = L.tileLayer(
'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
{
maxZoom: 17,
attribution: 'Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | ' +
'Darstellung: © OpenTopoMap (CC-BY-SA)'
}
);
var tracestrack = L.tileLayer(
'https://tile.tracestrack.com/topo__/{z}/{x}/{y}.png' +
'?key=bc0464a46d41b1107569bd81112f05ab',
{
maxZoom: 19,
attribution: '© <a href="https://www.tracestrack.com/">Tracestrack</a>, ' +
'© OpenStreetMap-Mitwirkende'
}
);
var luftbild = L.tileLayer(
'https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/' +
'normal/google3857/{z}/{y}/{x}.jpeg',
{
maxZoom: 19,
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>'
}
);
var overlay = L.tileLayer(
'https://mapsneu.wien.gv.at/basemap/bmapoverlay/' +
'normal/google3857/{z}/{y}/{x}.png',
{
maxZoom: 19,
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>'
}
);
// Zweite Luftbild-Instanz für die Gruppe, damit Layer-Wechsel nicht
// zwischen Gruppe und Solo-Luftbild kollidieren (Race Condition).
var luftbildKopie = L.tileLayer(
'https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/' +
'normal/google3857/{z}/{y}/{x}.jpeg',
{
maxZoom: 19,
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>'
}
);
var gemischt = L.layerGroup( [ luftbildKopie, overlay ] );
// OpenRailwayMap ist ein transparentes Gleis-Overlay und braucht eine
// Basiskarte darunter. Eigene OSM-Instanz für die Gruppe (gleiches
// Race-Condition-Muster wie beim Luftbild). Der Tileserver liefert
// 512px-Kacheln, daher tileSize/zoomOffset. Die a/b/c-Subdomains sind
// deprecated, daher URL ohne {s}.
var osmFuerBahn = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }
);
var bahnOverlay = L.tileLayer(
'https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
{
maxZoom: 19,
tileSize: 512,
zoomOffset: -1,
attribution: 'Overlay: <a href="https://www.openrailwaymap.org/">OpenRailwayMap</a> (CC-BY-SA 2.0)'
}
);
var eisenbahn = L.layerGroup( [ osmFuerBahn, bahnOverlay ] );
// Hilfsfunktionen für frische Instanzen: jede LayerGroup braucht
// EIGENE Tile-Layer, sonst kollidieren die Gruppen beim Umschalten
// (Race Condition, siehe luftbildKopie oben).
function neuesLuftbild() {
return L.tileLayer(
'https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/' +
'normal/google3857/{z}/{y}/{x}.jpeg',
{
maxZoom: 19,
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>'
}
);
}
function neuesBmapOverlay() {
return L.tileLayer(
'https://mapsneu.wien.gv.at/basemap/bmapoverlay/' +
'normal/google3857/{z}/{y}/{x}.png',
{
maxZoom: 19,
attribution: 'Datenquelle: <a href="https://basemap.at">basemap.at</a>'
}
);
}
function neuesBahnOverlay() {
return L.tileLayer(
'https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
{
maxZoom: 19,
tileSize: 512,
zoomOffset: -1,
attribution: 'Overlay: <a href="https://www.openrailwaymap.org/">OpenRailwayMap</a> (CC-BY-SA 2.0)'
}
);
}
function neuesOsm() {
return L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{ maxZoom: 19, attribution: '© OpenStreetMap-Mitwirkende' }
);
}
function neuesOpenTopo() {
return L.tileLayer(
'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
{
maxZoom: 17,
attribution: 'Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | ' +
'Darstellung: © OpenTopoMap (CC-BY-SA)'
}
);
}
function neuesTracestrack() {
return L.tileLayer(
'https://tile.tracestrack.com/topo__/{z}/{x}/{y}.png' +
'?key=bc0464a46d41b1107569bd81112f05ab',
{
maxZoom: 19,
attribution: '© <a href="https://www.tracestrack.com/">Tracestrack</a>, ' +
'© OpenStreetMap-Mitwirkende'
}
);
}
// Zug-Kombis: jeweils Basiskarte(n) unten, Gleise obendrauf.
var zugLuftbildKarte = L.layerGroup(
[ neuesLuftbild(), neuesBmapOverlay(), neuesBahnOverlay() ]
);
var zugLuftbild = L.layerGroup( [ neuesLuftbild(), neuesBahnOverlay() ] );
var zugKarte = eisenbahn; // OSM + Gleise (bereits oben gebaut)
var zugTopo = L.layerGroup( [ neuesOpenTopo(), neuesBahnOverlay() ] );
var zugTopo2 = L.layerGroup( [ neuesTracestrack(), neuesBahnOverlay() ] );
// Bus: ÖPNVKarte (memomaps.de) ist eine VOLLSTÄNDIGE Karte mit
// Buslinien/Haltestellen, KEIN transparentes Overlay. Deshalb gibt
// es keine Bus+Luftbild-Kombis. Hinweis: Datenpflege des Projekts
// ist laut OSM-Wiki unregelmäßig.
var bus = L.tileLayer(
'https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png',
{
maxZoom: 18,
attribution: 'Karte: <a href="https://memomaps.de/">memomaps.de</a> ' +
'(CC-BY-SA), Daten: © OpenStreetMap-Mitwirkende'
}
);
return {
basis: {
'Luftbild + Karte': gemischt,
'Luftbild': luftbild,
'Karte': osmStandard,
'Topokarte': osmRelief,
'Topokarte2': tracestrack,
'Zug + Luftbild + Karte': zugLuftbildKarte,
'Zug + Luftbild': zugLuftbild,
'Zug + Karte': zugKarte,
'Zug + Topokarte': zugTopo,
'Zug + Topokarte2': zugTopo2,
'Bus': bus
},
standard: gemischt
};
}
/* ====================================================================
* 3) PIN-ICONS
* ==================================================================== */
function icon( aktiv ) {
return L.divIcon( {
className: 'wikimap-pin' + ( aktiv ? ' wikimap-pin-aktiv' : '' ),
iconSize: aktiv ? [ 28, 28 ] : [ 18, 18 ],
iconAnchor: aktiv ? [ 14, 28 ] : [ 9, 18 ],
popupAnchor: [ 0, aktiv ? -28 : -18 ]
} );
}
/* ====================================================================
* 4) DATEN AUS WIKI-JSON-SEITE
* ==================================================================== */
function ladeJson( seite ) {
var api = new mw.Api();
return api.get( {
action: 'query',
prop: 'revisions',
titles: seite,
rvprop: 'content',
rvslots: 'main',
formatversion: 2
} ).then( function ( data ) {
var page = data.query.pages[ 0 ];
if ( !page || page.missing || !page.revisions ) {
throw new Error( 'Seite fehlt: ' + seite );
}
return JSON.parse( page.revisions[ 0 ].slots.main.content );
} );
}
/* ====================================================================
* 5) EINE EINZELNE KARTE BAUEN
* ==================================================================== */
function baueKarte( container, orte, gemeinden, kategorien ) {
var aktivId = container.getAttribute( 'data-aktiv-ort' ) || null;
// Ad-hoc-Pin-Modus: data-pin="lat,lon" zeigt GENAU EINEN Pin an
// diesen Koordinaten und KEINE Orte aus der zentralen JSON.
// Gedacht z.B. für Veranstaltungsseiten (Eventform), deren Ort
// nicht in WikiMap-Orte.json gepflegt wird.
// Optional: data-pin-name für den Popup-Text.
var pinAttr = container.getAttribute( 'data-pin' ) || '';
var pinKoord = null;
if ( pinAttr ) {
var pinTeile = pinAttr.split( ',' );
if ( pinTeile.length === 2 ) {
var pLat = parseFloat( pinTeile[ 0 ] );
var pLon = parseFloat( pinTeile[ 1 ] );
if ( !isNaN( pLat ) && !isNaN( pLon ) ) {
pinKoord = [ pLat, pLon ];
}
}
}
var pinName = container.getAttribute( 'data-pin-name' ) || '';
// Nur den aktiven Ort zeigen (alle anderen Pins ausblenden).
var nurAktiv = container.getAttribute( 'data-nur-aktiv' ) === 'ja';
// Start-Layer per Menü-Name wählen, z.B. data-layer="Eisenbahnkarte".
// Unbekannter Name => normaler Default.
var layerWunsch = container.getAttribute( 'data-layer' ) || '';
// Filter: eine oder mehrere Kategorien, kommagetrennt.
// "asiatisch,gasthaus" => nur Orte, deren kategorie EINE davon ist.
// Leer/nicht gesetzt => alle Orte.
var filterAttr = container.getAttribute( 'data-filter' ) || '';
var filterListe = filterAttr
.split( ',' )
.map( function ( s ) { return s.trim(); } )
.filter( function ( s ) { return s.length > 0; } );
var hoehe = container.getAttribute( 'data-hoehe' );
if ( hoehe ) {
container.style.height = parseInt( hoehe, 10 ) + 'px';
}
var hatFullscreen = !!( L.Control && L.Control.FullScreen );
var layer = baueLayer();
// Start-Layer: per data-layer gewünschter Menü-Name, sonst Default.
var startLayer = ( layerWunsch && layer.basis[ layerWunsch ] ) ?
layer.basis[ layerWunsch ] : layer.standard;
var map = L.map( container, {
center: CONFIG.defaultCenter,
zoom: CONFIG.defaultZoom,
layers: [ startLayer ],
fullscreenControl: hatFullscreen
} );
L.control.layers( layer.basis ).addTo( map );
// Sicherheitsnetz: Knopf explizit hinzufügen, falls die Option oben
// nicht gegriffen hat (z.B. Plugin kam minimal verspätet).
if ( hatFullscreen && !map.fullscreenControl ) {
map.addControl( new L.Control.FullScreen() );
}
if ( container.getAttribute( 'data-gemeinden' ) === 'ja' && gemeinden ) {
L.geoJSON( gemeinden, {
style: { color: '#3367d6', weight: 1.5, fillOpacity: 0.05 },
onEachFeature: function ( feature, lyr ) {
var p = feature.properties || {};
var name = p.name || 'Gemeinde';
var ziel = p.seite || name;
lyr.bindTooltip( name );
lyr.on( 'click', function () {
window.location.href = mw.util.getUrl( ziel );
} );
lyr.on( 'mouseover', function () {
lyr.setStyle( { fillOpacity: 0.25 } );
} );
lyr.on( 'mouseout', function () {
lyr.setStyle( { fillOpacity: 0.05 } );
} );
}
} ).addTo( map );
}
var aktiverMarker = null;
var sichtbare = [];
// Ad-hoc-Pin-Modus: nur dieser eine Pin, keine Orte aus der JSON.
if ( pinKoord ) {
var pinMarker = L.marker( pinKoord, {
icon: icon( true ),
zIndexOffset: 1000
} );
if ( pinName ) {
pinMarker.bindPopup( '<b>' + mw.html.escape( pinName ) + '</b>' );
}
pinMarker.addTo( map );
map.setView( pinKoord, CONFIG.focusZoom );
if ( pinName ) {
pinMarker.openPopup();
}
map.on( 'resize fullscreenchange', function () {
setTimeout( function () {
map.invalidateSize();
}, 100 );
} );
return map;
}
orte.forEach( function ( ort ) {
// Nur-aktiv-Modus: alles außer dem aktiven Ort überspringen.
if ( nurAktiv && ( !aktivId || ort.id !== aktivId ) ) {
return;
}
// Filter: wenn eine Filterliste gesetzt ist, muss die Kategorie
// des Ortes in der Liste vorkommen. Sonst wird der Ort ausgelassen.
if ( filterListe.length && filterListe.indexOf( ort.kategorie ) === -1 ) {
return;
}
// Koordinaten: bevorzugt aus dem geo-Feld (String "lat,lon",
// direkt aus OSM/OrganicMaps kopierbar, Leerzeichen egal),
// sonst aus den klassischen lat/lon-Zahlenfeldern.
var lat = ort.lat;
var lon = ort.lon;
if ( typeof ort.geo === 'string' ) {
var teile = ort.geo.split( ',' );
if ( teile.length === 2 ) {
lat = parseFloat( teile[ 0 ] );
lon = parseFloat( teile[ 1 ] );
}
}
if ( typeof lat !== 'number' || typeof lon !== 'number' ||
isNaN( lat ) || isNaN( lon ) ) {
return;
}
var istAktiv = aktivId && ort.id === aktivId;
// id ist Anzeigename und Ziel. seite (optional) übersteuert das
// Ziel; beginnt sie mit http:// oder https://, wird direkt auf
// die externe Adresse verlinkt statt auf eine Wiki-Seite.
var ziel = ort.seite || ort.id || '';
var istExtern = /^https?:\/\//i.test( ziel );
var link = istExtern ? ziel : mw.util.getUrl( ziel );
var m = L.marker( [ lat, lon ], {
icon: icon( istAktiv ),
zIndexOffset: istAktiv ? 1000 : 0
} );
// Kategorie-Farbe (nur für nicht-aktive Pins; aktiver bleibt rot).
// Wird gesetzt, sobald das Marker-DOM-Element existiert.
var farbe = kategorien && ort.kategorie ? kategorien[ ort.kategorie ] : null;
if ( farbe && !istAktiv ) {
m.on( 'add', function () {
if ( this._icon ) {
this._icon.style.background = farbe;
}
} );
}
m.bindPopup(
'<b>' + mw.html.escape( ort.id || '' ) + '</b><br>' +
'<a href="' + link + '"' +
( istExtern ? ' target="_blank" rel="noopener"' : '' ) +
'>Zur Seite →</a>'
);
m.addTo( map );
sichtbare.push( [ lat, lon ] );
if ( istAktiv ) {
aktiverMarker = m;
}
} );
if ( aktiverMarker ) {
map.setView( aktiverMarker.getLatLng(), CONFIG.focusZoom );
aktiverMarker.openPopup();
} else if ( sichtbare.length ) {
map.fitBounds( sichtbare, { padding: [ 30, 30 ] } );
}
map.on( 'resize fullscreenchange', function () {
setTimeout( function () {
map.invalidateSize();
}, 100 );
} );
return map;
}
/* ====================================================================
* 6) EINSTIEG
* ==================================================================== */
function init() {
var container = document.querySelectorAll( '.wikimap' );
if ( !container.length ) {
return;
}
var brauchtGemeinden = Array.prototype.some.call(
container,
function ( c ) {
return c.getAttribute( 'data-gemeinden' ) === 'ja';
}
);
var aufgaben = [ ladeJson( CONFIG.orteSeite ) ];
aufgaben.push(
brauchtGemeinden ?
ladeJson( CONFIG.gemeindenSeite ).catch( function () {
return null;
} ) :
$.Deferred().resolve( null )
);
$.when.apply( $, aufgaben ).then( function ( orteData, gemeinden ) {
var orte = ( orteData && orteData.orte ) || [];
// Optionaler Farb-Block: Kategorie -> Farbe. Fehlt er, bleiben
// die Pins in der Standardfarbe.
var kategorien = ( orteData && orteData.kategorien ) || {};
Array.prototype.forEach.call( container, function ( c ) {
try {
baueKarte( c, orte, gemeinden, kategorien );
} catch ( e ) {
c.textContent = 'Karte konnte nicht geladen werden.';
mw.log.error( e );
}
} );
} ).catch( function ( e ) {
Array.prototype.forEach.call( container, function ( c ) {
c.textContent = 'Ortsdaten konnten nicht geladen werden.';
} );
mw.log.error( e );
} );
}
/* ====================================================================
* 7) LEAFLET SICHERSTELLEN, DANN STARTEN
* ==================================================================== */
// Lädt EIN Skript nach. Wichtig: manche Leaflet-Plugins sind UMD-Module
// und registrieren sich bei vorhandenem AMD-Loader (z.B. ResourceLoader)
// NICHT am globalen L. Wir deaktivieren define.amd kurzzeitig, damit das
// Plugin den globalen-L-Pfad nimmt.
function ladeSkript( src ) {
return $.Deferred( function ( d ) {
var amd = window.define;
var hatAmd = amd && amd.amd;
if ( hatAmd ) {
window.define = undefined; // AMD kurz ausblenden
}
var s = document.createElement( 'script' );
s.src = src;
s.onload = function () {
if ( hatAmd ) {
window.define = amd; // AMD wiederherstellen
}
d.resolve();
};
s.onerror = function () {
if ( hatAmd ) {
window.define = amd;
}
mw.log.error( 'WikiMap: Datei nicht ladbar: ' + src );
d.resolve(); // trotzdem weitermachen
};
document.head.appendChild( s );
} ).promise();
}
function ladeCss( href ) {
var l = document.createElement( 'link' );
l.rel = 'stylesheet';
l.href = href;
document.head.appendChild( l );
}
function stelleLeafletBereit( fertig ) {
var q = LEAFLET_QUELLEN[ LEAFLET_MODUS ];
// CSS immer einhängen (auch wenn L schon da ist, könnte Plugin-CSS fehlen).
q.css.forEach( ladeCss );
// JS-Dateien strikt nacheinander laden und ERST starten, wenn sowohl
// L.map als auch L.Control.FullScreen existieren.
var kette = $.Deferred().resolve().promise();
q.js.forEach( function ( src ) {
kette = kette.then( function () {
// Leaflet-Core nur laden, wenn noch nicht da.
if ( /leaflet\.js$/.test( src ) && window.L && window.L.map ) {
return;
}
return ladeSkript( src );
} );
} );
kette.then( function () {
// Sicherheitsnetz: kurz warten, falls Plugin minimal verzögert ist.
var versuche = 0;
( function pruefe() {
if ( window.L && window.L.map ) {
fertig();
} else if ( versuche++ < 50 ) {
setTimeout( pruefe, 50 );
} else {
mw.log.error( 'WikiMap: Leaflet nicht verfügbar.' );
}
}() );
} );
}
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
$( function () {
if ( !document.querySelector( '.wikimap' ) ) {
return;
}
stelleLeafletBereit( init );
} );
} );
}() );