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, '&' ) .replace( /"/g, '"' ) .replace( /</g, '<' ) .replace( />/g, '>' ); } 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>