MediaWiki: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)
- Internet Explorer/Edge: mantenga presionada Ctrl mientras pulsa Actualizar, o presione Ctrl+F5
- Opera: Presiona Ctrl+F5.
//<nowiki>
/***************************************************************************
* mapTools v1.9, 2020-06-16
* Several map creation and supporting tools
* Original author: Roland Unger
* Support of desktop and mobile views
* Documentation: https://de.wikivoyage.org/wiki/Wikivoyage:mapTools.js
* License: GPL-2.0+, CC-by-sa 3.0
***************************************************************************/
( function( $, mw ) {
'use strict';
var mapTools = function() {
var indicatorClass = '.wv-coord-indicator';
var imgSrc = 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/WMA_button2b.png/24px-WMA_button2b.png';
var mapContainerId = 'wv-topMap';
var maxZoomLevel = 19;
var defaultZoomLevel = 17;
var defaultMapZoomLevel = 14;
var defaultNearbyZoomLevel = 10;
var nearbyClass = '.wv-open-nearby-map';
var fullScreenContainerId = 'wv-fullScreenMap';
var articlesMapId = 'wv-articles-map';
var articlesMapTitle = 'Übersicht der Wikivoyage-Artikel';
var mapframeContainer = '.mw-kartographer-container';
var mapframeMap = '.mw-kartographer-map';
var containerClass = '.vcard'; // wrapper class of a single marker or listing
var kartographerClass = '.mw-kartographer-maplink';
var nameClass = 'listing-name';
var imageClass = 'listing-image';
var dataLat = 'data-lat';
var dataLon = 'data-lon';
var dataZoom = 'data-zoom';
var dataName = 'data-name';
var dataColor = 'data-color';
var dataSymbol = 'data-symbol';
var dataNumber = 'data-number';
var dataGroup = 'data-group-translated'; // other wikis: 'data-type'
var dataDialog = 'data-dialog';
var dataHeight = 'data-height';
var dataOverlays = 'data-overlays';
var defaultShow = '["Maske","Track","Aktivitaet","Anderes","Anreise","Ausgehen","Aussicht","Besiedelt","Fehler","Gebiet","Kaufen","Kueche","Sehenswert","Unterkunft","aquamarinblau","blau","blaugruen","braun","cosmos","gold","grau","hellgruen","koenigsblau","magentarot","marineblau","orange","pflaumenblau","purpurrot","rot","rotbraun","schokobraun","schwarz","silber","stahlblau","violett","waldgruen"]';
var defaultShowArray = JSON.parse( defaultShow );
var titleIndicator = 'Klick öffnet und schließt die Karte für ';
var titleNearby = 'Klick öffnet die Umgebungskarte für ';
var titleClose = 'Schließen';
var mapOf = 'Karte von ';
var nearbyMapOf = 'Umgebungskarte von ';
var mapCenter = 'Kartenzentrum';
var defaultGroup = 'Karte';
var mask = 'Maske'; // mask layer name
var track = 'Track'; // track layer name
var magnifyMap = 'Karte vergrößern';
var data = {};
var groups = [];
// creating a Kartographer map
var createMap = function( id, center, zoom, caption, options ) {
function contains( arr, obj ) {
for ( var i = 0; i < arr.length; i++ ) {
if (arr[ i ] === obj) return true;
}
return false;
}
mw.loader.using( [ 'ext.kartographer.box' ] ).then( function() {
var $id = $( id );
var $body = $( 'body' );
var 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 = mw.loader.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 );
// the following property is used by Kartographer.js
if ( options.toggleNearby ) map.toggleNearby = true;
// adding markers by group names
if ( options.withData )
$.each( groups, function( i, group ) {
if ( contains( options.show, group.id ) ) {
layerOptions = {};
if ( group.name !== '' ) {
layerOptions.name = group.name;
layerOptions.attribution = group.name;
}
else
if ( group.attribution && group.attribution !== '' )
layerOptions.attribution = group.attribution;
map.addGeoJSONLayer( group.id, data[ group.id ], layerOptions );
}
} );
// 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() {
mw.loader.require( 'ext.kartographer.dialog' ).render( map );
} );
} );
}
else {
// adding Close control
if ( options.withClose ) {
var controls = $( '.leaflet-top.leaflet-right', $id );
var control = $( '<div class="leaflet-bar leaflet-control-static leaflet-control"></div>' )
.append( $( '<a class="closeControl"></a>' )
.attr( { title: titleClose, role: 'button',
'aria-disabled': 'false' } )
.click( function() {
$id.remove();
if ( options.isFullScreen )
$body.css( { overflow: 'auto' } );
} )
);
controls.prepend( control );
}
if ( options.isFullScreen ) {
$( document ).keydown( function( event ) {
if ( event.keyCode === 27 ) { // ESC
event.preventDefault();
$id.remove();
$body.css( { overflow: 'auto' } );
}
} );
}
else {
map.doWhenReady( function () {
map.$container.css( 'backgroundImage', '' );
map.$container.find( '.leaflet-marker-icon' ).each( function () {
var markerHeight = $( this ).height() / 2 + 10;
$( this ).css( {
clip: 'rect(auto auto ' + markerHeight + 'px auto)'
} );
} );
} );
}
// adding Nearby and Layers controls using Kartographer.js
if ( options.withControls ) mw.hook( 'wikipage.maps' ).fire( map );
}
} );
};
// creating GeoJSON data separated by group
var singleDataset = function( color, symbol, title, lat, lon,
description, group ) {
if ( group === null || group === '' ) group = defaultGroup;
if ( !data.hasOwnProperty( group ) ) data[ group ] = [];
data[ group ].push( {
'type': 'Feature',
properties: {
'marker-color': color,
'marker-size': 'medium',
'marker-symbol': symbol.toLowerCase(),
title: title,
description: description
},
geometry: {
'type': 'Point',
coordinates: [ lon, lat ]
}
} );
};
// Getting GeoJSON data sets from external sources (OSM, Commons)
var getGeoJSON = function( obj ) {
var promise, coordinates, geometry, i, j;
var world = [ [ [ 3600, -180 ], [ 3600, 180 ], [ -3600, 180 ], [ -3600, -180 ], [ 3600, -180 ] ] ];
var 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 = { 'stroke-width': 2, 'fill-opacity': 0.5 };
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++ )
if ( $.isEmptyObject( obj.features[ i ].properties ) )
obj.features[ i ].properties = properties;
else
obj.features[ i ].properties =
$.extend( {}, properties, obj.features[ i ].properties );
}
}
}, function() {
// failed. Do nothing.
} );
return promise;
};
// Creating attribution strings
var getAttribution = function( obj ) {
var uri = new mw.Uri( obj.url );
var 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
var getKartographerLiveData = function() {
var group, i, obj;
var promiseArray = [];
var attributions, link;
data = mw.config.get( 'wgKartographerLiveData' );
if ( data !== null ) {
groups = [];
for ( group in data )
// ignoring empty groups
if ( data[ group ].length > 0 && group.charAt( 0 ) !== '_' ) {
attributions = [];
for (i = 0; i < data[ group ].length; i++) {
obj = data[ group ][i];
if ( obj.type === 'ExternalData' && obj.url ) {
promiseArray.push( getGeoJSON( obj ) );
link = getAttribution( obj );
if ( link !== '' ) attributions.push( link );
}
}
attributions = attributions.join( ', ' );
if ( group === mask ) groups.unshift( { id: group, name: '' } );
else groups.push( { id: group, name: '', attribution: attributions } );
}
}
groups.sort( function( a, b ) {
// mask has to be the first layer
if ( a.id === mask ) return -1;
if ( b.id === mask ) return 1;
if ( a.id === track && b.id === mask ) return 1;
if ( b.id === track && a.id === mask ) return -1;
if ( a.id === track && b.id !== mask ) return -1;
if ( b.id === track && a.id !== mask ) return 1;
return a.id.localeCompare( b.id );
} );
// wait for getting all external data
// regardless of failures, initMapTools() will be executed
var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
if ( isIE11 )
$.when.apply( $, promiseArray ).then( function() {
initMapTools();
} );
else {
if ( typeof Promise !== 'undefined' )
Promise.all( promiseArray )
.then( function() { initMapTools(); } )
.catch( function() { initMapTools(); } );
// initialization also in case of failures
// maybe external data are not shown
else
initMapTools(); // for really old browsers
}
return;
};
// getting all vCard/listing and marker information from article
var getData = function() {
var group;
// 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
// getData is called by getKartographerLiveData() therefore groups
// object is set
if ( groups.length > 0 ) return;
// no wgKartographerLiveData or empty arrays
data = {};
var markers = $( containerClass );
if ( markers.length === 0 ) return;
var lat, lon, title, symbol, color, desc, image;
var clone, link, wikiLink, $this;
markers.each( function() {
$this = $( this );
link = $( kartographerClass, $this ).first();
if ( link.length > 0 ) {
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();
if ( wikiLink.length > 0 )
title = wikiLink[0].outerHTML;
else
title = $this.attr( dataName );
// putting image to description
desc = '';
image = $( '.' + imageClass, $this );
if ( image.length > 0 )
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 );
}
} );
// creating sorted group list
groups = [];
for ( group in data ) groups.push( { id: group, name: '' } );
groups.sort( function( a, b ) {
// mask has to be the first layer
if ( a.id === mask ) return -1;
if ( b.id === mask ) return 1;
return a.id.localeCompare( b.id );
} );
};
// displaying a map by clicking the geo-indicator button
var indicatorMap = function() {
var indicator = $( indicatorClass ).first();
if ( indicator.length === 0) return;
var id = mapContainerId;
var options = {
withClose: true,
withControls: true,
withData: true,
show: defaultShowArray,
withDialog: false,
toggleNearby: false,
allowFullScreen: true,
isFullScreen: false,
featureType: 'mapframe'
};
var zoom = indicator.attr( dataZoom );
if ( zoom === undefined || zoom < 0 || zoom > maxZoomLevel )
zoom = defaultMapZoomLevel;
var lat = indicator.attr( dataLat );
var lon = indicator.attr( dataLon );
var center = [ lat, lon ];
// no POIs --> show blue map-center marker
if ( groups.length === 0 ) {
singleDataset( '#3366cc', '', mapCenter, lat, lon, '', defaultGroup );
groups = [ { id: defaultGroup, name: '' } ];
}
var indicatorImg, indicatorDiv;
if ( mw.config.get( 'skin' ) === 'minerva' ) { // mobile view
indicatorImg = $( '<img>' )
.attr( 'src', imgSrc )
.attr( 'title', titleIndicator + mw.config.get( 'wgTitle' ) )
.css( { cursor: 'pointer' } )
.click( function() {
var container = $( '#' + id );
if ( container.length === 0 ) {
$( '#bodyContent' ).prepend( $( '<div></div>' )
.attr( { 'id': id, role: 'dialog' } )
.css( 'margin-top', '0.7em' )
);
createMap( '#' + id, center, zoom,
mapOf + mw.config.get( 'wgTitle' ), options );
}
else container.remove();
} );
indicatorDiv = $( '<div id="mw-indicator-i3-geo" class="mw-indicator">' )
.append( indicatorImg )
.css( 'float', 'right' );
$( '#section_0' ).after( indicatorDiv );
}
else { // desktop views
// replacing indicator image and adding event handlers
$( indicatorClass + ' .map-globe-default' )
.css( { display: 'none' } );
$( indicatorClass + ' .map-globe-js' )
.css( { display: 'inline', cursor: 'pointer' } )
.attr( 'title', titleIndicator + mw.config.get( 'wgTitle' ) )
.click( function() {
var container = $( '#' + id );
if ( container.length === 0 ) {
$( '#contentSub' ).after( $( '<div></div>' )
.attr( { 'id': id, role: 'dialog' } )
);
createMap( '#' + id, center, zoom,
mapOf + mw.config.get( 'wgTitle' ), options );
}
else container.remove();
} );
}
};
// displaying nearby maps by clicking a nearby link
var nearbyMap = function() {
var nearbyLinks = $( nearbyClass );
if ( nearbyLinks.length === 0) return;
var id = fullScreenContainerId;
var options = {
withClose: true,
withControls: true,
withData: true,
show: defaultShowArray,
withDialog: true,
toggleNearby: true,
allowFullScreen: false,
isFullScreen: true,
featureType: 'maplink'
};
var $this, link, parent;
var center, dialog, lat, lon, zoom;
nearbyLinks.each( function() {
$this = $( this );
$this.parent().show();
// link will replace span tag: copying data
link = $( '<a></a>' )
.html( $this.html() )
.css( { cursor: 'pointer' } )
.attr( 'title', titleNearby + mw.config.get( 'wgTitle' ) )
.attr( dataLat, $this.attr( dataLat ) )
.attr( dataLon, $this.attr( dataLon ) );
zoom = $this.attr( dataZoom );
if ( zoom !== undefined ) link.attr( dataZoom, zoom );
dialog = $this.attr( dataDialog );
if ( dialog !== undefined ) link.attr( dataDialog, dialog );
link.click( function( event ) {
var container = $( '#' + id );
if ( container.length === 0 ) {
var target = $( event.target );
lat = target.attr( dataLat );
if ( lat === undefined ) lat = 0;
lon = target.attr( dataLon );
if ( lon === undefined ) lon = 0;
center = [ lat, lon ];
zoom = target.attr( dataZoom );
if ( zoom === undefined || zoom < 0 || zoom > maxZoomLevel )
zoom = defaultNearbyZoomLevel;
dialog = target.attr( dataDialog );
if ( dialog === undefined ) dialog = 'false';
options.withDialog = ( dialog === 'true' || dialog === 'yes' );
$( 'body' ).append( $( '<div></div>' )
.attr( { 'id': id, role: 'dialog' } )
);
createMap( '#' + id, center, zoom,
nearbyMapOf + mw.config.get( 'wgTitle' ), options );
}
else container.remove();
} );
$this.replaceWith( link );
} );
};
// replacing the Maplink links by MapTools to show Wikivoyage controls
// see also: phabricator T180909
var replaceMaplinks = function() {
var links = $( kartographerClass );
if ( links.length === 0 ) return;
var id = fullScreenContainerId;
var options = {
withClose: true,
withControls: true,
withData: true,
show: null,
withDialog: true,
toggleNearby: false,
allowFullScreen: false,
isFullScreen: true,
featureType: 'maplink'
};
var center, lat, lon, name, symbolText, target, wrapper, zoom, $this;
links.each( function() {
$this = $( this );
$this.removeAttr( 'href' )
.css( { cursor: 'pointer', 'pointer-events': 'auto',
'text-decoration': 'none' } );
$this.click( function( event ) {
// marker could be contain an image -> closest
target = $( event.target ).closest( kartographerClass );
wrapper = target.closest( containerClass );
lat = target.attr( dataLat );
lon = target.attr( dataLon );
center = [ lat, lon ];
zoom = target.attr( dataZoom );
if ( zoom === undefined || zoom < 0 || zoom > maxZoomLevel )
zoom = defaultZoomLevel;
name = wrapper.attr( dataName ) || '';
symbolText = target.text();
if ( name === '' ) {
name = symbolText;
symbolText = '';
}
if ( name !== '' && symbolText !== '' )
name = symbolText + ': ' + name;
options.show = target.attr( dataOverlays );
if ( options.show === undefined )
options.show = defaultShowArray;
else options.show = JSON.parse( options.show );
$( 'body' ).append( $( '<div></div>' )
.attr( { 'id': id, role: 'dialog' } ) );
createMap( '#' + id, center, zoom, name, options );
} );
} );
};
// showing all articles on an earth map
var articlesMap = function() {
var map = $( '#' + articlesMapId ).first();
if ( map.length === 0 ) return;
var options = {
withClose: false,
withControls: true,
withData: false,
withDialog: false,
toggleNearby: true,
allowFullScreen: false,
// because Nearby mode cannot be toggled in full-screen mode
isFullScreen: false,
featureType: 'mapframe'
};
var zoom = Math.floor( map.height() / 500 );
if ( zoom < 0 ) zoom = 0;
if ( zoom > maxZoomLevel ) zoom = maxZoomLevel;
createMap( '#' + articlesMapId, [ 0, 0 ], zoom, articlesMapTitle, options );
};
// adding a magnify button to Kartographer container
var addMagnifyButton = function() {
var maps = $( mapframeContainer );
if ( maps.length === 0 ) return;
var id = fullScreenContainerId;
var options = {
withClose: true,
withControls: true,
withData: true,
show: null,
withDialog: true,
toggleNearby: false,
allowFullScreen: false,
isFullScreen: true,
featureType: 'maplink'
};
var caption, center, height, link, map, name, target, zoom,
zoomIncr, $this;
maps.each( function() {
$this = $( this );
// no magnify button if zoom is already maxZoomLevel
// not in frameless mode
map = $( mapframeMap, $this ).first();
caption = $( '.thumbcaption', $this ).first();
zoom = Number( map.attr( dataZoom ) );
if ( isNaN( zoom ) ) zoom = defaultMapZoomLevel;
if ( map.length > 0 && caption.length > 0 && zoom < maxZoomLevel) {
link = $( '<a class="internal"></a>' )
.css( { cursor: 'pointer' } )
.attr( 'title', magnifyMap )
.click( function( event ) {
target = $( event.target );
map = target.closest( mapframeContainer );
caption = $( '.thumbcaption', map ).first();
name = caption.text();
// getting initial position from data if lat or lon
// or zoom are undefined
map = $( mapframeMap, 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 = map.attr( dataOverlays );
if ( options.show === undefined )
options.show = defaultShowArray;
else options.show = JSON.parse( options.show );
$( 'body' ).append( $( '<div></div>' )
.attr( { 'id': id, role: 'dialog' } ) );
createMap( '#' + id, center, zoom, name, options );
} );
caption.prepend( $( '<div class="magnify"></div>' ).append( link ) );
}
} );
};
// calling all functions
var initMapTools = function() {
getData(); // getting POIs from article
indicatorMap();
nearbyMap();
replaceMaplinks();
articlesMap();
addMagnifyButton();
};
var init = function() {
getKartographerLiveData();
// calls initMapTools
};
return { init: init };
} ();
$( mapTools.init );
} ( jQuery, mediaWiki ) );
//</nowiki>