MediaWiki:Gadget-MapTools.js – Reiseführer auf Wikivoyage
Zum Inhalt springen
Aus Wikivoyage
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
//
/*****************************************************************************
* mapTools v2.2, 2026-04-01
* 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'
// technical constants
const
version
'2026-04-01'
dataLat
'data-lat'
dataLon
'data-lon'
dataZoom
'data-zoom'
dataName
'data-name'
dataHeight
'data-height'
dataOverlays
'data-overlays'
fallbackLang
'en'
// technical constants
const
maxZoomLevel
19
defaultMaplinkZoomLevel
17
defaultMapZoomLevel
14
defaultProperties
'stroke-width'
'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
'voy-listing-name'
imageClass
'voy-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'
// 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
let
defaultShowArray
messages
{};
// storing GeoJSON data
let
data
{};
// array of objects: { name: group.name, attribution: attributions }
let
groups
[],
multiPointUsed
false
// copying translation strings to messages depending on chain languages
const
addMessages
strings
chain
=>
for
let
chain
length
>=
--
if
strings
hasOwnProperty
chain
extend
messages
strings
chain
);
};
// copying translation strings to messages
const
setupMessages
()
=>
addMessages
wikiStrings
pageLang
fallbackLang
);
const
chain
userLang
==
pageLang
pageLang
fallbackLang
userLang
pageLang
fallbackLang
];
addMessages
userStrings
chain
);
};
// creating a Kartographer map
const
createMap
id
center
zoom
caption
options
bgColor
textColor
=>
mw
loader
using
'ext.kartographer.box'
).
then
function
()
const
$id
'#'
id
);
// for simple full-screen map
if
options
withDialog
&&
options
isFullScreen
$body
css
overflow
'hidden'
);
$id
css
position
'fixed'
height
'100%'
width
'100%'
top
left
'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
const
kartoBox
require
'ext.kartographer.box'
),
map
kartoBox
map
container
$id
],
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
let
group
layerOptions
if
options
withData
&&
groups
length
for
options
show
length
++
for
groups
length
++
group
groups
];
if
group
name
===
options
show
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
const
controls
'.leaflet-top.leaflet-right'
$id
),
control
''
append
''
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
bgColor
&&
options
withDialog
setTimeout
function
()
const
footCaption
footCaptionSelector
);
if
footCaption
length
const
captionArray
footCaption
text
().
split
":"
),
classes
captionMarkerClass
footCaption
html
mw
format
'$4$5'
classes
bgColor
textColor
captionArray
],
captionArray
||
''
);
},
700
);
);
);
};
// creating GeoJSON data separated by group
const
singleDataset
bgColor
symbol
title
lat
lon
description
group
=>
if
data
hasOwnProperty
group
data
group
[];
data
group
].
push
'type'
'Feature'
properties
'marker-color'
bgColor
'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)
const
getGeoJSON
obj
=>
const
world
3600
180
],
3600
180
],
3600
180
],
3600
180
],
3600
180
];
let
promise
coordinates
feature
geometry
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
geoJSON
features
length
++
geometry
geoJSON
features
].
geometry
if
geometry
continue
// push only first polygon
switch
geometry
type
case
'Polygon'
coordinates
push
geometry
coordinates
);
break
case
'MultiPolygon'
for
geometry
coordinates
length
++
coordinates
push
geometry
coordinates
][
);
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
obj
features
length
++
feature
obj
features
];
if
isEmptyObject
feature
properties
feature
properties
properties
else
feature
properties
extend
{},
properties
feature
properties
);
},
function
()
// failed. Do nothing.
);
return
promise
};
// Creating attribution strings
const
getAttribution
obj
=>
let
link
''
switch
obj
service
case
'page'
const
url
new
URL
obj
url
location
origin
),
title
url
searchParams
get
'title'
);
link
mw
msg
'project-localized-name-commonswiki'
': '
''
title
''
break
default
// other services
return
link
};
// getting Kartographer live data
const
getKartographerLiveData
()
=>
let
group
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
data
group
].
length
++
obj
data
group
][
];
if
obj
geometry
&&
obj
geometry
type
===
'MultiPoint'
multiPointUsed
true
// expand external data
if
obj
type
===
'ExternalData'
&&
obj
url
promiseArray
push
getGeoJSON
obj
);
if
obj
service
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
data
&&
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
};
// returning zoom parameter string as a valid number
const
getZoom
defaultValue
=>
const
zoom
typeof
==
'string'
parseInt
if
zoom
||
zoom
maxZoomLevel
return
defaultValue
||
defaultMapZoomLevel
return
zoom
};
const
makeContainer
id
=>
'#'
id
).
remove
();
return
''
id
id
role
'dialog'
'data-version'
version
);
};
// displaying a map by clicking the geo-indicator button
const
indicatorMap
()
=>
let
indicator
indicatorSelector
).
first
();
if
indicator
length
return
if
data
data
{};
groups
[];
defaultShowArray
push
messages
defaultGroupName
);
indicatorCoordsSelector
).
attr
'target'
'_blank'
);
const
id
indicatorMapContainerId
options
withClose
true
withControls
true
withData
true
show
defaultShowArray
withDialog
false
allowFullScreen
true
isFullScreen
false
featureType
'mapframe'
};
const
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
const
mapTitle
mw
format
messages
mapOf
pageTitle
);
if
isMinerva
// mobile view
// add indicator action button and event handler
const
indicatorImg
''
class
'voy-indicator-img'
src
indicatorGlobeImgSrc
width
'20'
height
'20'
);
indicator
''
id
'mw-indicator-i3-geo'
title
mw
format
messages
indicatorButtonTitle
pageTitle
),
class
'mw-indicator'
// cdx-button
href
'#'
css
display
'inline-block'
append
indicatorImg
append
`
${
messages
indicatorActionLabel
`
);
indicator
'
id
'page-actions-i3-geo'
class
'page-actions-menu__list-item'
append
indicator
click
function
()
'#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
()
const
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
const
getShow
=>
return
JSON
parse
defaultShowArray
};
// replace the Maplink links by MapTools to show Wikivoyage controls
const
replaceMaplinks
()
=>
const
links
kartographerSelector
);
if
links
length
||
multiPointUsed
return
const
id
fullScreenContainerId
options
withClose
true
withControls
true
withData
true
show
defaultShowArray
withDialog
true
allowFullScreen
false
isFullScreen
true
featureType
'maplink'
};
links
each
function
()
const
$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
const
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
),
bgColor
target
css
'background-color'
),
textColor
target
css
'color'
);
let
name
wrapper
attr
dataName
||
''
symbolText
target
text
();
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
bgColor
textColor
);
return
false
// don't follow the link
);
);
};
// adding a magnify button to Kartographer container
const
addMagnifyButton
()
=>
const
maps
mapframeContainerSelector
);
if
maps
length
||
multiPointUsed
return
const
id
fullScreenContainerId
options
withClose
true
withControls
true
withData
true
show
defaultShowArray
withDialog
true
allowFullScreen
false
isFullScreen
true
featureType
'maplink'
};
maps
each
function
()
const
$this
this
);
// no magnify button if zoom is already maxZoomLevel
// not in frameless mode
const
map
mapframeMapSelector
$this
).
first
(),
caption
'.thumbcaption'
$this
).
first
();
let
zoom
getZoom
map
attr
dataZoom
);
if
map
length
&&
caption
length
&&
zoom
maxZoomLevel
const
link
''
css
cursor
'pointer'
attr
'title'
messages
magnifyButtonTitle
click
function
event
const
target
event
target
);
let
map
target
closest
mapframeContainerSelector
);
const
caption
'.thumbcaption'
map
).
first
(),
name
caption
text
();
// getting initial position from data if lat or lon
// or zoom are undefined
map
mapframeMapSelector
map
).
first
();
const
center
map
attr
dataLat
),
map
attr
dataLon
];
let
zoom
Number
map
attr
dataZoom
);
if
isNaN
zoom
zoom
undefined
else
let
zoomIncr
const
height
screen
height
map
attr
dataHeight
);
if
height
zoomIncr
++
if
height
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
''
).
append
link
);
);
};
// adding all tools
// called by getKartographerLiveData()
const
addMapTools
()
=>
addMagnifyButton
();
indicatorMap
();
replaceMaplinks
();
};
const
init
()
=>
setupMessages
();
defaultShowArray
JSON
parse
messages
defaultShow
);
getKartographerLiveData
();
// calling addMapTools()
};
init
();
jQuery
mediaWiki
);
//
Abgerufen von „
MediaWiki
Gadget-MapTools.js
Abschnitt hinzufügen
US