Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt)
Zeile 19: Zeile 19:
  *    hier sofort der fertige {{Wetterwidget|...}}-Baustein bereit.
  *    hier sofort der fertige {{Wetterwidget|...}}-Baustein bereit.
  *
  *
  *  C) GEMEINDELEISTE: Das Formular gibt (abwählbar per Häkchen) vor dem
  *  C) WETTERLEISTE: Das Formular gibt (abwählbar per Häkchen) vor dem
  *    Wetterwidget-Baustein auch den Aufruf der [[Vorlage:Gemeindeleiste]]
  *    Wetterwidget-Baustein auch den Aufruf der [[Vorlage:Wetterleiste]]
  *    aus – die Button-Zeile mit Wetter-, Radar-, Wolken-, Nachthimmel-,
  *    aus – die Button-Zeile mit Wetter-, Radar-, Wolken-, Nachthimmel-,
  *    Pollen-, Warnungs-, Website- und Wikipedia-Link. Die Dienst-
  *    Pollen- und Warnungs-Link. Die Dienst-Adressen stecken in der
  *    Adressen stecken in der Vorlage (eine Stelle für Änderungen);
  *    Vorlage (eine Stelle für Änderungen); das Formular liefert nur
  *    das Formular liefert nur die Werte. Dafür gibt es drei neue
  *    die Werte, weitere Eingaben braucht die Leiste nicht. Sonstige
  *    Eingabefelder (PLZ, Website, Wikipedia). Beim Karten-Rücksprung
  *    Links der Gemeindeseite (Website, Wikipedia usw.) bleiben bewusst
  *    (B) ist das Häkchen automatisch abgewählt, denn eine Station ist
*    außen vor und werden wie bisher von Hand gepflegt. Beim Karten-
  *    keine Gemeinde.
  *    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),
  * Datenquelle: GeoSphere Austria Dataset-API (Open Data, CC BY 4.0),
Zeile 77: Zeile 82:
.on( 'input', aktualisiereBaustein );
.on( 'input', aktualisiereBaustein );


el.lat = $( '<input>' )
/* Ein Feld für beide Koordinaten – direkt aus OSM kopierbar */
.attr( { type: 'text', placeholder: 'z. B. 47,6969' } )
el.koordinaten = $( '<input>' )
.css( 'width', '10em' );
.attr( { type: 'text', placeholder: 'z. B. 47.67475, 16.01918' } )
 
.css( { width: '100%', 'max-width': '20em' } );
el.lon = $( '<input>' )
.attr( { type: 'text', placeholder: 'z. B. 15,8446' } )
.css( 'width', '10em' );
 
/* --- Zusatzfelder für die Gemeindeleiste (Button-Zeile) --- */
el.plz = $( '<input>' )
.attr( { type: 'text', placeholder: 'z. B. 2632' } )
.css( 'width', '6em' )
.on( 'input', aktualisiereBaustein );
 
el.website = $( '<input>' )
.attr( { type: 'text',
placeholder: 'z. B. https://www.gemeinde-altendorf.at/' } )
.css( { width: '100%', 'max-width': '26em' } )
.on( 'input', aktualisiereBaustein );
 
el.wikipedia = $( '<input>' )
.attr( { type: 'text',
placeholder: 'z. B. https://de.wikipedia.org/wiki/Altendorf_(Niederösterreich)' } )
.css( { width: '100%', 'max-width': '26em' } )
.on( 'input', aktualisiereBaustein );


