Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
//<nowiki> /*********************************************************************  * poi2gpx v1.5, 2024-10-04  * Download of article’s points of interest and tracks to a GPX file  * Original author: Roland Unger  * Support of desktop and mobile views  * Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:Gadget-Poi2gpx.js  * License: GPL-2.0+, CC-by-sa 3.0  *********************************************************************/ /* eslint-disable mediawiki/class-doc */  ( function ( $, mw ) { 	'use strict'; 	 	var poi2gpx = function() {  		// technical constants 		const minervaPageActionsSelector = '.page-actions-menu #page-actions-edit', 			imgSrc = '//upload.wikimedia.org/wikivoyage/de/thumb/5/5f/WV-poi2gpx-green.svg/50px-WV-poi2gpx-green.svg.png', 			imgSrcMinerva = '//upload.wikimedia.org/wikivoyage/de/thumb/6/63/WV-poi2gpx-black.svg/50px-WV-poi2gpx-black.svg.png', 			// image for download link  			allowedNamespaces = [ 				0, // Main 				2, // User 				4  // Project 			], 			commentClasses = [ 'listing-hours', 'listing-checkin', 'listing-checkout', 				'listing-price', 'listing-credit' ], 			containerClass = 'vcard', 				// contains wrapper markup of a single marker or listing 			contentClass = 'listing-content', 			dataColor = 'data-color', 			dataName = 'data-name', 			dataPhone = 'data-phone', 			dataType = 'data-group-translated', // other wikis: 'data-type' 			dataUrl = 'data-url', 			fallbackLang = 'en', 			kartographerClass = 'mw-kartographer-maplink', 			nameClass = 'listing-name', 			noGpxClass = 'listing-no-gpx';  		// strings depending on page language 		// depending on group names defined in Module:Marker utilities/Groups 		const wikiStrings = { 			de: { 				track: 'Track' 			}, 			en: { 				track: 'track' 			}, 			es: { 				track: 'sendero' 			}, 			fr: { 				track: 'piste' 			}, 			it: { 				track: 'traccia' 			} 		};  		// strings depending on user language 		const userStrings = { 			de: { 				gpxFileDescr: 'Kartenpositionen aus dem deutschen Wikivoyage-Artikel', 				gpxLabel:     'GPX', 				gpxTitle:     'Download der Kartenpositionen als GPX-Datei' 			}, 			en: { 				gpxFileDescr: 'Map positions from the German Wikivoyage article', 				gpxLabel:     'GPX', 				gpxTitle:     'Download of the map positions as a GPX file' 			}, 			es: { 				gpxFileDescr: 'Posiciones en el mapa del artículo de Wikivoyage en alemán', 				gpxLabel:     'GPX', 				gpxTitle:     'Descarga de las posiciones del mapa como archivo GPX' 			}, 			fr: { 				gpxFileDescr: 'Positions sur la carte de l’article de Wikivoyage allemand', 				gpxLabel:     'GPX', 				gpxTitle:     'Téléchargement des positions de la carte sous forme de fichier GPX' 			}, 			it: { 				gpxFileDescr: 'Posizioni sulla mappa dall’articolo tedesco di Wikivoyage', 				gpxLabel:     'GPX', 				gpxTitle:     'Download delle posizioni della mappa come file GPX' 			} 		};  		// internal use 		const pageLang = mw.config.get( 'wgPageContentLanguage' ), 			userLang = mw.config.get( 'wgUserLanguage' ), 			pageTitle = mw.config.get( 'wgTitle' ), 			isMinerva = mw.config.get( 'skin' ) === 'minerva'; // mobile view  		var gpxFile = null, // check for URL object 			trackdata = null, 			messages = {};  		// copying translation strings to messages depending on chain languages 		function addMessages( strings, chain ) { 			for ( var i = chain.length - 1; i >= 0; i-- ) { 				if ( strings.hasOwnProperty( chain[ i ] ) ) { 					$.extend( messages, strings[ chain[ i ] ] ); 				} 			} 		}  		// copying translation strings to messages 		function setupMessages() { 			addMessages( wikiStrings, [ pageLang, fallbackLang ] ); 			const chain = ( userLang == pageLang ) ? [ pageLang, fallbackLang ] : 				[ userLang, pageLang, fallbackLang ]; 			addMessages( userStrings, chain ); 		}  		// to use text in XML tags 		function replace( text ) { 			return text.replace( /\&/g, '&amp;' ) 				.replace( /"/g, '&quot;' ) 				.replace( /</g, '&lt;' ) 				.replace( />/g, '&gt;' ); 		}  		function removeTags( s ) { 			return $( '<div>' + s + '</div>' ).text(); 		}  		function getString( prop ) { 			if ( !prop ) { 				return ''; 			} 			if ( typeof( prop ) == 'string' ) { 				return prop; 			} 			if ( prop[ pageLang ] ) { 				return prop[ pageLang ]; 			} else if ( prop.en ) { 				return prop.en; 			} 			for ( var i in prop ) { 				return prop[ i ]; 			} 			return ''; 		}  		function makeFile( text ) { // modern Browsers 	    	const data = new Blob( [ text ], { type: 'application/gpx+xml' } ); 	    	if ( !gpxFile ) { 	    		window.URL.revokeObjectURL( gpxFile ); 	    	} 	    	gpxFile = window.URL.createObjectURL( data ); 	    	return gpxFile; 		}  		function getPhone( selector, $this ) { 			var v = $( selector, $this ).first(); 			if ( v.length ) { 				v = v.attr( dataPhone ); 				if ( v ) { 					return v; 				} 			} 			return ''; 		}  		// Getting GeoJSON data sets from external sources (OSM, Commons) 		function getGeoJSON( obj ) { 			var promise, coordinates, geometry, i, 				properties = obj.properties; // for all but not for 'page'  			promise = $.getJSON( obj.url ).then( function( geoJSON ) { 				switch ( obj.service ) { 					case 'page': // from Commons 						if ( geoJSON.jsondata && geoJSON.jsondata.data ) { 							$.extend( obj, geoJSON.jsondata.data ); 						} 						break;  					case 'geoline': // from OSM 					case 'geoshape': 						$.extend( obj, geoJSON );  						if ( properties ) { 							for ( i = 0; i < obj.features.length; i++ ) { 								if ( $.isEmptyObject( obj.features[ i ].properties ) ) { 									obj.features[ i ].properties = properties; 								} else { 									obj.features[ i ].properties = 										$.extend( {}, properties, obj.features[ i ].properties ); 								} 							} 						} 				} 			}, function() { 				// failed, no tracks will be added 			} );  			return promise; 		}  		// getting Kartographer live data 		function getKartographerLiveData() { 			var i, obj, promiseArray = [];  			const liveData = mw.config.get( 'wgKartographerLiveData' ); 			if ( liveData ) { 				trackdata = liveData[ messages.track ]; 				if ( trackdata && !trackdata.length ) { 					trackdata = null; 				} 				if ( trackdata ) { 					for ( i = 0; i < trackdata.length; i++ ) { 						obj = trackdata[ i ]; 						if ( obj.type === 'ExternalData' && obj.url ) { 							promiseArray.push( getGeoJSON( obj ) ); 						} 					} 				} 			} 			// wait for getting all external data 			if ( typeof Promise !== 'undefined' ) { 				Promise.all( promiseArray ) 					.then( function() { createFile(); } ) 					.catch( function() { createFile(); } ); 					// create file also in case of failures 			} else { 				createFile(); 			} 			return; 		}  		function writeTrack( coordinates, tracks, type, properties ) { 			var j, k, s, coords; 			if ( !coordinates || !coordinates.length ) { 				return tracks; 			}  			tracks += '\n  <trk>\n'; 			if ( properties ) { 				s = replace( removeTags( getString( properties.title ) ) ); 				if ( s ) { 					tracks += '    <name>' + s + '</name>\n'; 				} 				s = replace( removeTags( getString( properties.desc ) ) ); 				if ( s ) { 					tracks += '    <desc>' + s + '</desc>\n'; 				} 				if ( properties.stroke ) { 					tracks += '    <extensions>\n' + 						'      <gpxx:WaypointExtension xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">\n' + 						'        <gpxx:DisplayColor>' + properties.stroke + '</gpxx:DisplayColor>\n' + 						'      </gpxx:WaypointExtension>\n' + 						'    </extensions>\n'; 				} 			} 			 			for ( j = 0; j < coordinates.length; j++ ) { 				coords = coordinates[ j ]; 				if ( type === 'MultiPolygon' ) { 					coords = coordinates[ j ][ 0 ]; 					// only first polygon 					// tests not yet completed 				}  				if ( coords.length ) { 					tracks += '    <trkseg>\n'; 					for ( k = 0; k < coords.length; k++ ) { 						tracks += '      <trkpt lat="' + coords[ k ][ 1 ]  							+ '" lon="' + coords[ k ][ 0 ] + '" />\n'; 					} 					tracks += '    </trkseg>\n'; 				} 			} 			return tracks + '  </trk>\n'; 		}  		function writeTracks() { 			var tracks = ''; 			if ( !trackdata || !trackdata.length ) { 				return ''; 			} 			 			var i, coordinates, geoJSON, geometry, properties;  			for ( i = 0; i < trackdata.length; i++ ) { 				if ( trackdata[ i ].features ) { 					geoJSON = trackdata[ i ].features[ 0 ]; 					geometry = geoJSON.type === 'Feature' ? geoJSON.geometry : geoJSON; 					coordinates = geometry ? geometry.coordinates : null; 					properties = geoJSON.properties;  					switch ( geometry.type ) { 						case 'LineString': 							tracks = writeTrack( [ coordinates ], tracks, 'MultiLineString', properties ); 							break; 						case 'MultiLineString': 						case 'Polygon': 							tracks = writeTrack( coordinates, tracks, 'MultiLineString', properties ); 							break; 						case 'MultiPolygon': 							tracks = writeTrack( coordinates, tracks, 'MultiPolygon', properties ); 					} 				} 			} 			 			return tracks; 		}  		function getText() { // generate GPX output 			var markers = $( '.' + containerClass ).not( '.' + noGpxClass ); 			if ( !markers.length ) { 				return ''; 			}  			var aType, cmt, color, count, desc, gpxx, i, link, lat, lon, name, 				text = '', v, 				minlat = null, minlon = null, maxlat = null, maxlon = null; // for bounds  			markers.each( function() { 				var $this = $( this ); 				link = $( '.' + kartographerClass, $this ).first(); 			 				if ( link.length ) { 					lat = link.attr( 'data-lat' ); 					lon = link.attr( 'data-lon' ); 					if ( minlat === null || lat < minlat ) { 						minlat = lat; 					} 					if ( minlon === null || lon < minlon ) { 						minlon = lon; 					} 					if ( maxlat === null || lat > maxlat ) { 						maxlat = lat; 					} 					if ( maxlon === null || lon > maxlon ) { 						maxlon = lon; 					}  					color = $this.attr( dataColor ); 					aType = $this.attr( dataType );  					count = link.html(); 					if ( count.indexOf( '<' ) >= 0 ) { 						count= ''; // no number but html tag 					} else { 						count = ' ' + ( '0' + count ).slice( -2 ); 					}  					name = $this.attr( dataName ); 					if ( !name ) { 						name = $( '.' + nameClass, $this ).first(); 					} 					name = replace( removeTags( name ) );  					desc = $( '.' + contentClass, $this ).first(); 					desc = ( desc.length ) ? replace( desc.text() ) : '';  					cmt = ''; 					for ( i = 0; i < commentClasses.length; i++ ) { 						v = $( '.' + commentClasses[ i ], $this ).first(); 						if ( v.length ) { 							if ( cmt ) { 								cmt += ' '; 							} 							cmt += replace( v.text() ); 						} 					}  					gpxx = ''; 					v = $( '.listing-address', $this ).first(); 					if ( v.length ) { 						gpxx += '        <gpxx:Address>\n' + 							'          <gpxx:StreetAddress>' + replace( v.text() ) + '</gpxx:StreetAddress>\n' + 							'        </gpxx:Address>\n'; 					} 					v = getPhone( '.listing-landline .listing-phone-number', $this ); 					if ( v ) { 						gpxx += '        <gpxx:PhoneNumber Category="Phone">' + v + '</gpxx:PhoneNumber>\n'; 					} 					v = getPhone( '.listing-tollfree .listing-phone-number', $this ); 					if ( v ) { 						gpxx += '        <gpxx:PhoneNumber Category="Tollfree">' + v + '</gpxx:PhoneNumber>\n'; 					} 					v = getPhone( '.listing-mobile .listing-phone-number', $this ); 					if ( v ) { 						gpxx += '        <gpxx:PhoneNumber Category="Mobile">' + v + '</gpxx:PhoneNumber>\n'; 					} 					v = getPhone( '.listing-fax .listing-phone-number', $this ); 					if ( v ) { 						gpxx += '        <gpxx:PhoneNumber Category="Fax">' + v + '</gpxx:PhoneNumber>\n'; 					} 					v = $( '.listing-email a', $this ).first(); 					if ( v.length ) { 						gpxx += '        <gpxx:PhoneNumber Category="Email">' + v.text() + '</gpxx:PhoneNumber>\n'; 					} 					v = $this.attr( dataUrl ); 					if ( v ) { 						gpxx += '        <gpxx:PhoneNumber Category="URL">' + replace( v ) + '</gpxx:PhoneNumber>\n'; 					}  					text += '  <wpt lat="' + lat + '" lon="' + lon + '">\n' + 						'    <name>[' + aType + count + '] ' + name + '</name>\n' + 						'    <type>' + aType + '</type>\n' + 						'    <extensions>\n' + //						'      <color>' + color + '</color>\n' + 						'      <gpxx:WaypointExtension xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3">\n' + 						'        <gpxx:DisplayColor>' + color + '</gpxx:DisplayColor>\n' + 						'        <gpxx:DisplayMode>SymbolAndName</gpxx:DisplayMode>\n' + 						gpxx + 						'      </gpxx:WaypointExtension>\n' + 						'    </extensions>\n'; 					if ( desc ) { 						text += '    <desc>' + desc + '</desc>\n'; 					} 					if ( cmt ) { 						text += '    <cmt>' + cmt + '</cmt>\n'; 					} 					text += '  </wpt>\n'; 				} 			});  			text += writeTracks();  			if ( !text ) { 				return ''; 			} 			return '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n' + 				'<gpx version="1.1" creator="Wikivoyage"\n' + 				'  xmlns="http://www.topografix.com/GPX/1/1"\n' + 				'  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + 				'  xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"\n' + 				'  xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\n' + 				'    http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensions/v3/GpxExtensionsv3.xsd">\n\n' + 				'  <metadata>\n' + 				'    <name>' + replace( pageTitle ) + '</name>\n' + 				'    <desc>' + messages.gpxFileDescr + '</desc>\n' + 				'    <author>\n' + 				'      <name>Wikivoyage</name>\n' + 				'    </author>\n' + 				'    <copyright>\n' + //				'      <license>https://creativecommons.org/publicdomain/zero/1.0/</license>\n' + 				'      <license>https://creativecommons.org/licenses/by-sa/3.0/</license>\n' + // desc is CC-by-sa 3.0 				'    </copyright>\n' + 				'    <bounds minlat="'+ minlat + '" maxlat="' + maxlat + '" minlon="' + minlon +'" maxlon="' + maxlon + '"></bounds>\n' + 				'  </metadata>\n\n' + 				text + 				'</gpx>'; 		}  		function createFile() { 			var downloadText = getText(); 			if ( !downloadText ) { 				return; 			} 		 			var fileName = pageTitle + '_' + pageLang + '.gpx'; 			fileName = fileName.replace( / |:|\/|\\/gi, '_' );  			var image = $( '<img>', { 					class: 'voy-indicator-img', 					src: isMinerva ? imgSrcMinerva : imgSrc, 					width: isMinerva ? '15' : '25', 					height: isMinerva ? '20' : '25' 				}); 			if ( isMinerva ) { 				image.css( { 'margin-left': '3px' } ); // = ( 20 - 15 ) / 2 			} 			var indicator = $( '<a>', { 					id: 'mw-indicator-i3-gpx', 					class: 'mw-indicator', /* evtl + cdx-button */ 					title: messages.gpxTitle 				} ) 				.css( { display: 'inline-block' } ) 				.append( image ) 				.attr( { 					href: makeFile( downloadText ), 					download: fileName 				} );  			if ( isMinerva ) { // mobile view 				indicator.append( $( `<span>${messages.gpxLabel}</span>` ) );  				indicator = $( '<li></li>', { 						id: 'page-actions-poi2gpx', 						class: 'page-actions-menu__list-item' 					} ) 					.append( indicator ); 				$( minervaPageActionsSelector ).after( indicator ); 			} else { 				var indicators = $( '.mw-indicators' ).first(); // always on desktop 				var geoIndicator; 				// international version: only: 				// indicators.prepend( indicator ); 				indicators.each( function() { 					geoIndicator = $( '#mw-indicator-i3-geo', $( this ) ); 					if ( geoIndicator.length ) { 						geoIndicator.after( indicator ); 					} else { 						$( this ).prepend( indicator ); 					} 				}); 			} 		}  		function allowedForCurrentPage() { 			const namespace = mw.config.get( 'wgNamespaceNumber' ); 			return allowedNamespaces.includes( namespace ) && 				mw.config.get( 'wgAction' ) == 'view'; 		}  		function init() { 			if ( !allowedForCurrentPage() ) { 				return; 			} 			if ( typeof Blob === 'undefined' ) { // very old browsers 				return; 			} 			setupMessages(); 			getKartographerLiveData(); 			// calls createFile 		}  		return { init: init }; 	} (); 	 	$( poi2gpx.init );  } ( jQuery, mediaWiki ) );  //</nowiki>