MediaWiki:Gadget-MapTools.js

Nota: Después de publicar, quizás necesite actualizar la caché de su navegador para ver los cambios.

  • Firefox/Safari: Mantenga presionada la tecla Shift mientras pulsa el botón Actualizar, o presiona Ctrl+F5 o Ctrl+R (⌘+R en Mac)
  • Google Chrome: presione Ctrl+Shift+R (⌘+Shift+R en Mac)
  • Edge: mantenga presionada Ctrl mientras pulsa Actualizar, o presione Ctrl+F5
//<nowiki>
/*****************************************************************************
 * mapTools v2.1, 2023-10-17
 * 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 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',

			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 ver = '2023-03-29',
			$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.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>', {
					src: indicatorGlobeImgSrc,
					width: '20', height: '20'
				} );
				indicator = $( '<a>', {
						id: 'mw-indicator-i3-geo',
						title: mw.format( messages.indicatorButtonTitle, pageTitle ),
						class: 'mw-indicator', href: '#'
					} )
					.css( { display: 'inline-block' } )
					.append( indicatorImg )
					.append( document.createTextNode( ' ' + messages.indicatorActionLabel ) );
				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 );
						}
					} );
				$( '#page-actions #page-actions-edit' ).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>