MediaWiki:Poi2gpx.js

Aus Wikivoyage
Zur Navigation springen Zur Suche springen

Hinweis: Leere nach dem Speichern 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
  • Internet Explorer: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Gehe zu Menü → Einstellungen (Opera → Einstellungen auf dem Mac) und dann auf Datenschutz & Sicherheit → Browserdaten löschen → Gespeicherte Bilder und Dateien.
//<nowiki>
/*********************************************************************
 * poi2gpx v1.4, 2019-09-24
 * 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:Poi2gpx.js
 * License: GPL-2.0+, CC-by-sa 3.0
 *********************************************************************/

( function ( $, mw ) {
	'use strict';
	
	var poi2gpx = function() {
		var imgSrc = 'https://upload.wikimedia.org/wikivoyage/de/thumb/f/f0/WV-poi2gpx.svg/25px-WV-poi2gpx.svg.png';
			// image for download link
		var download = 'Download der Kartenpositionen als GPX-Datei'; // tooltip
		var mainDesc = 'Kartenpositionen aus dem deutschen Wikivoyage-Artikel'; // GPX description

		var containerClass = 'vcard'; // contains wrapper markup of a single marker or listing
		var kartographerClass = 'mw-kartographer-maplink';
		var nameClass = 'listing-name';
		var contentClass = 'listing-content';
		var dataName = 'data-name';
		var dataColor = 'data-color';
		var dataType = 'data-group'; // other wikis: 'data-type'
		var dataUrl = 'data-url';
		var dataPhone = 'data-phone';
		var comments = [ 'listing-hours', 'listing-checkin', 'listing-checkout', 'listing-price', 'listing-credit' ];

		var gpxFile = null; // check for URL object
		var trackdata = null;

		var translations = {
			around:    'in der Umgebung',
			buy:       'Kaufen',
			'do':      'Aktivität',
			drink:     'Ausgehen',
			eat:       'Küche',
			error:     'Unbekannt',
			go:        'Anreise',
			other:     'Anderes',
			populated: 'Besiedelt',
			see:       'Sehenswürdigkeit',
			sleep:     'Unterkunft',
			track:     'Track', // group name of tracks, intl. version: 'track'
			view:      'Aussicht',

			black:     'Schwarz',
			blue:      'Blau',
			brown:     'Braun',
			chocolate: 'Schokobraun',
			cosmos:    'Cosmos',
			forestgreen: 'Waldgrün',
			fuchsia:   'Purpurrot',
			gold:      'Gold',
			gray:      'Grau',
			grey:      'Grau',
			lime:      'Hellgrün',
			magenta:   'Magentarot',
			maroon:    'Kastanienbraun',
			mediumaquamarine: 'Aquamarinblau',
			navy:      'Marineblau',
			orange:    'Orange',
			plum:      'Pflaumenblau',
			purple:    'Violett',
			red:       'Rot',
			royalblue: 'Königsblau',
			silver:    'Silber',
			steelblue: 'Stahlblau',
			teal:      'Blaugrün'
		};

		var makeFile = function( text ) { // modern Browsers
	    	var data = new Blob( [text], { type: 'application/gpx+xml' } );
	    	if ( gpxFile !== null ) window.URL.revokeObjectURL( gpxFile );
	    	gpxFile = window.URL.createObjectURL( data );
	    	return gpxFile;
		};

		var ieSaveFile = function( text, fileName ) { // IE 11
			var data = new Blob( [text], { type: 'application/gpx+xml' } );
			window.navigator.msSaveOrOpenBlob( data, fileName );
		};

		var replace = function( text ) { // to use text in XML tags
			return text.replace( /\&/g, '&amp;' )
				.replace( /"/g, '&quot;' )
				.replace( /</g, '&lt;' )
				.replace( />/g, '&gt;' );
		};

		var getPhone = function( selector, $this ) {
			var r = '';
			var v = $( selector, $this ).first();
			if ( v.length !== 0 ) {
				v = v.attr( dataPhone );
				if ( v !== undefined ) r = v;
			}
			return r;
		};

		// Getting GeoJSON data sets from external sources (OSM, Commons)
		var getGeoJSON = function( obj ) {
			var promise, coordinates, geometry, i;
			var 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
		var getKartographerLiveData = function() {
			var i, obj;
			var promiseArray = [];

			trackdata = mw.config.get( 'wgKartographerLiveData' );
			if ( trackdata ) {
				trackdata = trackdata[ translations.track ];
				if ( trackdata && trackdata.length === 0 )
					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
			var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
			if ( isIE11 )
				$.when.apply( $, promiseArray ).then( function() {
					createFile();
				} );
			else
				if ( typeof Promise !== 'undefined' )
					Promise.all( promiseArray )
						.then( function() { createFile(); } )
						.catch( function() { createFile(); } );
						// create file also in case of failures
				else
					createFile();
			return;
		};

		var getString = function( prop ) {
			if ( !prop ) return '';
			if ( typeof( prop ) == 'string' ) return prop;
			var wikiLang = mw.config.get( 'wgPageContentLanguage' );
			if ( prop[ wikiLang ] ) return prop[ wikiLang ];
			if ( prop.en ) return prop.en;
			for ( var i in prop ) { return prop[ i ] }
			return '';
		};

		var removeTags = function( s ) {
			return $( '<div>' + s + '</div>' ).text();
		};

		var writeTrack = function( coordinates, tracks, type, properties ) {
			var j, k, s, coords;
			if ( !coordinates || coordinates.length === 0 ) 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 > 0 ) {
					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';
		};

		var writeTracks = function() {
			var tracks = '';
			if ( !trackdata || trackdata.length === 0 ) return '';
			
			var i, geoJSON, geometry, coordinates, 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':
							tracks = writeTrack( coordinates, tracks, 'MultiLineString', properties );
							break;

						case 'Polygon':
							tracks = writeTrack( coordinates, tracks, 'MultiLineString', properties );
							break;
						case 'MultiPolygon':
							tracks = writeTrack( coordinates, tracks, 'MultiPolygon', properties );
					}
				}
			}
			
			return tracks;
		};

		var getText = function() { // generate GPX output
			var markers = $( '.' + containerClass );
			if ( markers.length === 0 ) return '';

			var link, lat, lon, count, i, name, aType, translType, color,
				desc, cmt, v, gpxx;
			var text = '';
			var minlat = null, minlon = null, maxlat = null, maxlon = null; // for bounds

			markers.each( function() {
				var $this = $( this );
				link = $( '.' + kartographerClass, $this ).first();
			
				if ( link.length > 0 ) {
					lat = link.attr( 'data-lat' );
					lon = link.attr( 'data-lon' );
					if ( minlat === null ) minlat = lat;
					if ( minlon === null ) minlon = lon;
					if ( maxlat === null ) maxlat = lat;
					if ( maxlon === null ) maxlon = lon;
					if ( lat < minlat ) minlat = lat;
					if ( lat > maxlat ) maxlat = lat;
					if ( lon < minlon ) minlon = lon;
					if ( lon > maxlon ) maxlon = lon;

					color = $this.attr( dataColor );
					aType = $this.attr( dataType );
					translType = aType;
					if ( aType in translations && translations[aType] !== '' )
						translType = translations[aType];

					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 === undefined ) {
						name = $( '.' + nameClass, $this ).first();
					}
					name = replace( removeTags( name ) );

					desc = $( '.' + contentClass, $this ).first();
					if ( desc.length === 0 ) desc = '';
					else desc = replace( desc.text() );

					cmt = '';
					for ( i = 0; i < comments.length; i++ ) {
						v = $( '.' + comments[ i ], $this ).first();
						if ( v.length !== 0 ) {
							if ( cmt !== '' ) cmt += ' ';
							cmt += replace( v.text() );
						}
					}

					gpxx = '';
					v = $( '.listing-address', $this ).first();
					if ( v.length !== 0 ) {
						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 !== 0 ) {
						gpxx += '        <gpxx:PhoneNumber Category="Email">' + v.text() + '</gpxx:PhoneNumber>\n';
					}
					v = $this.attr( dataUrl );
					if ( v !== undefined ) {
						gpxx += '        <gpxx:PhoneNumber Category="URL">' + replace( v ) + '</gpxx:PhoneNumber>\n';
					}

					text += '  <wpt lat="' + lat + '" lon="' + lon + '">\n' +
						'    <name>[' + translType + 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';
				}
			});

			var tracks = writeTracks();
			text += tracks;

			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( mw.config.get( 'wgTitle' ) ) + '</name>\n' +
				'    <desc>' + mainDesc + '</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>';
		};

		var allowedForCurrentPage = function() {
			var namespace = mw.config.get( 'wgNamespaceNumber' );
			if (namespace !== 0 && namespace !== 2 && namespace !== 4) {
				return false;
			}
			return mw.config.get('wgAction') == 'view';
		};

		var createFile = function() {
			if ( typeof Blob === "undefined" ) return; // very old browsers
			if ( !allowedForCurrentPage() ) return;

			var downloadText = getText();
			if ( downloadText === '' ) return;
		
			var fileName = mw.config.get( 'wgTitle' ) + '_'
				+ mw.config.get( 'wgPageContentLanguage' ) + '.gpx';
			fileName = fileName.replace( / |:|\/|\\/gi, '_' );

			var link, downloadImg;
			if ( !window.navigator.msSaveOrOpenBlob ) { // modern browsers
				link = $('<a>', {
	    			alt : download,
    				title : download,
			    	href : makeFile( downloadText ),
		    		download : fileName
				}).append( $( '<img src="' + imgSrc + '"'
					+ '" title="' + download + '">' ) );
				downloadImg = $( '<div id="mw-indicator-i3-gpx" class="mw-indicator">' );
				downloadImg.append( link );
			}
			else { // ie 11
				link = $( '<img src="' + imgSrc + '"'
					+ '" title="' + download + '">' )
					.click( function() { ieSaveFile( downloadText, fileName ); })
					.css( { cursor: 'pointer' } );
				downloadImg = $( '<div id="mw-indicator-i3-gpx" class="mw-indicator">' );
				downloadImg.append( link );
			}

			if ( mw.config.get('skin') === 'minerva' ) { // mobile view
				downloadImg.css( 'float', 'right' );
				$( '#section_0' ).after( downloadImg );
			}
			else {
				var indicators = $( '.mw-indicators' ).first(); // always on desktop
				var geoIndicator;
				// international version: only:
				// indicators.prepend( downloadImg );
				indicators.each( function() {
					geoIndicator = $( '#mw-indicator-i3-geo', $(this) );
					if ( geoIndicator.length === 0 ) {
						$(this).prepend( downloadImg );
					}
					else {
						geoIndicator.after( downloadImg );
					}
				});
			}
		};
	
		var init = function() {
			getKartographerLiveData();
			// calls createFile
		};

		return { init: init };
	} ();
	
	$( poi2gpx.init );

} ( jQuery, mediaWiki ) );

//</nowiki>