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> /***************************************************************************** * mapTools v2.1, 2024-10-06 * Several map creation and supporting tools * Original author: Roland Unger * Support of desktop and mobile views * Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:Gadget-MapTools.js * License: GPL-2.0+, CC-by-sa 3.0 ****************************************************************************/ /* eslint-disable mediawiki/class-doc */ ( function( $, mw ) { 'use strict'; var mapTools = function() { // technical constants const ver = '2024-10-06', maxZoomLevel = 19, defaultMaplinkZoomLevel = 17, defaultMapZoomLevel = 14, defaultProperties = { 'stroke-width': 2, 'fill-opacity': 0.5 }, indicatorSelector = '.voy-coord-indicator', indicatorCoordsSelector = '.voy-coords a', indicatorGlobeImgSrc = 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Earth_-_The_Noun_Project.svg/20px-Earth_-_The_Noun_Project.svg.png', indicatorMapContainerId = 'voy-topMap', fullScreenContainerId = 'voy-fullScreenMap', mapframeContainerSelector = '.mw-kartographer-container', mapframeMapSelector = '.mw-kartographer-map', markerSelector = '.vcard', // wrapper selector of a single marker or listing kartographerSelector = '.mw-kartographer-maplink', nameClass = 'listing-name', imageClass = 'listing-image', footCaptionSelector = '.oo-ui-windowManager-fullscreen .mw-kartographer-captionfoot', captionMarkerClass = 'voy-caption-marker', captionInverseMarkerClass = 'voy-caption-marker-invers', minervaPageActionsSelector = '.page-actions-menu #page-actions-edit', dataLat = 'data-lat', dataLon = 'data-lon', dataZoom = 'data-zoom', dataName = 'data-name', dataColor = 'data-color', dataSymbol = 'data-symbol', dataNumber = 'data-number', dataGroup = 'data-group-translated', // other wikis: 'data-type' dataDialog = 'data-dialog', dataHeight = 'data-height', dataOverlays = 'data-overlays', fallbackLang = 'en'; // strings depending on page content language const wikiStrings = { de: { defaultShow: '["Maske","Track","Aktivität","Anderes","Anreise","Ausgehen","Aussicht","Besiedelt","Fehler","Gebiet","Gesundheit","Kaufen","Küche","Natur","Religion","Sehenswert","Unterkunft","aquamarinblau","cosmos","gold","hellgrün","orange","pflaumenblau","rot","silber","violett"]', defaultGroupName: 'Karte', mask: 'Maske', track: 'Track' }, en: { defaultShow: '["mask","around","buy","city","do","drink","eat","go","listing","other","see","sleep","vicinity","view","black","blue","brown","chocolate","forestgreen","gold","gray","grey","lime","magenta","maroon","mediumaquamarine","navy","orange","plum","purple","red","royalblue","silver","steelblue","teal"]', defaultGroupName: 'map', mask: 'mask', track: 'track' }, es: { defaultShow: '["máscara","sendero","área","beber","comer","comprar","dormir","error","habitadas","hacer","ir","otro","ver","vista","aguamarina","ciruela","cosmos","oro","lima","naranja","violeta","plata","rojo"]', defaultGroupName: 'mapa', mask: 'máscara', track: 'sendero' }, fr: { defaultShow: '["aller","destination","diplomatie","loger","manger","sortir","ville","voir","mask","around","buy","city","do","drink","eat","go","listing","other","see","sleep","vicinity","view","black","blue","brown","chocolate","forestgreen","gold","gray","grey","lime","magenta","maroon","mediumaquamarine","navy","orange","plum","purple","red","royalblue","silver","steelblue","teal"]', defaultGroupName: 'carte', mask: 'mask', track: 'piste' }, it: { defaultShow: '["mask","around","buy","city","do","drink","eat","go","listing","other","see","sleep","vicinity","view","black","blue","brown","chocolate","forestgreen","gold","gray","grey","lime","magenta","maroon","mediumaquamarine","navy","orange","plum","purple","red","royalblue","silver","steelblue","teal"]', defaultGroupName: 'mappa', mask: 'mask', track: 'traccia' } }; // strings depending on user language const userStrings = { de: { closeButtonTitle: 'Schließen', indicatorActionLabel: 'Karte', indicatorButtonTitle: 'Klick öffnet oder schließt die Karte für $1', magnifyButtonTitle: 'Karte vergrößern', mapCenter: 'Kartenzentrum', mapOf: 'Karte von $1' }, en: { closeButtonTitle: 'Close', indicatorActionLabel: 'Map', indicatorButtonTitle: 'Click to open or close the map of $1', magnifyButtonTitle: 'Enlarge map', mapCenter: 'Map center', mapOf: 'Map of $1' }, es: { closeButtonTitle: 'Cerrar', indicatorActionLabel: 'Mapa', indicatorButtonTitle: 'Haga clic para abrir o cerrar el mapa de $1', magnifyButtonTitle: 'Aumentar mapa', mapCenter: 'Centro del mapa', mapOf: 'Mapa de $1' }, fr: { closeButtonTitle: 'Fermer', indicatorActionLabel: 'Carte', indicatorButtonTitle: 'Cliquez pour ouvrir ou fermer le carte de $1', magnifyButtonTitle: 'Agrandir la carte', mapCenter: 'Centre de la carte', mapOf: 'Carte de $1' }, it: { closeButtonTitle: 'Chiudi', indicatorActionLabel: 'Mappa', indicatorButtonTitle: 'Clicca per aprire o chiudere la mappa di $1', magnifyButtonTitle: 'Ingrandisci mappa', mapCenter: 'Centro mappa', mapOf: 'Mappa di $1' } }; // internal use const $body = $( 'body' ), pageLang = mw.config.get( 'wgPageContentLanguage' ), userLang = mw.config.get( 'wgUserLanguage' ), pageTitle = mw.config.get( 'wgTitle' ), articlePath = mw.config.get( 'wgArticlePath' ), thumbPath = '//upload.wikimedia.org/wikipedia/commons/thumb/', scriptUrl = mw.format( 'https://wikivoyage.toolforge.org/w/data/$1-articles.js', pageLang ), isMinerva = mw.config.get( 'skin' ) === 'minerva'; // mobile view var defaultShowArray, messages = {}; // storing GeoJSON data var data = {}; // array of objects: { name: group.name, attribution: attributions } var groups = []; // 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 ); } // creating a Kartographer map function createMap( id, center, zoom, caption, options, color, isInvers ) { mw.loader.using( [ 'ext.kartographer.box' ] ).then( function() { var $id = $( '#' + id ), group, i, j, layerOptions; // for simple full-screen map if ( !options.withDialog && options.isFullScreen ) { $body.css( { overflow: 'hidden' } ); $id.css( { position: 'fixed', height: '100%', width: '100%', top: 0, left: 0, 'z-index': 101 } ); // vector skin } // creating base map // fortunately ext.kartographer.box is not validating the // GeoJSON against the GeoJSON+simplestyle schema // as it is done by maplink/mapframe tags // https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0 // see also: phabricator task T181604 var kartoBox = require( 'ext.kartographer.box' ); var map = kartoBox.map( { container: $id[ 0 ], center: center, zoom: zoom, allowFullScreen: options.allowFullScreen, alwaysInteractive: true, captionText: caption, fullscreen: options.isFullScreen, featureType: options.featureType } ); // following line is necessary for proper loading of // map-dialog sidebar map.initView( center, zoom ); // adding markers by group names to separate layers if ( options.withData && groups.length ) { for ( i = 0; i < options.show.length; i++ ) { for ( j = 0; j < groups.length; j++ ) { group = groups[ j ]; if ( group.name === options.show[ i ] ) { layerOptions = { name: group.name }; if ( group.attribution !== '' ) { layerOptions.attribution = group.attribution; } map.addGeoJSONLayer( data[ group.name ], layerOptions ); break; } } } } // adding dialog to full-screen map if ( options.withDialog ) { $id.addClass( 'mw-kartographer-mapDialog-map' ); mw.loader.using( 'ext.kartographer.dialog' ).done( function() { map.doWhenReady( function() { require( 'ext.kartographer.dialog' ).render( map ); } ); } ); } else { // adding Close control to non-full-screen map if required if ( options.withClose ) { var controls = $( '.leaflet-top.leaflet-right', $id ), control = $( '<div class="leaflet-bar leaflet-control-static leaflet-control"></div>' ) .append( $( '<a class="voy-icon-close"></a>', { title: messages.closeButtonTitle, role: 'button', 'aria-disabled': 'false' } ) .click( function() { $id.remove(); if ( options.isFullScreen ) { $body.css( { overflow: 'auto' } ); } } ) ); controls.prepend( control ); } // adding Nearby and Layers controls using Kartographer.js if ( options.withControls ) { mw.hook( 'wikipage.maps' ).fire( map ); } } map.doWhenReady( function() { // remove inert attribute $id.removeAttr( 'inert' ); if ( color && options.withDialog ) { setTimeout( function() { var footCaption = $( footCaptionSelector ); if ( footCaption.length ) { var captionArray = footCaption.text().split(":"), classes = captionMarkerClass + ( isInvers ? ' ' + captionInverseMarkerClass : '' ); footCaption.html( mw.format( '<span class="$1" style="background-color: $2">$3</span>$4', classes, color, captionArray[ 0 ], captionArray[ 1 ] || '' ) ); } }, 700); } } ); } ); } // creating GeoJSON data separated by group function singleDataset( color, symbol, title, lat, lon, description, group ) { group = group || messages.defaultGroupName; if ( !data.hasOwnProperty( group ) ) { data[ group ] = []; } data[ group ].push( { 'type': 'Feature', properties: { 'marker-color': color, 'marker-size': 'medium', 'marker-symbol': symbol ? symbol.toLowerCase() : symbol, title: title, description: description }, geometry: { 'type': 'Point', coordinates: [ lon, lat ] } } ); } // Getting GeoJSON data sets from external sources (OSM, Commons) function getGeoJSON( obj ) { var promise, coordinates, feature, geometry, i, j, world = [ [ [ 3600, -180 ], [ 3600, 180 ], [ -3600, 180 ], [ -3600, -180 ], [ 3600, -180 ] ] ], properties = obj.properties; // for all but not for 'page' promise = $.ajax( { // instead of $.getJSON dataType: 'json', url: obj.url, timeout: 3000 } ).then( function( geoJSON ) { switch ( obj.service ) { case 'page': if ( geoJSON.jsondata && geoJSON.jsondata.data ) { $.extend( obj, geoJSON.jsondata.data ); } break; case 'geomask': coordinates = world; for ( i = 0; i < geoJSON.features.length; i++ ) { geometry = geoJSON.features[ i ].geometry; if ( !geometry ) { continue; } // push only first polygon switch ( geometry.type ) { case 'Polygon': coordinates.push( geometry.coordinates[ 0 ] ); break; case 'MultiPolygon': for ( j = 0; j < geometry.coordinates.length; j++ ) { coordinates.push( geometry.coordinates[ j ][ 0 ] ); } } } obj.type = 'Feature'; obj.geometry = { type: 'Polygon', coordinates: coordinates }; if ( !properties ) { properties = defaultProperties; } if ( $.isEmptyObject( obj.properties ) ) { obj.properties = properties; } else { obj.properties = $.extend( {}, properties, obj.properties ); } break; case 'geoline': case 'geoshape': $.extend( obj, geoJSON ); if ( properties ) { for ( i = 0; i < obj.features.length; i++ ) { feature = obj.features[ i ]; if ( $.isEmptyObject( feature.properties ) ) { feature.properties = properties; } else { feature.properties = $.extend( {}, properties, feature.properties ); } } } } }, function() { // failed. Do nothing. } ); return promise; } // Creating attribution strings function getAttribution( obj ) { var uri = new mw.Uri( obj.url ), link = ''; switch ( obj.service ) { case 'page': link = mw.msg( 'project-localized-name-commonswiki' ) + ': ' + '<a target="_blank" href="' + '//commons.wikimedia.org/wiki/Data:' + encodeURI( uri.query.title ) + '">' + uri.query.title + '</a>'; break; default: // other services } return link; } // getting Kartographer live data function getKartographerLiveData() { var group, i, obj, promiseArray = [], attributions, link; data = mw.config.get( 'wgKartographerLiveData' ); if ( data ) { groups = []; // start with empty global array for ( group in data ) { // ignoring empty groups if ( data[ group ].length ) { attributions = []; for ( i = 0; i < data[ group ].length; i++ ) { obj = data[ group ][ i ]; // expand external data if ( obj.type === 'ExternalData' && obj.url ) { promiseArray.push( getGeoJSON( obj ) ); link = getAttribution( obj ); if ( link !== '' ) { attributions.push( link ); } } } attributions = attributions.join( ', ' ); groups.push( { name: group, attribution: attributions } ); } } } // wait for getting all external data // regardless of failures, addMapTools() will be executed if ( typeof Promise !== 'undefined' ) { Promise.all( promiseArray ) .then( function() { addMapTools(); } ) // initialization also in case of failures // maybe external data are not shown .catch( function() { addMapTools(); } ); } else { addMapTools(); // for really old browsers } } // getting all vCard/listing and marker information from article function getPOIsFromArticle() { // initally try to get wgKartographerLiveData because of masks // no marker(s): mw.config.get( 'wgKartographerLiveData' ) returns null // no map(s): all group arrays like see, do, etc. are empty // see phabricator task T183770 // no wgKartographerLiveData or empty arrays data = {}; var markers = $( markerSelector ); if ( !markers.length ) { return; } var clone, color, desc, group, image, lat, link, lon, symbol, $this, title, wikiLink; markers.each( function() { $this = $( this ); link = $( kartographerSelector, $this ).first(); if ( link.length ) { lat = link.attr( dataLat ); lon = link.attr( dataLon ); color = $this.attr( dataColor ); group = $this.attr( dataGroup ); // check if only marker number and no HTML tag symbol = $this.attr( dataSymbol ); if ( symbol ) { if ( symbol.charAt( 0 ) === '-' ) { symbol = link.text(); } // getting title title = $( '.' + nameClass, $this ).first(); clone = title.clone(); $( '.image', clone ).remove(); // remove images from title wikiLink = $( 'a', clone ).first(); clone.remove(); title = ( wikiLink.length ) ? wikiLink[ 0 ].outerHTML : $this.attr( dataName ); // putting image to description desc = ''; image = $( '.' + imageClass, $this ); if ( image.length ) { desc = image.html() // for mobile view: show image from noscript instead of placeholder .replace( '<noscript>', '' ).replace( '</noscript>', '' ); } // adding to GeoJSON data table singleDataset( color, symbol, title, lat, lon, desc, group ); } } } ); groups = []; // start with empty array for ( group in data ) { groups.push( { name: group, attribution: '' } ); } } // returning zoom parameter string as a valid number function getZoom( s, defaultValue ) { var zoom = ( typeof s == 'string' ) ? parseInt( s ) : -1; if ( zoom < 0 || zoom > maxZoomLevel ) { return defaultValue || defaultMapZoomLevel; } return zoom; } function makeContainer( id ) { return $( '<div></div>', { id: id, role: 'dialog', 'data-ver': ver } ); } // displaying a map by clicking the geo-indicator button function indicatorMap() { var indicator = $( indicatorSelector ).first(); if ( !indicator.length ) { return; } $( indicatorCoordsSelector ).attr( 'target', '_blank' ); var id = indicatorMapContainerId; var options = { withClose: true, withControls: true, withData: true, show: defaultShowArray, withDialog: false, allowFullScreen: true, isFullScreen: false, featureType: 'mapframe' }; var zoom = getZoom( indicator.attr( dataZoom ) ), lat = indicator.attr( dataLat ), lon = indicator.attr( dataLon ), center = [ lat, lon ]; // no POIs --> show blue map-center marker if ( !groups.length ) { singleDataset( '#3366cc', '', messages.mapCenter, lat, lon, '', messages.defaultGroupName ); groups = [ { name: messages.defaultGroupName, attribution: '' } ]; } // add or modify indicator action buttons var mapTitle = mw.format( messages.mapOf, pageTitle ); if ( isMinerva ) { // mobile view // add indicator action button and event handler var indicatorImg = $( '<img>', { class: 'voy-indicator-img', src: indicatorGlobeImgSrc, width: '20', height: '20' } ); indicator = $( '<a>', { id: 'mw-indicator-i3-geo', title: mw.format( messages.indicatorButtonTitle, pageTitle ), class: 'mw-indicator', // cdx-button href: '#' } ) .css( { display: 'inline-block' } ) .append( indicatorImg ) .append( $( `<span>${messages.indicatorActionLabel}</span>` ) ); indicator = $( '<li>', { id: 'page-actions-i3-geo', class: 'page-actions-menu__list-item' } ) .append( indicator ) .click( function() { var container = $( '#' + id ); if ( container.length ) { container.remove(); } else { $( '#bodyContent' ).prepend( makeContainer( id ) ); createMap( id, center, zoom, mapTitle, options ); } } ); $( minervaPageActionsSelector ).after( indicator ); } else { // desktop views // replace indicator image and add an event handler $( indicatorSelector + ' .voy-map-globe-default' ) .css( { display: 'none' } ); $( indicatorSelector + ' .voy-map-globe-js' ) .css( { display: 'inline', cursor: 'pointer' } ) .attr( 'title', mw.format( messages.indicatorButtonTitle, pageTitle ) ) .click( function() { var container = $( '#' + id ); if ( container.length ) { container.remove(); } else { $( '#contentSub' ).after( makeContainer( id ) ); createMap( id, center, zoom, mapTitle, options ); } } ); } } // returning show parameter string as an array function getShow( s ) { return ( s ) ? JSON.parse( s ) : defaultShowArray; } // replace the Maplink links by MapTools to show Wikivoyage controls // see also: phabricator T180909 function replaceMaplinks() { var links = $( kartographerSelector ); if ( !links.length ) { return; } var id = fullScreenContainerId; var options = { withClose: true, withControls: true, withData: true, show: null, withDialog: true, allowFullScreen: false, isFullScreen: true, featureType: 'maplink' }; var center, color, isInvers, lat, lon, name, symbolText, target, wrapper, zoom, $this; links.each( function() { $this = $( this ); $this.attr( 'href', '#' ) .css( { cursor: 'pointer', 'pointer-events': 'auto', 'text-decoration': 'none' } ); $this.click( function( event ) { event.stopImmediatePropagation(); event.preventDefault(); // marker could contain an image -> closest target = $( event.target ).closest( kartographerSelector ); wrapper = target.closest( markerSelector ); lat = target.attr( dataLat ); lon = target.attr( dataLon ); center = [ lat, lon ]; zoom = getZoom( target.attr( dataZoom ), defaultMaplinkZoomLevel ); name = wrapper.attr( dataName ) || ''; symbolText = target.text(); if ( symbolText !== '' ) { color = wrapper.attr( dataColor ); isInvers = target.closest( '.listing-map-inverse' ).length; } if ( name === '' ) { name = symbolText; } else if ( name !== '' && symbolText !== '' ) { name = symbolText + ': ' + name; } options.show = getShow( target.attr( dataOverlays ) ); $body.append( makeContainer( id ) ); createMap( id, center, zoom, name, options, color, isInvers ); return false; // don't follow the link } ); } ); } // adding a magnify button to Kartographer container function addMagnifyButton() { var maps = $( mapframeContainerSelector ); if ( !maps.length ) { return; } var id = fullScreenContainerId; var options = { withClose: true, withControls: true, withData: true, show: null, withDialog: true, allowFullScreen: false, isFullScreen: true, featureType: 'maplink' }; var caption, center, height, link, map, name, target, $this, zoom, zoomIncr; maps.each( function() { $this = $( this ); // no magnify button if zoom is already maxZoomLevel // not in frameless mode map = $( mapframeMapSelector, $this ).first(); caption = $( '.thumbcaption', $this ).first(); zoom = getZoom( map.attr( dataZoom ) ); if ( map.length && caption.length && zoom < maxZoomLevel ) { link = $( '<a class="internal"></a>' ) .css( { cursor: 'pointer' } ) .attr( 'title', messages.magnifyButtonTitle ) .click( function( event ) { target = $( event.target ); map = target.closest( mapframeContainerSelector ); caption = $( '.thumbcaption', map ).first(); name = caption.text(); // getting initial position from data if lat or lon // or zoom are undefined map = $( mapframeMapSelector, map ).first(); center = [ map.attr( dataLat ), map.attr( dataLon ) ]; zoom = Number( map.attr( dataZoom ) ); if ( isNaN( zoom ) ) { zoom = undefined; } else { zoomIncr = 1; height = screen.height / map.attr( dataHeight ); if ( height > 4 ) { zoomIncr++; } if ( height > 8 ) { zoomIncr++; } zoom += zoomIncr; if ( zoom > maxZoomLevel ) { zoom = maxZoomLevel; } } options.show = getShow( map.attr( dataOverlays ) ); $body.append( makeContainer( id ) ); createMap( id, center, zoom, name, options ); } ); caption.prepend( $( '<div class="magnify"></div>' ).append( link ) ); } } ); } // adding all tools // called by getKartographerLiveData() function addMapTools() { // groups array is set by getKartographerLiveData() // if groups array is empty try to get data from article if ( !groups.length ) { getPOIsFromArticle(); } addMagnifyButton(); indicatorMap(); replaceMaplinks(); } function init() { setupMessages(); defaultShowArray = JSON.parse( messages.defaultShow ), getKartographerLiveData(); // calling addMapTools() } return { init: init }; } (); $( mapTools.init ); } ( jQuery, mediaWiki ) ); //</nowiki>