Diese Seite beschreibt das Wetter-Widget-System dieses Wikis vollständig – was es kann, wie es zusammenspielt und wie man es Schritt für Schritt in einem anderen MediaWiki nachbaut. Der komplette Code aller Bestandteile steht am Ende der Seite.
Stand: Juli 2026
Was das System kann
- Wetterkästchen auf beliebigen Seiten: aktuelle Messwerte (Temperatur groß, Wind mit Richtung und Böen, Luftfeuchtigkeit, Niederschlag, Luftdruck, im Winter Schneehöhe) der nächstgelegenen Wetterstation, dazu eine 3-Tages-Vorhersage mit Höchst-/Tiefstwerten und Niederschlag – punktgenau für die Koordinaten des Ortes.
- Wetterleiste: eine Zeile aus Icon-Buttons zu externen Diensten (7-Tage-Prognose auf wetter.zone, Regenradar und Bewölkung auf Windy, Nachthimmel auf timeanddate, Pollenbelastung, Wetterwarnungen) – alle Links werden aus den Koordinaten zusammengesetzt und können daher nie auf den falschen Ort zeigen.
- Generator-Formular: Ortsname und Koordinaten eingeben (direkt aus OSM kopierbar), Station aus Vorschlägen wählen, fertige Bausteine kopieren. Kein Handtippen von Vorlagen-Aufrufen nötig.
- Kartenanbindung (optional): Ein Export-Knopf erzeugt alle aktiven Wetterstationen als Einträge für die WikiMap; ein Klick auf einen Stationsmarker führt zurück zum Generator, wo der passende Widget-Baustein schon fertig bereitsteht.
Datenquelle ist die offene Dataset-API der GeoSphere Austria (vormals ZAMG): Open Data unter CC BY 4.0, kein API-Schlüssel nötig, Limit 5 Anfragen/Sekunde bzw. 240/Stunde je Besucher (wird im Alltag nie erreicht, da jeder Browser sein eigenes Limit hat). Die Messwerte stammen aus dem TAWES-Stationsnetz (10-Minuten-Takt), die Vorhersage aus dem AROME-Modell (2,5-km-Gitter, alle 3 Stunden neu, rund 60 Stunden weit). Das System ist damit bewusst auf Österreich zugeschnitten.
Das Grundprinzip: Koordinaten und Station sind getrennt
Die zwei Datenarten im Widget haben getrennte Quellen, und genau das macht das System flexibel:
lat/lonbestimmen den Punkt, für den das Modell die Vorhersage rechnet – frei wählbar, jeder Fleck im Land, ganz ohne Station. Auch alle Links der Wetterleiste hängen nur an den Koordinaten.stationbestimmt, von welchem echten Messgerät die aktuellen Werte stammen – das geht nur dort, wo eines steht. Die Station steht mit Name und Seehöhe ehrlich in der Fußzeile des Widgets; sie darf also ruhig im Tal liegen, während die Vorhersage für den Berg gilt.
Die Bausteine im Überblick
| Bestandteil | Seite im Wiki | Aufgabe |
|---|---|---|
| Widget-Vorlage | Vorlage:Wetterwidget |
Legt beim Einbinden einen unsichtbaren Container mit den Ortsdaten an (Ort, Koordinaten, Station). |
| Widget-Gadget | MediaWiki:Gadget-Wetter.js |
Findet die Container beim Seitenaufruf, holt Messwerte und Vorhersage von GeoSphere und zeichnet das Kästchen. |
| Leisten-Vorlage | Vorlage:Wetterleiste |
Rendert die Button-Zeile; die Dienst-Adressen stecken zentral hier – ein Dienstwechsel ist eine einzige Änderung. |
| Generator-Gadget | MediaWiki:Gadget-WetterGenerator.js |
Das Formular: Stationssuche, Bausteinerzeugung, WikiMap-Export, Karten-Rücksprung. |
| Aktivierung | MediaWiki:Gadgets-definition |
Zwei Zeilen schalten beide Gadgets für alle Besucher frei. |
| Generator-Seite | beliebig, z. B. Schwarzatal:Wetterwidget erstellen |
Normale Wikiseite mit einem einzigen div-Container; nur hier wird das Formular sichtbar. |
| Icons | File:-Namensraum |
Sechs kleine Bilder für die Wetterleiste (Liste siehe Einrichtung). |
Es gibt keinen weiteren „Aktivieren"-Schalter: Sobald die Zeilen in der Gadgets-definition stehen und die Skriptseiten existieren, laufen die Gadgets. Beide werden zwar auf jeder Seite mitgeladen, tun aber nur dann etwas, wenn ihr Auslöser vorhanden ist – das Widget-Gadget braucht einen Wetterwidget-Container, der Generator seinen div.
Ablauf beim Seitenaufruf
- Jemand öffnet eine Gemeindeseite mit eingebundenem
{{Wetterwidget|…}}. - Die Vorlage hat dort einen Container mit den Ortsdaten als data-Attribute hinterlassen.
- Das Widget-Gadget erkennt den Container und stellt zwei Anfragen an die GeoSphere-API: aktuelle Messwerte der eingetragenen Station und die Vorhersage-Zeitreihe für die Koordinaten.
- Aus den Antworten zeichnet es das Kästchen; die Stundenwerte der Vorhersage werden zu Tagen (Wiener Zeit) mit Höchst-/Tiefstwert und Niederschlagssumme gebündelt.
- Schlägt eine Anfrage fehl, erscheint eine rote Meldung mit Ursache im Kästchen – der Rest funktioniert weiter. Bei Parameter-Änderungen der API probiert das Gadget selbstständig Ausweich-Parametersätze durch.
Die Wetterleiste braucht dagegen gar kein Skript: Sie ist reiner Wikitext, die Links entstehen beim Rendern der Seite.
Verwendung im Alltag
Normalfall – über den Generator:
- Generator-Seite öffnen, Ortsname eintragen und die Koordinaten einfügen. Das Koordinatenfeld nimmt alles, was man so kopiert:
47.67475, 16.01918aus OSM, Schreibweise mit Dezimalkomma oder gleich die ganze OSM-Adresse – es zählen die letzten beiden Zahlen, versehentlich vertauschte Reihenfolge wird erkannt. - „Stationen suchen" klicken, eine der fünf vorgeschlagenen Stationen (mit Entfernung und Seehöhe) anhaken – die nächstgelegene ist vorausgewählt, die Wahl bleibt aber bewusst beim Menschen: Für einen Bergort kann eine etwas entferntere Höhenstation ehrlicher sein als die nächste im Tal.
- „Baustein kopieren" – im Feld stehen Wetterleiste und Wetterwidget fertig untereinander (das Häkchen „Wetterleiste mit ausgeben" steuert das). Auf der Zielseite im Quelltext einfügen, speichern.
Von der Karte aus (mit WikiMap-Anbindung): Stationsmarker antippen, dem Link folgen – der Generator öffnet sich mit fertigem Baustein für genau diese Station; Ortsname und Koordinaten sind vorbefüllt und noch anpassbar. Die Leiste ist hier automatisch abgewählt, denn eine Station ist keine Gemeinde.
Von Hand geht es natürlich auch – die Aufrufe sehen so aus (Muster stehen auch auf den Vorlagenseiten):
{{Wetterleiste
|ort = Altendorf
|lat = 47.6587
|lon = 16.0155
|pollenloc = (erzeugt der Generator automatisch)
}}
{{Wetterwidget
|ort = Altendorf
|lat = 47.6587
|lon = 16.0155
|station = 11381
|stationsname = Pottschach
|stationshoehe = 416
}}
Einrichtung in einem anderen Wiki
Voraussetzungen: MediaWiki mit aktivierter Gadgets-Erweiterung (Standard), jQuery (bringt MediaWiki mit), HTTPS (für den Kopieren-Knopf). ParserFunctions wird bewusst nicht benötigt – die Vorlagen kommen mit der Kernfunktion urlencode aus. Sinnvoll einsetzbar ist das System nur für Orte in Österreich, weil Messnetz und Modell der GeoSphere gehören.
- Vorlage:Wetterwidget anlegen, Inhalt: Code-Block A unten.
- Vorlage:Wetterleiste anlegen, Inhalt: Code-Block B.
- MediaWiki:Gadget-Wetter.js anlegen, Inhalt: Code-Block C.
- MediaWiki:Gadget-WetterGenerator.js anlegen, Inhalt: Code-Block D.
- In MediaWiki:Gadgets-definition zwei Zeilen ergänzen (Stern gehört dazu;
default= für alle aktiv,hidden= kein Eintrag in den Benutzereinstellungen):* Wetter[ResourceLoader|default|hidden]|Wetter.js* WetterGenerator[ResourceLoader|default|hidden]|WetterGenerator.js
- Generator-Seite anlegen (Name frei), einziger Inhalt:
<div id="wetter-generator"></div>
- Icons hochladen (42-px-tauglich):
File:weather.png,File:Rain.png,File:Cloud.png,File:nightsky.png,File:pollen.pngundFile:Warnung.png. Fehlt eines, zeigt der betreffende Button einen roten Dateilink – nichts geht kaputt. - Hart neu laden (Strg+F5), denn MediaWiki cached Gadget-Skripte. Danach auf einer Testseite einen Baustein einfügen und alle Buttons einmal durchklicken.
An das eigene Wiki anpassen (alle Stellen sind im Code kommentiert):
- Im Generator die Konstante
EXPORT_ZENTRUM(Mittelpunkt für den Export-Umkreis) auf die eigene Region setzen; Vorgabe ist das Schwarzatal. - In der Leisten-Vorlage ist beim Pollen-Button das Bundesland fest auf Niederösterreich gesetzt – für andere Regionen austauschen.
- Der Karten-Export und der Rücksprung setzen das hauseigene WikiMap-Gadget dieses Wikis voraus (Einträge im Format von
WikiMap-Orte.json). Ohne WikiMap einfach den Export-Kasten ungenutzt lassen – alles andere funktioniert unabhängig davon.
WikiMap-Anbindung einrichten (optional): Auf der Generator-Seite im Kasten „Stationen für die WikiMap" den Umkreis wählen und exportieren, die erzeugten Einträge in die Liste orte von MediaWiki:WikiMap-Orte.json einfügen – am besten vor einen bestehenden Eintrag, dann stimmen die Kommas. Einmalig im Block kategorien ergänzen: "Wetterstation": "#2a6fb8" (Farbe frei wählbar). Ändert sich das Stationsnetz oder der Name der Generator-Seite, einfach neu exportieren und den Block austauschen.
Grenzen und Betriebshinweise
- Die Widget-Vorhersage endet nach rund 60 Stunden – dort hört der offene AROME-Datensatz auf. Längere Prognosen liefert der wetter.zone-Button der Leiste (GeoSpheres eigene Punktprognose mit ECMWF-Anschluss).
- Der Vorhersage-Endpunkt der API läuft bei GeoSphere noch als „prerelease"; ein Nachfolger ist für Anfang 2027 angekündigt. Das Widget fragt Parameternamen deshalb mit Ausweichvarianten an und meldet Ausfälle sichtbar im Kästchen.
- Die Pflicht zur Namensnennung (CC BY 4.0) erfüllt die eingebaute Fußzeile „Datenquelle: GeoSphere Austria".
- Die Adressmuster der externen Dienste (wetter.zone, Windy, timeanddate, MSN, GeoSphere-Warn-App) folgen den beobachteten bzw. dokumentierten Schemata der Anbieter. Ändert ein Dienst sein Muster, ist die Korrektur genau eine Zeile in der Leisten-Vorlage – Hinweise dazu stehen dort.
- Fehlerbilder: Rotlink
Vorlage:…= Vorlage noch nicht angelegt. Rote Meldung im Kästchen = API-Problem samt Ursache (bei „Browser hat die Anfrage blockiert" die Konsole auf CORS prüfen). „Die Station liefert derzeit keine Werte" = Station prüfen und ggf. über den Generator eine andere wählen.
Code-Block A: Vorlage:Wetterwidget
<includeonly><div class="wetterwidget" data-ort="{{{ort|}}}" data-lat="{{{lat|}}}" data-lon="{{{lon|}}}" data-station="{{{station|}}}" data-stationsname="{{{stationsname|}}}" data-stationshoehe="{{{stationshoehe|}}}">Aktuelle Wetterdaten für {{{ort|diesen Ort}}} gibt es mit eingeschaltetem JavaScript.</div></includeonly><noinclude>
Dieser Baustein zeigt das Wetterwidget mit Messwerten und Vorhersage der GeoSphere Austria an. Die Anzeige übernimmt das Gadget [[MediaWiki:Gadget-Wetter.js]].
Bausteine für Gemeindeseiten erstellt man am einfachsten über die Generator-Seite (Formular), die den fertigen Aufruf samt passender Wetterstation liefert.
'''Parameter:''' ort, lat, lon (Dezimalgrad), station (GeoSphere-Stations-ID), stationsname, stationshoehe (m)
</noinclude>
Code-Block B: Vorlage:Wetterleiste
<includeonly><div class="wetterleiste">[[File:weather.png|42px|link=https://wetter.zone/wetter/main/{{{lat|}}}/{{{lon|}}}/{{urlencode:{{{ort|Ort}}}|PATH}}|Wetterprognose {{{ort|}}} – wetter.zone (GeoSphere)]] [[File:Rain.png|42px|link=https://www.windy.com/?radar,{{{lat|}}},{{{lon|}}},10|Regenradar (Windy)]] [[File:Cloud.png|42px|link=https://www.windy.com/?clouds,{{{lat|}}},{{{lon|}}},9|Bewölkung (Windy)]] [[File:nightsky.png|42px|link=https://www.timeanddate.com/astronomy/night/@{{{lat|}}},{{{lon|}}}|Nachthimmel (timeanddate.com)]] [[File:pollen.png|42px|link=https://www.msn.com/de-de/wetter/pollen/in-{{urlencode:{{{ort|Ort}}}|PATH}},Nieder%C3%B6sterreich?loc={{{pollenloc|}}}&weadegreetype=C|Pollenbelastung {{{ort|}}}]] [[File:Warnung.png|42px|link=https://warnungen.zamg.at/wsapp/de/alle/heute/19177,257711,783484,534322/{{{lon|}}},{{{lat|}}}|Wetterwarnungen für {{{ort|}}} (GeoSphere)]]</div></includeonly><noinclude>
Wetter-Buttonzeile für Gemeindeseiten: Wetterprognose, Regenradar, Bewölkung, Nachthimmel, Pollenbelastung und Wetterwarnungen. Alle Buttons werden aus den Koordinaten der Gemeinde zusammengesetzt – kein Link kann mehr auf den falschen Ort zeigen. Weitere Links der Gemeindeseite (Website, Wikipedia usw.) gehören bewusst nicht hierher und werden wie bisher direkt auf der Seite gepflegt.
Den fertigen Aufruf erzeugt am einfachsten der Wetterwidget-Generator (Formular); er kodiert dabei auch den Wert für <code>pollenloc</code> vor. Die Vorlage kommt bewusst ohne Zusatz-Erweiterungen aus (nur die MediaWiki-Kernfunktion <code>urlencode</code>).
'''Parameter:'''
{| class="wikitable"
! Feld !! Pflicht? !! Bedeutung
|-
| <code>ort</code> || ja || Ortsname (erscheint in Linkbeschriftungen und Adressen)
|-
| <code>lat</code> / <code>lon</code> || ja || Koordinaten in Dezimalgrad, Punkt als Dezimalzeichen
|-
| <code>pollenloc</code> || empfohlen || Vorkodierte Ortsangabe für den MSN-Pollenlink; liefert der Generator automatisch. Fehlt sie (etwa bei Hand-Aufrufen), rät MSN den Ort nur aus dem Namen – bei mehrdeutigen Ortsnamen also besser über den Generator erzeugen.
|}
'''Hinweise:'''
* Soll später ein Dienst getauscht werden, genügt eine Änderung '''hier in der Vorlage''' – alle Gemeindeseiten übernehmen sie automatisch.
* Der wetter.zone-Link nutzt nur Koordinaten und Ortsname (ohne PLZ- und Länderteil). Sollte die Seite damit einmal keine Daten anzeigen, testweise in der Adresse hier <code>/0/AT</code> anhängen – die Stelle ist im Quelltext leicht zu finden.
* Der Wetterwarnungen-Button nutzt die GeoSphere-Warn-App mit den '''Koordinaten der Gemeinde''' – funktioniert damit bundeslandunabhängig, auch für die steirischen und burgenländischen Nachbarn. Der erste Zahlenblock in der Adresse ist der Kartenausschnitt (ganz Österreich). Sollte die App den übergebenen Punkt wider Erwarten nicht übernehmen, hier ersatzweise auf die Übersicht <code><nowiki>https://warnungen.zamg.at/html/de/heute/alle/at/</nowiki></code> umstellen.
* Der Pollen-Button nutzt MSN mit fest hinterlegtem Bundesland Niederösterreich, weil der österreichische Polleninformationsdienst keine ortsbezogenen Links anbietet. Für steirische/burgenländische Orte stimmt dadurch nur die Beschriftung ungefähr – die Belastungsdaten selbst gelten für die Koordinaten.
* Benötigte Icons: <code>File:weather.png</code>, <code>File:Rain.png</code>, <code>File:Cloud.png</code>, <code>File:nightsky.png</code>, <code>File:pollen.png</code> (vorhanden) sowie '''<code>File:Warnung.png</code>''' – dieses Warnsymbol bitte einmalig hochladen; solange es fehlt, zeigt der Button einen roten Dateilink.
* Die Adressmuster von Windy (<code>?ebene,lat,lon,zoom</code>) und timeanddate (<code>@lat,lon</code>) folgen dem gängigen Schema der Anbieter; sollte eines davon einmal nicht mehr stimmen, hier zentral korrigieren.
'''Beispiel:'''
<pre>
{{Wetterleiste
|ort = Altendorf
|lat = 47.6587
|lon = 16.0155
|pollenloc = (erzeugt der Generator automatisch)
}}
</pre>
</noinclude>
Code-Block C: MediaWiki:Gadget-Wetter.js
/**
* MediaWiki:Gadget-Wetter.js
* =====================================================================
* Wetterwidget für Gemeindeseiten: zeigt aktuelle Messwerte der
* eingetragenen GeoSphere-Wetterstation sowie eine 3-Tages-Vorhersage
* (AROME-Modell, 2,5-km-Gitter) für die Koordinaten des Ortes.
*
* Datenquelle: GeoSphere Austria Dataset-API (Open Data, CC BY 4.0),
* kein API-Schlüssel nötig. Doku: https://dataset.api.hub.geosphere.at/v1/docs/
*
* EINRICHTUNG (einmalig):
* 1) Diesen Code nach [[MediaWiki:Gadget-Wetter.js]] kopieren.
* 2) In [[MediaWiki:Gadgets-definition]] ergänzen:
* * Wetter[ResourceLoader|default|hidden]|Wetter.js
* 3) [[MediaWiki:Gadget-Wetter]] anlegen, Inhalt z. B.:
* Wetterwidget mit GeoSphere-Daten.
* 4) [[Vorlage:Wetterwidget]] mit dem Inhalt der mitgelieferten Datei
* Vorlage-Wetterwidget.wikitext anlegen.
*
* VERWENDUNG:
* Auf der Gemeindeseite den {{Wetterwidget|...}}-Baustein einfügen –
* am einfachsten erzeugt über die Generator-Seite (Formular). Das Gadget
* findet alle Bausteine der Seite und befüllt sie beim Seitenaufruf.
*
* TECHNISCHE HINWEISE:
* - Pro Widget und Seitenaufruf entstehen zwei API-Anfragen (Messwerte,
* Vorhersage); das GeoSphere-Limit von 240 Anfragen pro Stunde gilt je
* Besucher und wird damit bei weitem nicht erreicht.
* - Die Parameternamen der Vorhersage werden mit Ausweichvarianten
* angefragt, da der Vorhersage-Endpunkt laut GeoSphere noch als
* "prerelease" gilt. Das AROME-Modell rechnet alle 3 Stunden neu und
* liefert 60 Stunden Vorhersage; laut GeoSphere wird der Datensatz
* Anfang 2027 durch einen Nachfolger (neuer Endpunkt) abgelöst.
* =====================================================================
*/
( function () {
'use strict';
var BASIS = 'https://dataset.api.hub.geosphere.at/v1';
var ZEITZONE = 'Europe/Vienna';
var MAX_TAGE = 3;
/* Parametersätze in absteigender Wunschreihenfolge. Antwortet der
* Server mit Status 400 (unbekannter Parameter), wird der nächste,
* kleinere Satz versucht. */
var MESS_SAETZE = [
[ 'TL', 'RF', 'DD', 'FFAM', 'FFX', 'RR', 'P0', 'SH' ],
[ 'TL', 'RF', 'DD', 'FFAM', 'RR' ],
[ 'TL' ]
];
var PROGNOSE_SAETZE = [
[ 't2m', 'rr_acc' ],
[ 't2m', 'rr' ],
[ 't2m' ]
];
$( function () {
$( '.wetterwidget' ).each( function () {
starteWidget( $( this ) );
} );
} );
/* ------------------------------------------------------------------
* Ablaufsteuerung je Widget
* ------------------------------------------------------------------ */
function starteWidget( $box ) {
var cfg = {
ort: $.trim( $box.attr( 'data-ort' ) || '' ),
latText: $.trim( $box.attr( 'data-lat' ) || '' ),
lonText: $.trim( $box.attr( 'data-lon' ) || '' ),
station: $.trim( $box.attr( 'data-station' ) || '' ),
stationsname: $.trim( $box.attr( 'data-stationsname' ) || '' ),
stationshoehe: $.trim( $box.attr( 'data-stationshoehe' ) || '' )
};
var latOk = isFinite( parseFloat( cfg.latText ) );
var lonOk = isFinite( parseFloat( cfg.lonText ) );
cfg.mitMesswerten = cfg.station !== '';
cfg.mitVorhersage = latOk && lonOk;
gestalteBox( $box );
$box.empty().append(
titelZeile( cfg ),
$( '<div>' ).css( kleinGrau() ).text( 'Wetterdaten werden geladen …' )
);
if ( !cfg.mitMesswerten && !cfg.mitVorhersage ) {
zeichneWidget( $box, cfg, {
messFehler: 'Im Baustein fehlen Stations-ID und Koordinaten – ' +
'bitte über die Generator-Seite neu erstellen.',
prognoseFehler: ''
} );
return;
}
var stand = { mess: null, messFehler: '', prognose: null, prognoseFehler: '' };
var offen = ( cfg.mitMesswerten ? 1 : 0 ) + ( cfg.mitVorhersage ? 1 : 0 );
function fertigMelden() {
offen -= 1;
if ( offen <= 0 ) {
zeichneWidget( $box, cfg, stand );
}
}
if ( cfg.mitMesswerten ) {
ladeMitVarianten( MESS_SAETZE, function ( satz ) {
return BASIS + '/station/current/tawes-v1-10min' +
'?parameters=' + satz.join( ',' ) +
'&station_ids=' + encodeURIComponent( cfg.station );
}, function ( fehler, antwort, satz ) {
if ( fehler ) {
stand.messFehler = fehler;
} else {
stand.mess = leseMesswerte( antwort, satz );
if ( !stand.mess ) {
stand.messFehler = 'Die Station liefert derzeit keine Werte.';
}
}
fertigMelden();
} );
}
if ( cfg.mitVorhersage ) {
ladeMitVarianten( PROGNOSE_SAETZE, function ( satz ) {
return BASIS + '/timeseries/forecast/nwp-v1-1h-2500m' +
'?parameters=' + satz.join( ',' ) +
'&lat_lon=' + encodeURIComponent( cfg.latText + ',' + cfg.lonText );
}, function ( fehler, antwort, satz ) {
if ( fehler ) {
stand.prognoseFehler = fehler;
} else {
stand.prognose = leseVorhersage( antwort, satz );
if ( !stand.prognose ) {
stand.prognoseFehler = 'Die Vorhersagedaten waren unvollständig.';
}
}
fertigMelden();
} );
}
}
/* Fragt der Reihe nach die Parametersätze an; bei Status 400 wird der
* nächste Satz versucht (falls sich Parameternamen der API ändern). */
function ladeMitVarianten( saetze, baueUrl, fertig ) {
var i = 0;
function versuch() {
$.getJSON( baueUrl( saetze[ i ] ) )
.done( function ( antwort ) {
fertig( null, antwort, saetze[ i ] );
} )
.fail( function ( x ) {
if ( x && x.status === 400 && i + 1 < saetze.length ) {
i += 1;
versuch();
return;
}
fertig( netzFehlerText( x ), null, null );
} );
}
versuch();
}
function netzFehlerText( x ) {
if ( !x || x.status === 0 ) {
return 'Der Browser hat die Anfrage blockiert (etwa wegen CORS) ' +
'oder es besteht keine Internetverbindung.';
}
return 'Der GeoSphere-Server antwortete mit Status ' + x.status + '.';
}
/* ------------------------------------------------------------------
* API-Antworten auswerten
* ------------------------------------------------------------------ */
/* Liefert das Parameterobjekt (mit data/unit) aus einer GeoJSON-
* Antwort, tolerant gegenüber Gross-/Kleinschreibung und Struktur. */
function holeParameter( antwort, name ) {
var feature = antwort && antwort.features && antwort.features[ 0 ];
var params = feature && feature.properties && feature.properties.parameters;
var schluessel, i;
if ( !params ) {
return null;
}
if ( $.isArray( params ) ) {
for ( i = 0; i < params.length; i++ ) {
if ( params[ i ] && String( params[ i ].name ).toLowerCase() ===
name.toLowerCase() ) {
return params[ i ];
}
}
return null;
}
if ( params[ name ] ) {
return params[ name ];
}
for ( schluessel in params ) {
if ( schluessel.toLowerCase() === name.toLowerCase() ) {
return params[ schluessel ];
}
}
return null;
}
function holeZeiten( antwort ) {
var roh = ( antwort && antwort.timestamps ) || [];
var zeiten = [];
var i, d;
for ( i = 0; i < roh.length; i++ ) {
d = new Date( roh[ i ] );
zeiten.push( isFinite( d.getTime() ) ? d : null );
}
return zeiten;
}
function zahlOderNull( wert ) {
var z = parseFloat( wert );
return isFinite( z ) ? z : null;
}
/* Aktuelle Messwerte: jeweils der letzte Eintrag der Zeitreihe. */
function leseMesswerte( antwort, satz ) {
var zeiten = holeZeiten( antwort );
var idx = zeiten.length - 1;
var werte = {};
var gefunden = false;
var i, p, wert;
if ( idx < 0 ) {
return null;
}
for ( i = 0; i < satz.length; i++ ) {
p = holeParameter( antwort, satz[ i ] );
wert = ( p && p.data ) ? zahlOderNull( p.data[ idx ] ) : null;
werte[ satz[ i ] ] = wert;
if ( wert !== null ) {
gefunden = true;
}
}
return gefunden ? { werte: werte, zeit: zeiten[ idx ] } : null;
}
/* Vorhersage: stündliche Werte zu Tagen (Wiener Zeit) bündeln. */
function leseVorhersage( antwort, satz ) {
var zeiten = holeZeiten( antwort );
var temperatur = holeParameter( antwort, 't2m' );
var tDaten = temperatur && temperatur.data;
var regenName = $.inArray( 'rr_acc', satz ) !== -1 ? 'rr_acc' :
( $.inArray( 'rr', satz ) !== -1 ? 'rr' : null );
var kumuliert = regenName === 'rr_acc';
var regen = regenName ? holeParameter( antwort, regenName ) : null;
var rDaten = regen && regen.data;
var inKelvin = false;
var proTag = {};
var reihenfolge = [];
var ab = Date.now() - 45 * 60000; /* laufende Stunde noch mitnehmen */
var i, t, r, vorher, schluessel, eintrag, tage, tag;
if ( !tDaten || !tDaten.length || !zeiten.length ) {
return null;
}
inKelvin = istKelvin( temperatur, tDaten );
for ( i = 0; i < zeiten.length && i < tDaten.length; i++ ) {
if ( !zeiten[ i ] ) {
continue;
}
/* Niederschlag der Stunde: bei kumulierten Werten Differenz
* zum vorigen Zeitschritt, sonst der Wert selbst. */
r = null;
if ( rDaten ) {
if ( kumuliert ) {
vorher = i > 0 ? zahlOderNull( rDaten[ i - 1 ] ) : 0;
r = zahlOderNull( rDaten[ i ] );
r = ( r === null || vorher === null ) ? null :
Math.max( 0, r - vorher );
} else {
r = zahlOderNull( rDaten[ i ] );
}
}
if ( zeiten[ i ].getTime() < ab ) {
continue; /* Vergangenheit erst nach der Differenzbildung verwerfen */
}
t = zahlOderNull( tDaten[ i ] );
if ( t !== null && inKelvin ) {
t -= 273.15;
}
schluessel = tagesSchluessel( zeiten[ i ] );
if ( !proTag[ schluessel ] ) {
proTag[ schluessel ] = {
ersteZeit: zeiten[ i ], min: null, max: null,
regen: null, stunden: 0
};
reihenfolge.push( schluessel );
}
eintrag = proTag[ schluessel ];
eintrag.stunden += 1;
if ( t !== null ) {
eintrag.min = ( eintrag.min === null ) ? t : Math.min( eintrag.min, t );
eintrag.max = ( eintrag.max === null ) ? t : Math.max( eintrag.max, t );
}
if ( r !== null ) {
eintrag.regen = ( eintrag.regen === null ) ? r : eintrag.regen + r;
}
}
tage = [];
for ( i = 0; i < reihenfolge.length && tage.length < MAX_TAGE; i++ ) {
tag = proTag[ reihenfolge[ i ] ];
if ( tag.stunden >= 3 && tag.min !== null ) {
tage.push( {
name: tagesName( tag.ersteZeit, reihenfolge[ i ] ),
min: tag.min,
max: tag.max,
regen: tag.regen
} );
}
}
return tage.length ? { tage: tage } : null;
}
function istKelvin( parameter, daten ) {
var einheit = parameter && parameter.unit ? String( parameter.unit ) : '';
var i, w;
if ( /^k(elvin)?$/i.test( $.trim( einheit ) ) ) {
return true;
}
if ( /c/i.test( einheit ) ) {
return false;
}
for ( i = 0; i < daten.length; i++ ) {
w = zahlOderNull( daten[ i ] );
if ( w !== null ) {
return w > 150; /* plausible Celsiuswerte liegen weit darunter */
}
}
return false;
}
function tagesSchluessel( datum ) {
try {
return datum.toLocaleDateString( 'de-AT', { timeZone: ZEITZONE } );
} catch ( e ) {
return datum.toDateString();
}
}
function tagesName( datum, schluessel ) {
var heute = tagesSchluessel( new Date() );
var morgen = tagesSchluessel( new Date( Date.now() + 86400000 ) );
if ( schluessel === heute ) {
return 'Heute';
}
if ( schluessel === morgen ) {
return 'Morgen';
}
try {
return datum.toLocaleDateString( 'de-AT',
{ weekday: 'long', timeZone: ZEITZONE } );
} catch ( e ) {
return schluessel;
}
}
function uhrzeit( datum ) {
try {
return datum.toLocaleTimeString( 'de-AT',
{ hour: '2-digit', minute: '2-digit', timeZone: ZEITZONE } );
} catch ( e ) {
return '';
}
}
/* ------------------------------------------------------------------
* Darstellung
* ------------------------------------------------------------------ */
function zeichneWidget( $box, cfg, stand ) {
$box.empty().append( titelZeile( cfg ) );
/* --- Abschnitt: aktuelle Messwerte --- */
if ( stand.mess ) {
$box.append( messwerteBlock( cfg, stand.mess ) );
} else if ( cfg.mitMesswerten || stand.messFehler ) {
$box.append( fehlerZeile( 'Aktuelle Messwerte nicht verfügbar: ' +
stand.messFehler ) );
}
/* --- Abschnitt: Vorhersage --- */
if ( stand.prognose ) {
$box.append( trennlinie(), vorhersageBlock( stand.prognose ) );
} else if ( cfg.mitVorhersage ) {
$box.append( trennlinie(), fehlerZeile( 'Vorhersage nicht verfügbar: ' +
stand.prognoseFehler ) );
}
$box.append(
$( '<div>' )
.css( { 'margin-top': '0.6em', 'font-size': '80%', color: '#54595d' } )
.text( 'Datenquelle: GeoSphere Austria (CC BY 4.0)' +
( stand.prognose ? ' · Vorhersage: AROME-Modell' : '' ) )
);
}
function messwerteBlock( cfg, mess ) {
var w = mess.werte;
var $block = $( '<div>' );
var zeilen = [];
var windKmh, boeenKmh, stationsZeile;
if ( w.TL !== null && w.TL !== undefined ) {
$block.append(
$( '<div>' )
.css( { 'font-size': '2.1em', 'font-weight': 'bold',
'line-height': '1.15' } )
.text( formatZahl( w.TL, 1 ) + ' °C' )
);
}
if ( istZahl( w.FFAM ) ) {
windKmh = Math.round( w.FFAM * 3.6 );
boeenKmh = istZahl( w.FFX ) ? Math.round( w.FFX * 3.6 ) : null;
zeilen.push( 'Wind: ' + windKmh + ' km/h' +
( istZahl( w.DD ) ? ' aus ' + windrichtung( w.DD ) : '' ) +
( boeenKmh !== null && boeenKmh > windKmh + 5 ?
', Böen ' + boeenKmh + ' km/h' : '' ) );
}
if ( istZahl( w.RF ) ) {
zeilen.push( 'Luftfeuchtigkeit: ' + Math.round( w.RF ) + ' %' );
}
if ( istZahl( w.RR ) && w.RR >= 0.05 ) {
zeilen.push( 'Niederschlag: ' + formatZahl( w.RR, 1 ) +
' mm in 10 Minuten' );
}
if ( istZahl( w.P0 ) ) {
zeilen.push( 'Luftdruck: ' + Math.round( w.P0 ) + ' hPa' );
}
if ( istZahl( w.SH ) && w.SH >= 1 ) {
zeilen.push( 'Schneehöhe: ' + Math.round( w.SH ) + ' cm' );
}
$.each( zeilen, function ( i, text ) {
$block.append( $( '<div>' ).css( { 'font-size': '92%',
margin: '0.15em 0' } ).text( text ) );
} );
stationsZeile = 'Station ' + ( cfg.stationsname || 'Nr. ' + cfg.station );
if ( cfg.stationshoehe ) {
stationsZeile += ', ' + cfg.stationshoehe + ' m';
}
if ( mess.zeit ) {
stationsZeile = 'Gemessen um ' + uhrzeit( mess.zeit ) + ' Uhr · ' +
stationsZeile;
}
$block.append( $( '<div>' ).css( kleinGrau() ).text( stationsZeile ) );
return $block;
}
function vorhersageBlock( prognose ) {
var $block = $( '<div>' );
var $reihe = $( '<div>' ).css( { display: 'flex', 'flex-wrap': 'wrap' } );
$block.append(
$( '<div>' )
.css( { 'font-weight': 'bold', 'font-size': '92%',
'margin-bottom': '0.3em' } )
.text( 'Vorhersage (Höchst- / Tiefstwert)' )
);
$.each( prognose.tage, function ( i, tag ) {
var regenText = ( tag.regen === null ) ? '' :
( tag.regen < 0.05 ? 'trocken' :
formatZahl( tag.regen, 1 ) + ' mm' );
var $zelle = $( '<div>' ).css( { flex: '1 1 6em',
'min-width': '6em', margin: '0.15em 0' } );
$zelle.append(
$( '<div>' ).css( { 'font-size': '88%', 'font-weight': 'bold' } )
.text( tag.name ),
$( '<div>' ).text( Math.round( tag.max ) + '° / ' +
Math.round( tag.min ) + '°' )
);
if ( regenText ) {
$zelle.append( $( '<div>' ).css( { 'font-size': '85%',
color: '#54595d' } ).text( regenText ) );
}
$reihe.append( $zelle );
} );
$block.append( $reihe );
return $block;
}
function titelZeile( cfg ) {
return $( '<div>' )
.css( { 'font-weight': 'bold', 'font-size': '108%',
'margin-bottom': '0.35em' } )
.text( 'Wetter in ' + ( cfg.ort || 'der Region' ) );
}
function trennlinie() {
return $( '<div>' ).css( { 'border-top': '1px solid #c8ccd1',
margin: '0.6em 0' } );
}
function fehlerZeile( text ) {
return $( '<div>' ).css( { 'font-size': '88%', color: '#b32424',
margin: '0.3em 0' } ).text( text );
}
function gestalteBox( $box ) {
$box.css( {
border: '1px solid #a2a9b1',
'border-radius': '2px',
background: '#f8f9fa',
padding: '0.8em 1em',
'max-width': '26em',
'box-sizing': 'border-box'
} );
}
function kleinGrau() {
return { 'font-size': '85%', color: '#54595d', 'margin-top': '0.35em' };
}
/* ------------------------------------------------------------------
* Kleine Helfer
* ------------------------------------------------------------------ */
function istZahl( wert ) {
return typeof wert === 'number' && isFinite( wert );
}
function formatZahl( wert, nachkomma ) {
return wert.toFixed( nachkomma ).replace( '.', ',' ).replace( '-0,0', '0,0' );
}
function windrichtung( grad ) {
var richtungen = [ 'Nord', 'Nordost', 'Ost', 'Südost',
'Süd', 'Südwest', 'West', 'Nordwest' ];
return richtungen[ Math.round( grad / 45 ) % 8 ];
}
}() );
Code-Block D: MediaWiki:Gadget-WetterGenerator.js
/**
* MediaWiki:Gadget-WetterGenerator.js (Ausbaustufe 3)
* =====================================================================
* Formular zum Erstellen von Wetterwidget-Bausteinen für Gemeindeseiten
* – jetzt zusätzlich mit:
*
* A) WIKIMAP-EXPORT: Ein Klick holt die aktuelle Stationsliste live von
* GeoSphere und erzeugt fertige Einträge für WikiMap-Orte.json
* (Kategorie "Wetterstation", einstellbarer Umkreis, leer = ganz
* Österreich). Die Einträge werden in die Liste "orte" der Datei
* kopiert – am besten VOR einen bestehenden Eintrag, dann stimmen
* die Kommas. Einmalig im Block "kategorien" ergänzen:
* "Wetterstation": "#2a6fb8"
*
* B) KARTEN-RÜCKSPRUNG: Die exportierten Einträge verweisen im Feld
* "seite" auf diese Generator-Seite samt Adress-Parametern
* (ws_station, ws_name, ws_lat, ws_lon, ws_hoehe). Öffnet jemand
* einen Stationsmarker auf der WikiMap und folgt dem Link, steht
* hier sofort der fertige {{Wetterwidget|...}}-Baustein bereit.
*
* C) WETTERLEISTE: Das Formular gibt (abwählbar per Häkchen) vor dem
* Wetterwidget-Baustein auch den Aufruf der [[Vorlage:Wetterleiste]]
* aus – die Button-Zeile mit Wetter-, Radar-, Wolken-, Nachthimmel-,
* Pollen- und Warnungs-Link. Die Dienst-Adressen stecken in der
* Vorlage (eine Stelle für Änderungen); das Formular liefert nur
* die Werte, weitere Eingaben braucht die Leiste nicht. Sonstige
* Links der Gemeindeseite (Website, Wikipedia usw.) bleiben bewusst
* außen vor und werden wie bisher von Hand gepflegt. Beim Karten-
* Rücksprung (B) ist das Häkchen automatisch abgewählt, denn eine
* Station ist keine Gemeinde.
*
* D) KOORDINATEN-EINGABE: Ein einziges Feld für beide Koordinaten,
* direkt aus OSM oder OrganicMaps kopierbar ("47.67475, 16.01918");
* auch Dezimalkomma oder die komplette OSM-Adresse funktionieren.
*
* Datenquelle: GeoSphere Austria Dataset-API (Open Data, CC BY 4.0),
* kein API-Schlüssel nötig. Doku: https://dataset.api.hub.geosphere.at/v1/docs/
*
* EINRICHTUNG: unverändert gegenüber Ausbaustufe 1 – dieser Code ersetzt
* einfach den bisherigen Inhalt von [[MediaWiki:Gadget-WetterGenerator.js]].
* Der Eintrag in [[MediaWiki:Gadgets-definition]] bleibt gleich:
* * WetterGenerator[ResourceLoader|default|hidden]|WetterGenerator.js
* Die Generator-Seite enthält weiterhin nur:
* <div id="wetter-generator"></div>
* =====================================================================
*/
( function () {
'use strict';
/* Stationsliste der 10-Minuten-Messstationen (TAWES) samt Metadaten */
var META_URL = 'https://dataset.api.hub.geosphere.at/v1/' +
'station/current/tawes-v1-10min/metadata';
/* So viele nächstgelegene Stationen werden zur Auswahl angeboten */
var MAX_STATIONEN = 5;
/* Bezugspunkt für den Export-Umkreis: grobe Mitte des Schwarzatals
* (Raum Payerbach/Gloggnitz). */
var EXPORT_ZENTRUM = { lat: 47.70, lon: 15.95 };
var EXPORT_RADIUS_VORGABE = '50';
var stationen = null; /* geladene Stationsliste (wird nur 1x geholt) */
var topStationen = []; /* Ergebnis der letzten Suche, sortiert */
var letzteSuche = null; /* { lat, lon } der letzten Suche */
var el = {}; /* Referenzen auf die Formularelemente */
$( function () {
var container = document.getElementById( 'wetter-generator' );
if ( container ) {
baueFormular( $( container ) );
uebernimmAusKarte();
}
} );
/* ------------------------------------------------------------------
* Formular aufbauen
* ------------------------------------------------------------------ */
function baueFormular( $container ) {
el.ort = $( '<input>' )
.attr( { type: 'text', placeholder: 'z. B. Reichenau an der Rax' } )
.css( 'width', '20em' )
.on( 'input', aktualisiereBaustein );
/* Ein Feld für beide Koordinaten – direkt aus OSM kopierbar */
el.koordinaten = $( '<input>' )
.attr( { type: 'text', placeholder: 'z. B. 47.67475, 16.01918' } )
.css( { width: '100%', 'max-width': '20em' } );
el.mitLeiste = $( '<input>' )
.attr( { type: 'checkbox' } )
.prop( 'checked', true )
.on( 'change', aktualisiereBaustein );
el.suchen = $( '<button>' )
.attr( 'type', 'button' )
.text( 'Stationen suchen' )
.on( 'click', sucheStationen );
el.status = $( '<div>' ).css( 'margin', '0.7em 0' );
el.liste = $( '<div>' ).css( 'margin', '0.5em 0' );
el.ausgabe = $( '<textarea>' )
.attr( { rows: 8, readonly: 'readonly' } )
.css( textFeldStil() );
el.kopieren = $( '<button>' )
.attr( 'type', 'button' )
.text( 'Baustein kopieren' )
.css( 'display', 'none' )
.on( 'click', function () {
kopiereFeld( el.ausgabe, setzeStatus,
'Baustein kopiert – jetzt auf der Zielseite einfügen.' );
} );
/* --- Export-Bereich (WikiMap) --- */
el.exportRadius = $( '<input>' )
.attr( { type: 'text' } )
.val( EXPORT_RADIUS_VORGABE )
.css( 'width', '6em' );
el.exportKnopf = $( '<button>' )
.attr( 'type', 'button' )
.text( 'Stationen exportieren' )
.on( 'click', exportiereStationen );
el.exportStatus = $( '<div>' ).css( 'margin', '0.7em 0' );
el.exportAusgabe = $( '<textarea>' )
.attr( { rows: 10, readonly: 'readonly' } )
.css( textFeldStil() );
el.exportKopieren = $( '<button>' )
.attr( 'type', 'button' )
.text( 'Einträge kopieren' )
.css( 'display', 'none' )
.on( 'click', function () {
kopiereFeld( el.exportAusgabe, setzeExportStatus,
'Einträge kopiert – jetzt in WikiMap-Orte.json einfügen.' );
} );
el.exportHinweis = $( '<div>' )
.css( { 'margin-top': '0.5em', 'font-size': '85%', color: '#54595d',
display: 'none' } )
.text( 'Einmalig im Block "kategorien" von WikiMap-Orte.json ' +
'ergänzen: "Wetterstation": "#2a6fb8" – dann bekommen die ' +
'Stationen ihre eigene Pinfarbe und ihren Filter.' );
$container.empty().append(
kasten().append(
$( '<p>' ).text(
'Ort und Koordinaten eingeben – das Formular sucht die ' +
'nächstgelegenen GeoSphere-Wetterstationen und erstellt ' +
'den fertigen Baustein für die Gemeindeseite.'
),
feld( 'Ortsname', el.ort ),
feld( 'Koordinaten „Breite, Länge" (direkt aus OSM kopierbar)',
el.koordinaten ),
$( '<label>' )
.css( { display: 'block', margin: '0.6em 0' } )
.append( el.mitLeiste, document.createTextNode(
' Wetterleiste (Button-Zeile) mit ausgeben' ) ),
$( '<div>' ).css( 'margin', '0.7em 0' ).append( el.suchen ),
el.status,
el.liste,
el.ausgabe,
$( '<div>' ).css( 'margin', '0.5em 0' ).append( el.kopieren )
),
kasten().css( 'margin-top', '1em' ).append(
$( '<div>' )
.css( { 'font-weight': 'bold', 'margin-bottom': '0.3em' } )
.text( 'Stationen für die WikiMap' ),
$( '<p>' ).css( 'margin', '0.3em 0' ).text(
'Erzeugt fertige Einträge für WikiMap-Orte.json – alle ' +
'derzeit aktiven Wetterstationen im gewählten Umkreis. ' +
'Der Klick auf einen Stationsmarker der Karte führt ' +
'danach hierher zurück und liefert sofort den passenden ' +
'Widget-Baustein.'
),
feld( 'Umkreis in km (leer = ganz Österreich)', el.exportRadius ),
$( '<div>' ).css( 'margin', '0.7em 0' ).append( el.exportKnopf ),
el.exportStatus,
el.exportAusgabe,
$( '<div>' ).css( 'margin', '0.5em 0' ).append( el.exportKopieren ),
el.exportHinweis
),
$( '<div>' )
.css( { 'margin-top': '0.8em', 'font-size': '85%', color: '#54595d' } )
.text( 'Stationsdaten: GeoSphere Austria, Dataset-API (CC BY 4.0)' )
);
}
function kasten() {
return $( '<div>' ).css( {
border: '1px solid #a2a9b1',
background: '#f8f9fa',
padding: '1em',
'max-width': '42em'
} );
}
function feld( beschriftung, $eingabe ) {
return $( '<div>' ).css( 'margin', '0.5em 0' ).append(
$( '<label>' )
.css( { display: 'block', 'font-weight': 'bold' } )
.text( beschriftung ),
$eingabe
);
}
function textFeldStil() {
return {
width: '100%',
'max-width': '38em',
'font-family': 'monospace',
display: 'none'
};
}
/* ------------------------------------------------------------------
* Stationsliste laden (gemeinsam für Suche und Export)
* ------------------------------------------------------------------ */
function ladeStationsliste( beiErfolg, beiFehler ) {
if ( stationen ) {
beiErfolg();
return;
}
$.getJSON( META_URL )
.done( function ( daten ) {
stationen = lesbareStationen( daten );
if ( !stationen.length ) {
stationen = null;
beiFehler( 'Die Antwort der GeoSphere-API enthielt keine ' +
'Stationen. Bitte später erneut versuchen.' );
return;
}
beiErfolg();
} )
.fail( function ( antwort ) {
beiFehler( ladeFehlerText( antwort ) );
} );
}
function ladeFehlerText( antwort ) {
if ( !antwort || antwort.status === 0 ) {
return 'Die Stationsliste konnte nicht geladen werden: Der Browser ' +
'hat die Anfrage blockiert oder es besteht keine ' +
'Internetverbindung.';
}
return 'Die Stationsliste konnte nicht geladen werden (Serverantwort ' +
antwort.status + '). Bitte später erneut versuchen.';
}
/* API-Antwort in eine einheitliche Stationsliste umwandeln */
function lesbareStationen( daten ) {
var roh = ( daten && daten.stations ) || [];
var ergebnis = [];
$.each( roh, function ( i, st ) {
var lat = holeZahl( st, [ 'lat', 'latitude' ] );
var lon = holeZahl( st, [ 'lon', 'longitude' ] );
var name;
if ( lat === null || lon === null ) {
return; /* ohne Koordinaten unbrauchbar */
}
if ( st.is_active === false ) {
return; /* stillgelegte Stationen ausblenden */
}
name = String( st.name || 'Unbenannte Station' );
ergebnis.push( {
id: String( st.id ),
name: name,
anzeige: huebscherName( name ),
bundesland: st.state ? String( st.state ) : '',
lat: lat,
lon: lon,
hoehe: holeZahl( st, [ 'altitude', 'elevation' ] )
} );
} );
return ergebnis;
}
/* ------------------------------------------------------------------
* Einzel-Suche (unverändertes Verhalten)
* ------------------------------------------------------------------ */
function sucheStationen() {
var paar = parseKoordinatenPaar( el.koordinaten.val() );
if ( !$.trim( el.ort.val() ) ) {
setzeStatus( 'Bitte zuerst den Ortsnamen eintragen – er steckt in ' +
'mehreren Links der Wetterleiste.', true );
return;
}
if ( !paar ) {
setzeStatus( 'Bitte Koordinaten als „Breite, Länge" eingeben – ' +
'am einfachsten direkt aus OSM kopiert, z. B. ' +
'47.67475, 16.01918.', true );
return;
}
letzteSuche = { lat: paar.lat, lon: paar.lon };
setzeStatus( 'Stationsliste wird geladen …', false );
ladeStationsliste( werteAus, function ( fehlerText ) {
setzeStatus( fehlerText, true );
} );
}
/* Nimmt beide Koordinaten aus einem Feld entgegen: "47.67475, 16.01918"
* (direkt aus OSM kopiert), mit Dezimalkomma "47,6969 15,8446" oder
* gleich die ganze OSM-Adresse mit #map=14/47.6/16.0 – es zählen die
* letzten beiden Zahlen im Text. Steht die Länge versehentlich vor der
* Breite, wird getauscht; in Österreich überschneiden sich die
* Wertebereiche (Breite 46–49, Länge 9–17,5) nicht. */
function parseKoordinatenPaar( text ) {
var token = String( text || '' ).match( /-?\d+[.,]\d+|-?\d+/g ) || [];
var lat, lon, tausch;
if ( token.length < 2 ) {
return null;
}
lat = parseKoordinate( token[ token.length - 2 ] );
lon = parseKoordinate( token[ token.length - 1 ] );
if ( lat === null || lon === null ) {
return null;
}
if ( lat >= 9 && lat <= 17.5 && lon >= 46 && lon <= 49.1 ) {
tausch = lat;
lat = lon;
lon = tausch;
}
return { lat: lat, lon: lon };
}
function werteAus() {
var i;
for ( i = 0; i < stationen.length; i++ ) {
stationen[ i ].entfernung = entfernungKm(
letzteSuche.lat, letzteSuche.lon,
stationen[ i ].lat, stationen[ i ].lon
);
}
topStationen = stationen.slice().sort( function ( a, b ) {
return a.entfernung - b.entfernung;
} ).slice( 0, MAX_STATIONEN );
var hinweis = 'Nächstgelegene Stationen – die erste ist vorausgewählt:';
if ( !imBereichOesterreich( letzteSuche.lat, letzteSuche.lon ) ) {
hinweis = 'Achtung: Die Koordinaten liegen außerhalb Österreichs – ' +
'bitte prüfen. ' + hinweis;
}
setzeStatus( hinweis, false );
el.liste.empty();
$.each( topStationen, function ( idx, st ) {
el.liste.append( radioZeile( idx, st.anzeige +
( st.bundesland ? ' (' + st.bundesland + ')' : '' ) +
' – ' + formatKm( st.entfernung ) + ' entfernt, ' +
( st.hoehe !== null ?
Math.round( st.hoehe ) + ' m Seehöhe' : 'Höhe unbekannt' ),
idx === 0 ) );
} );
el.ausgabe.show();
el.kopieren.show();
aktualisiereBaustein();
}
function radioZeile( idx, beschreibung, angehakt ) {
return $( '<label>' )
.css( { display: 'block', margin: '0.25em 0' } )
.append(
$( '<input>' )
.attr( { type: 'radio', name: 'wg-station' } )
.val( idx )
.prop( 'checked', angehakt )
.on( 'change', aktualisiereBaustein ),
document.createTextNode( ' ' + beschreibung )
);
}
/* ------------------------------------------------------------------
* Rücksprung von der WikiMap: ?ws_station=...&ws_lat=...
* ------------------------------------------------------------------ */
function uebernimmAusKarte() {
var id = leseUrlParameter( 'ws_station' );
var lat = parseKoordinate( leseUrlParameter( 'ws_lat' ) || '' );
var lon = parseKoordinate( leseUrlParameter( 'ws_lon' ) || '' );
var name, hoehe;
if ( !id || lat === null || lon === null ) {
return;
}
name = leseUrlParameter( 'ws_name' ) || ( 'Nr. ' + id );
hoehe = parseKoordinate( leseUrlParameter( 'ws_hoehe' ) || '' );
el.ort.val( name );
el.koordinaten.val( String( lat ) + ', ' + String( lon ) );
/* Eine Station ist keine Gemeinde: Leiste hier nicht mitausgeben. */
el.mitLeiste.prop( 'checked', false );
letzteSuche = { lat: lat, lon: lon };
topStationen = [ {
id: id, name: name, anzeige: name, bundesland: '',
lat: lat, lon: lon, hoehe: hoehe, entfernung: 0
} ];
setzeStatus( 'Station aus der Karte übernommen – der Baustein steht ' +
'unten bereit. Für andere Vorschläge einfach „Stationen suchen" ' +
'klicken.', false );
el.liste.empty().append( radioZeile( 0, name +
( hoehe !== null ? ', ' + Math.round( hoehe ) + ' m Seehöhe' : '' ) +
' (aus der Karte übernommen)', true ) );
el.ausgabe.show();
el.kopieren.show();
aktualisiereBaustein();
}
function leseUrlParameter( name ) {
var treffer = new RegExp( '[?&]' + name + '=([^&#]*)' )
.exec( location.search );
if ( !treffer ) {
return null;
}
try {
return decodeURIComponent( treffer[ 1 ].replace( /\+/g, ' ' ) );
} catch ( e ) {
return null;
}
}
/* ------------------------------------------------------------------
* Export für WikiMap-Orte.json
* ------------------------------------------------------------------ */
function exportiereStationen() {
var radiusText = $.trim( el.exportRadius.val() );
var radius = radiusText === '' ? null : parseKoordinate( radiusText );
if ( radiusText !== '' && ( radius === null || radius <= 0 ) ) {
setzeExportStatus( 'Bitte einen Umkreis in Kilometern angeben – ' +
'oder das Feld leeren für ganz Österreich.', true );
return;
}
setzeExportStatus( 'Stationsliste wird geladen …', false );
ladeStationsliste( function () {
baueExport( radius );
}, function ( fehlerText ) {
setzeExportStatus( fehlerText, true );
} );
}
function baueExport( radius ) {
var treffer = [];
var zeilen = [];
var i, st;
for ( i = 0; i < stationen.length; i++ ) {
st = stationen[ i ];
if ( radius === null || entfernungKm( EXPORT_ZENTRUM.lat,
EXPORT_ZENTRUM.lon, st.lat, st.lon ) <= radius ) {
treffer.push( st );
}
}
if ( !treffer.length ) {
setzeExportStatus( 'Keine Station im gewählten Umkreis gefunden – ' +
'bitte den Umkreis vergrößern.', true );
return;
}
treffer.sort( function ( a, b ) {
return a.anzeige.localeCompare( b.anzeige );
} );
for ( i = 0; i < treffer.length; i++ ) {
st = treffer[ i ];
zeilen.push( ' { "id": ' +
JSON.stringify( 'Wetterstation ' + st.anzeige ) +
', "lat": ' + st.lat.toFixed( 4 ) +
', "lon": ' + st.lon.toFixed( 4 ) +
', "kategorie": "Wetterstation"' +
', "seite": ' + JSON.stringify( urlFuerStation( st ) ) +
' },' );
}
el.exportAusgabe.val( zeilen.join( '\n' ) ).show();
el.exportKopieren.show();
el.exportHinweis.show();
setzeExportStatus( treffer.length + ' Stationen bereit. Den Block in ' +
'die Liste "orte" von WikiMap-Orte.json einfügen – am besten VOR ' +
'einen bestehenden Eintrag, dann stimmen die Kommas. Die ' +
'JSON-Seite meldet beim Speichern selbst, falls eines fehlt.',
false );
}
/* Absolute Adresse dieser Generator-Seite samt Stations-Parametern.
* Absolut deshalb, weil die WikiMap externe Ziele (http/https)
* unverändert übernimmt – Wiki-Seitennamen dagegen könnten keine
* Parameter tragen. */
function urlFuerStation( st ) {
return location.protocol + '//' + location.host +
mw.config.get( 'wgScript' ) +
'?title=' + encodeURIComponent( mw.config.get( 'wgPageName' ) ) +
'&ws_station=' + encodeURIComponent( st.id ) +
'&ws_name=' + encodeURIComponent( st.anzeige ) +
'&ws_lat=' + st.lat.toFixed( 4 ) +
'&ws_lon=' + st.lon.toFixed( 4 ) +
( st.hoehe !== null ? '&ws_hoehe=' + Math.round( st.hoehe ) : '' );
}
/* ------------------------------------------------------------------
* Baustein erzeugen und kopieren
* ------------------------------------------------------------------ */
function aktualisiereBaustein() {
var gewaehlt = el.liste.find( 'input:checked' ).val();
if ( gewaehlt === undefined || !letzteSuche ) {
return;
}
var st = topStationen[ parseInt( gewaehlt, 10 ) ];
if ( !st ) {
return;
}
var text = '';
if ( el.mitLeiste.prop( 'checked' ) ) {
text += baueLeiste() + '\n';
}
text += '{{Wetterwidget\n' +
'|ort = ' + $.trim( el.ort.val() ) + '\n' +
'|lat = ' + letzteSuche.lat.toFixed( 4 ) + '\n' +
'|lon = ' + letzteSuche.lon.toFixed( 4 ) + '\n' +
'|station = ' + st.id + '\n' +
'|stationsname = ' + ( st.anzeige || st.name ) + '\n' +
'|stationshoehe = ' + ( st.hoehe !== null ? Math.round( st.hoehe ) : '' ) + '\n' +
'}}';
el.ausgabe.val( text );
}
/* Aufruf der [[Vorlage:Wetterleiste]]. Die Dienst-Adressen stecken
* in der Vorlage – hier werden nur die Werte geliefert. */
function baueLeiste() {
var ort = $.trim( el.ort.val() );
var lat = letzteSuche.lat.toFixed( 4 );
var lon = letzteSuche.lon.toFixed( 4 );
return '{{Wetterleiste\n' +
'|ort = ' + ort + '\n' +
'|lat = ' + lat + '\n' +
'|lon = ' + lon + '\n' +
'|pollenloc = ' + pollenLoc( ort, lat, lon ) + '\n' +
'}}';
}
/* Der MSN-Pollenlink erwartet die Ortsangabe als base64-codiertes
* JSON im loc-Parameter. Wikitext kann nicht base64-codieren, darum
* kocht das Formular den Wert vor. Das Bundesland ist fest auf
* Niederösterreich gesetzt (alle Schwarzatal-Gemeinden liegen dort).
* +, / und = werden URL-sicher ersetzt. */
function pollenLoc( ort, lat, lon ) {
var daten = {
l: ort, r: 'Niederösterreich', c: 'Österreich',
i: 'AT', g: 'de-de', x: String( lon ), y: String( lat )
};
try {
return btoa( unescape( encodeURIComponent(
JSON.stringify( daten ) ) ) )
.replace( /\+/g, '%2B' )
.replace( /\//g, '%2F' )
.replace( /=/g, '%3D' );
} catch ( e ) {
return '';
}
}
function kopiereFeld( $feld, statusSetzer, erfolgsMeldung ) {
var text = $feld.val();
if ( !text ) {
statusSetzer( 'Noch nichts zu kopieren vorhanden.', true );
return;
}
function fallback() {
try {
$feld[ 0 ].select();
document.execCommand( 'copy' );
statusSetzer( erfolgsMeldung, false );
} catch ( e ) {
statusSetzer( 'Kopieren nicht möglich – bitte den Text im ' +
'Feld markieren und manuell kopieren.', true );
}
}
if ( navigator.clipboard && navigator.clipboard.writeText ) {
navigator.clipboard.writeText( text ).then( function () {
statusSetzer( erfolgsMeldung, false );
}, fallback );
} else {
fallback();
}
}
/* ------------------------------------------------------------------
* Kleine Helfer
* ------------------------------------------------------------------ */
/* GROSSSCHREIBUNG der API hübsch umwandeln: "WIEN/HOHE WARTE" wird zu
* "Wien/Hohe Warte", "LUNZ AM SEE" zu "Lunz am See". Umlaut-
* Umschreibungen (OE/UE/AE) werden nur zurückgewandelt, wenn kein
* Vokal davorsteht – so bleibt "Feuerkogel" verschont. Heuristik:
* Einzelfälle lassen sich im JSON-Export nachträglich anpassen. */
function huebscherName( roh ) {
var fuellwoerter = [ 'am', 'an', 'im', 'in', 'bei', 'ob', 'auf',
'unter', 'der', 'die', 'das', 'und', 'zu' ];
var text = String( roh || '' ).toLowerCase()
.replace( /(^|[^aeiouq])ae/g, '$1ä' )
.replace( /(^|[^aeiouq])oe/g, '$1ö' )
.replace( /(^|[^aeiouq])ue/g, '$1ü' )
.replace( /(^|[\s\/\-.(])([a-zäöü])/g, function ( ganz, davor, buchstabe ) {
return davor + buchstabe.toUpperCase();
} );
var teile = text.split( ' ' );
var i;
for ( i = 1; i < teile.length; i++ ) {
if ( $.inArray( teile[ i ].toLowerCase(), fuellwoerter ) !== -1 ) {
teile[ i ] = teile[ i ].toLowerCase();
}
}
return teile.join( ' ' );
}
function parseKoordinate( text ) {
var bereinigt = String( text || '' )
.replace( /,/g, '.' )
.replace( /[^0-9.\-]/g, '' );
var wert = parseFloat( bereinigt );
return isFinite( wert ) ? wert : null;
}
function holeZahl( objekt, namen ) {
var i, wert;
for ( i = 0; i < namen.length; i++ ) {
wert = parseFloat( objekt[ namen[ i ] ] );
if ( isFinite( wert ) ) {
return wert;
}
}
return null;
}
/* Luftlinie in Kilometern (Haversine-Formel) */
function entfernungKm( lat1, lon1, lat2, lon2 ) {
var rad = Math.PI / 180;
var dLat = ( lat2 - lat1 ) * rad;
var dLon = ( lon2 - lon1 ) * rad;
var a = Math.sin( dLat / 2 ) * Math.sin( dLat / 2 ) +
Math.cos( lat1 * rad ) * Math.cos( lat2 * rad ) *
Math.sin( dLon / 2 ) * Math.sin( dLon / 2 );
return 6371 * 2 * Math.atan2( Math.sqrt( a ), Math.sqrt( 1 - a ) );
}
function imBereichOesterreich( lat, lon ) {
return lat >= 46.3 && lat <= 49.1 && lon >= 9.4 && lon <= 17.3;
}
function formatKm( km ) {
return km.toFixed( 1 ).replace( '.', ',' ) + ' km';
}
function setzeStatus( text, istFehler ) {
el.status
.text( text )
.css( 'color', istFehler ? '#b32424' : '#202122' );
}
function setzeExportStatus( text, istFehler ) {
el.exportStatus
.text( text )
.css( 'color', istFehler ? '#b32424' : '#202122' );
}
}() );
Hinweis zum Kopieren: Die Blöcke einfach in der normalen Seitenansicht markieren und kopieren – so landen die Zeichen unverfälscht in der Zwischenablage.