el.mitLeiste = $( '<input>' )
el.mitLeiste = $( '<input>' )
Zeile 171: Zeile 155:
),
),
feld( 'Ortsname', el.ort ),
feld( 'Ortsname', el.ort ),
feld( 'Breitengrad (Dezimalgrad)', el.lat ),
feld( 'Koordinaten „Breite, Länge" (direkt aus OSM kopierbar)',
feld( 'Längengrad (Dezimalgrad)', el.lon ),
el.koordinaten ),
feld( 'PLZ (für den Wetterlink der Leiste)', el.plz ),
feld( 'Gemeinde-Website (optional)', el.website ),
feld( 'Wikipedia-Adresse (optional)', el.wikipedia ),
$( '<label>' )
$( '<label>' )
.css( { display: 'block', margin: '0.6em 0' } )
.css( { display: 'block', margin: '0.6em 0' } )
.append( el.mitLeiste, document.createTextNode(
.append( el.mitLeiste, document.createTextNode(
' Gemeindeleiste (Button-Zeile) mit ausgeben' ) ),
' Wetterleiste (Button-Zeile) mit ausgeben' ) ),
$( '<div>' ).css( 'margin', '0.7em 0' ).append( el.suchen ),
$( '<div>' ).css( 'margin', '0.7em 0' ).append( el.suchen ),
el.status,
el.status,
Zeile 303: Zeile 284:
* ------------------------------------------------------------------ */
* ------------------------------------------------------------------ */
function sucheStationen() {
function sucheStationen() {
var lat = parseKoordinate( el.lat.val() );
var paar = parseKoordinatenPaar( el.koordinaten.val() );
var lon = parseKoordinate( el.lon.val() );


if ( lat === null || lon === null ) {
if ( !$.trim( el.ort.val() ) ) {
setzeStatus( 'Bitte beide Koordinaten in Dezimalgrad eingeben, ' +
setzeStatus( 'Bitte zuerst den Ortsnamen eintragen – er steckt in ' +
'z. B. 47,6969 und 15,8446.', true );
'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;
return;
}
}
letzteSuche = { lat: lat, lon: lon };
letzteSuche = { lat: paar.lat, lon: paar.lon };
setzeStatus( 'Stationsliste wird geladen …', false );
setzeStatus( 'Stationsliste wird geladen …', false );
ladeStationsliste( werteAus, function ( fehlerText ) {
ladeStationsliste( werteAus, function ( fehlerText ) {
setzeStatus( fehlerText, true );
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 };
}
}


Zeile 380: Zeile 391:


el.ort.val( name );
el.ort.val( name );
el.lat.val( String( lat ).replace( '.', ',' ) );
el.koordinaten.val( String( lat ) + ', ' + String( lon ) );
el.lon.val( String( lon ).replace( '.', ',' ) );
/* Eine Station ist keine Gemeinde: Leiste hier nicht mitausgeben. */
/* Eine Station ist keine Gemeinde: Leiste hier nicht mitausgeben. */
el.mitLeiste.prop( 'checked', false );
el.mitLeiste.prop( 'checked', false );
Zeile 518: Zeile 528:
}
}


/* Aufruf der [[Vorlage:Gemeindeleiste]]. Die Dienst-Adressen stecken
/* Aufruf der [[Vorlage:Wetterleiste]]. Die Dienst-Adressen stecken
* in der Vorlage – hier werden nur die Werte geliefert. */
* in der Vorlage – hier werden nur die Werte geliefert. */
function baueLeiste() {
function baueLeiste() {
Zeile 524: Zeile 534:
var lat = letzteSuche.lat.toFixed( 4 );
var lat = letzteSuche.lat.toFixed( 4 );
var lon = letzteSuche.lon.toFixed( 4 );
var lon = letzteSuche.lon.toFixed( 4 );
return '{{Gemeindeleiste\n' +
return '{{Wetterleiste\n' +
'|ort = ' + ort + '\n' +
'|ort = ' + ort + '\n' +
'|lat = ' + lat + '\n' +
'|lat = ' + lat + '\n' +
'|lon = ' + lon + '\n' +
'|lon = ' + lon + '\n' +
'|plz = ' + $.trim( el.plz.val() ) + '\n' +
'|website = ' + $.trim( el.website.val() ) + '\n' +
'|wikipedia = ' + $.trim( el.wikipedia.val() ) + '\n' +
'|pollenloc = ' + pollenLoc( ort, lat, lon ) + '\n' +
'|pollenloc = ' + pollenLoc( ort, lat, lon ) + '\n' +
'}}';
'}}';

Aktuelle Version vom 25. Juli 2026, 01:25 Uhr

/**
 * 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' );
	}
}() );