Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
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. Dafür gibt es ein neues Eingabefeld (PLZ). 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.
  *
  *
  * Datenquelle: GeoSphere Austria Dataset-API (Open Data, CC BY 4.0),
  * Datenquelle: GeoSphere Austria Dataset-API (Open Data, CC BY 4.0),
Zeile 85: Zeile 86:
.css( 'width', '10em' );
.css( 'width', '10em' );


/* --- Zusatzfelder für die Gemeindeleiste (Button-Zeile) --- */
/* --- Zusatzfeld für die Wetterleiste (Button-Zeile) --- */
el.plz = $( '<input>' )
el.plz = $( '<input>' )
.attr( { type: 'text', placeholder: 'z. B. 2632' } )
.attr( { type: 'text', placeholder: 'z. B. 2632' } )
.css( 'width', '6em' )
.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 );
.on( 'input', aktualisiereBaustein );


Zeile 174: Zeile 163:
feld( 'Längengrad (Dezimalgrad)', el.lon ),
feld( 'Längengrad (Dezimalgrad)', el.lon ),
feld( 'PLZ (für den Wetterlink der Leiste)', el.plz ),
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 518: Zeile 505:
}
}


/* 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 511:
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' +
'|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' +
'}}';
'}}';

Version vom 25. Juli 2026, 00:36 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. Dafür gibt es ein neues Eingabefeld (PLZ). 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.
 *
 * 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 );

		el.lat = $( '<input>' )
			.attr( { type: 'text', placeholder: 'z. B. 47,6969' } )
			.css( 'width', '10em' );

		el.lon = $( '<input>' )
			.attr( { type: 'text', placeholder: 'z. B. 15,8446' } )
			.css( 'width', '10em' );

		/* --- Zusatzfeld für die Wetterleiste (Button-Zeile) --- */
		el.plz = $( '<input>' )
			.attr( { type: 'text', placeholder: 'z. B. 2632' } )
			.css( 'width', '6em' )
			.on( 'input', aktualisiereBaustein );

		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( 'Breitengrad (Dezimalgrad)', el.lat ),
				feld( 'Längengrad (Dezimalgrad)', el.lon ),
				feld( 'PLZ (für den Wetterlink der Leiste)', el.plz ),
				$( '<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 lat = parseKoordinate( el.lat.val() );
		var lon = parseKoordinate( el.lon.val() );

		if ( lat === null || lon === null ) {
			setzeStatus( 'Bitte beide Koordinaten in Dezimalgrad eingeben, ' +
				'z. B. 47,6969 und 15,8446.', true );
			return;
		}
		letzteSuche = { lat: lat, lon: lon };
		setzeStatus( 'Stationsliste wird geladen …', false );
		ladeStationsliste( werteAus, function ( fehlerText ) {
			setzeStatus( fehlerText, true );
		} );
	}

	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.lat.val( String( lat ).replace( '.', ',' ) );
		el.lon.val( String( lon ).replace( '.', ',' ) );
		/* 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' +
			'|plz = ' + $.trim( el.plz.val() ) + '\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' );
	}
}() );