Module:Infobox mapframe
Appearance
![]() | This module is rated as beta, and is ready for widespread use. It is still new and should be used with some caution to ensure the results are as expected. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This Lua module is used on approximately 1,030,000 pages, or roughly 2% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
![]() | This module depends on the following other modules: |
Related pages |
---|
Usage
Module that automatically makes a mapframe suitable for an infobox automatically, with minimal user input.
Generic usage
Just use the template {{Infobox mapframe}}, following the documentation there. This module may also be imported to another Lua module.
Automatic maps in infoboxes
- Example edits: Template:Infobox prison, Template:Infobox prison/doc
Edit the infobox template (or its sandbox). Add lines like the following examples to the infobox. Replace numbers (4, or 97 to 99), with the appropriate number based on how many other image or data parameters are already present.
If placing near the top of infobox | If placing at/near the bottom of the infobox |
---|---|
| image4 = {{#invoke:Infobox mapframe|auto}} | caption4 = {{#invoke:Infobox mapframe|autocaption}} |
| header97 = {{#if:{{{mapframe|}}}|Location}} | data98 = {{#invoke:Infobox mapframe|auto}} | data99 = {{#invoke:Infobox mapframe|autocaption}} |
If the template has a {{#invoke:Check for unknown parameters}}
, add the following parameters:
| mapframe | mapframe-area_km2 | mapframe-area_mi2 | mapframe-caption | mapframe-coord | mapframe-coordinates | mapframe-custom | mapframe-frame-coord | mapframe-frame-coordinates | mapframe-frame-height | mapframe-frame-width | mapframe-geomask | mapframe-geomask-fill | mapframe-geomask-fill-opacity | mapframe-geomask-stroke-color | mapframe-geomask-stroke-colour | mapframe-geomask-stroke-width | mapframe-height | mapframe-id | mapframe-length_km | mapframe-length_mi | mapframe-marker | mapframe-marker-color | mapframe-marker-colour | mapframe-point | mapframe-shape | mapframe-shape-fill | mapframe-shape-fill-opacity | mapframe-stroke-color | mapframe-stroke-colour | mapframe-stroke-width | mapframe-switcher | mapframe-width | mapframe-wikidata | mapframe-zoom
Once this is done, the above parameters will be available to users of the template.
- Defaults values for these parameters can also be specified in the #invoke calls above, e.g.
{{#invoke:Infobox mapframe|auto|mapframe-marker=library}}
means that the library marker will be used, unless a different value is passed in to the template. - The maps are off by default, which means maps will not be displayed unless
|mapframe=yes
is present in the template call. To turn maps on by default, in the #invoke calls above add|onByDefault=yes
– which means maps will be displayed unless|mapframe=no
is present in the template call. onByDefault can also be set to a conditional, such as if another parameters is present, e.g.|onByDefault={{#if:{{{pushpin_map|}}}|no|yes}}
- Add the new parameters to the document. You can use
{{Infobox mapframe/doc/parameters}}
. If any default values are modified in the #invoke calls, add them as parameters in the form |parameter-name=value. The default output is shown here collapsed:
Extended content
|
---|
|
- Parameters can also be added to the TemplateData can also be added by copy-pasting from
See also
local mf = require('Module:Mapframe')
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
-- Defaults
local DEFAULT_FRAME_WIDTH = "270"
local DEFAULT_FRAME_HEIGHT = "200"
local DEFAULT_ZOOM = 10
local DEFAULT_GEOMASK_STROKE_WIDTH = "1"
local DEFAULT_GEOMASK_STROKE_COLOR = "#777777"
local DEFAULT_GEOMASK_FILL = "#888888"
local DEFAULT_GEOMASK_FILL_OPACITY = "0.5"
local DEFAULT_SHAPE_STROKE_WIDTH = "3"
local DEFAULT_SHAPE_STROKE_COLOR = "#FF0000"
local DEFAULT_SHAPE_FILL = "#606060"
local DEFAULT_SHAPE_FILL_OPACITY = "0.5"
local DEFAULT_LINE_STROKE_WIDTH = "5"
local DEFAULT_LINE_STROKE_COLOR = "#FF0000"
local DEFAULT_MARKER_COLOR = "#5E74F3"
-- Trim whitespace from args, and remove empty args
function trimArgs(argsTable)
local cleanArgs = {}
for key, val in pairs(argsTable) do
if type(val) == 'string' then
val = val:match('^%s*(.-)%s*$')
if val ~= '' then
cleanArgs[key] = val
end
else
cleanArgs[key] = val
end
end
return cleanArgs
end
function getBestStatement(item_id, property_id)
if not(item_id) or not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then
return false
end
local statements = mw.wikibase.getBestStatements(item_id, property_id)
if not statements or #statements == 0 then
return false
end
local hasNoValue = ( statements[1].mainsnak and statements[1].mainsnak.snaktype == 'novalue' )
if hasNoValue then
return false
end
return statements[1]
end
function hasWikidataProperty(item_id, property_id)
return getBestStatement(item_id, property_id) and true or false
end
function getStatementValue(statement)
return statement and statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value or nil
end
function relatedEntity(item_id, property_id)
local value = getStatementValue( getBestStatement(item_id, property_id) )
return value and value.id or false
end
function idType(id)
if not id then
return nil
elseif mw.ustring.match(id, "[Pp]%d+") then
return "property"
elseif mw.ustring.match(id, "[Qq]%d+") then
return "item"
else
return nil
end
end
function getZoom(value, unit)
local length_km
if unit == 'km' then
length_km = tonumber(value)
elseif unit == 'mi' then
length_km = tonumber(value)*1.609344
elseif unit == 'km2' then
length_km = math.sqrt(tonumber(value))
elseif unit == 'mi2' then
length_km = math.sqrt(tonumber(value))*1.609344
end
-- max for zoom 2 is 6400km, for zoom 3 is 3200km, for zoom 4 is 1600km, etc
local zoom = math.floor(8 - (math.log10(length_km) - 2)/(math.log10(2)))
-- limit to values below 17
zoom = math.min(17, zoom)
-- take off 1 when calculated from area, to account for unusual shapes
if unit == 'km2' or unit == 'mi2' then
zoom = zoom - 1
end
-- minimum value is 1
return math.max(1, zoom)
end
function shouldAutoRun(frame)
-- Check if should be running
local explicitlyOn = yesno(mw.text.trim(frame.getParent(frame).args.mapframe or "")) -- true of false or nil
local onByDefault = (explicitlyOn == nil) and yesno(mw.text.trim(frame.args.onByDefault or ""), false) -- true or false
return explicitlyOn or onByDefault
end
function argsFromAuto(frame)
-- Get args from the frame (invoke call) and the parent (template call).
-- Frame arguments are default values which are overridden by parent values
-- when both are present
local args = getArgs(frame, {parentFirst = true})
-- Discard args not prefixed with "mapframe_", remove that prefix from those that remain
local fixedArgs = {}
for name, val in pairs(args) do
local fixedName = string.match(name, "^mapframe_(.+)$" )
if fixedName then
fixedArgs[fixedName] = val
-- allow coord, coordinates, id to be unprefixed
elseif name == "coordinates" or name == "coord" or name == "id" then
fixedArgs[name] = val
-- allow qid to be unprefixed, map to id (if not already present)
elseif name == "qid" and not fixedArgs.id then
fixedArgs.id = val
end
end
return fixedArgs
end
local p = {}
p.autocaption = function(frame)
if not shouldAutoRun(frame) then return "" end
local args = argsFromAuto(frame)
if args.caption then return args.caption end
local maskItem
local maskType = idType(args.geomask)
if maskType == 'item' then
maskItem = args.geomask
elseif maskType == "property" then
maskItem = relatedEntity(args.id or mw.wikibase.getEntityIdForCurrentPage(), args.geomask)
end
local maskItemLabel = maskItem and mw.wikibase.getLabel( maskItem )
return maskItemLabel and "Location in "..maskItemLabel or ""
end
p.auto = function(frame)
if not shouldAutoRun(frame) then return "" end
local args = argsFromAuto(frame)
local mapframe = p._main(args)
return frame:preprocess(mapframe)
end
p.main = function(frame)
local parent = frame.getParent(frame)
local parentArgs = parent.args
local mapframe = p._main(parentArgs)
return frame:preprocess(mapframe)
end
p._main = function(_config)
-- `config` is the args passed to this module
local config = trimArgs(_config)
-- Use wikidata by default
local useWikidata = true
-- Do not use wikidata when coords are specified, unless explicitly set
if config.coord then
useWikidata = config.wikidata and true or false
end
-- Require wikidata item, or specified coords
local wikidataId = config.id or mw.wikibase.getEntityIdForCurrentPage()
if not(wikidataId) and not(config.coord) then
return ''
end
-- Require coords (specified or from wikidata), so that map will be centred somewhere
-- (P625 = coordinate location)
local hasCoordinates = hasWikidataProperty(wikidataId, 'P625') or config.coordinates or config.coord
if not hasCoordinates then
return ''
end
-- `args` is the arguments which will be passed to the mapframe module
local args = {}
-- Some defaults/overrides for infobox presentation
args.display = "inline"
args.frame = "yes"
args.plain = "yes"
args["frame-width"] = config["frame-width"] or DEFAULT_FRAME_WIDTH
args["frame-height"] = config["frame-height"] or DEFAULT_FRAME_HEIGHT
args["frame-align"] = "center"
args["frame-coord"] = config["frame-coordinates"] or config["frame-coord"] or ""
-- Note: config["coordinates"] or config["coord"] should not be used for the alignment of the frame;
-- see talk page ( https://en.wikipedia.org/wiki/Special:Diff/876492931 )
-- deprecated lat and long parameters
args["frame-lat"] = config["frame-lat"] or config["frame-latitude"] or ""
args["frame-long"] = config["frame-long"] or config["frame-longitude"] or ""
-- Calculate zoom from length or area (converted to km or km2)
if config.length_km then
args.zoom = getZoom(config.length_km, 'km')
elseif config.length_mi then
args.zoom = getZoom(config.length_mi, 'mi')
elseif config.area_km2 then
args.zoom = getZoom(config.area_km2, 'km2')
elseif config.area_mi2 then
args.zoom = getZoom(config.area_mi2, 'mi2')
else
args.zoom = config.zoom or DEFAULT_ZOOM
end
-- Conditionals: whether point, geomask should be shown
local hasOsmRelationId = hasWikidataProperty(wikidataId, 'P402') -- P402 is OSM relation ID
local shouldShowPointMarker;
if config.point == "on" then
shouldShowPointMarker = true
elseif config.point == "none" then
shouldShowPointMarker = false
else
shouldShowPointMarker = not(hasOsmRelationId) or (config.marker and config.marker ~= 'none') or (config.coordinates or config.coord)
end
local shouldShowShape = config.shape ~= 'none'
local shapeType = config.shape == 'inverse' and 'shape-inverse' or 'shape'
local shouldShowLine = config.line ~= 'none'
local maskItem
local maskType = idType(config.geomask)
if maskType == 'item' then
maskItem = config.geomask
elseif maskType == "property" then
maskItem = relatedEntity(wikidataId, config.geomask)
end
-- Keep track of arg numbering
local argNumber = ''
local function incrementArgNumber()
if argNumber == '' then
argNumber = 2
else
argNumber = argNumber + 1
end
end
-- Geomask
if maskItem then
args["type"..argNumber] = "shape-inverse"
args["id"..argNumber] = maskItem
args["stroke-width"..argNumber] = config["geomask-stroke-width"] or DEFAULT_GEOMASK_STROKE_WIDTH
args["stroke-color"..argNumber] = config["geomask-stroke-color"] or config["geomask-stroke-colour"] or DEFAULT_GEOMASK_STROKE_COLOR
args["fill"..argNumber] = config["geomask-fill"] or DEFAULT_GEOMASK_FILL
args["fill-opacity"..argNumber] = config["geomask-fill-opacity"] or DEFAULT_SHAPE_FILL_OPACITY
-- Recalculate zoom based on mask area, if available, and not set in config
if not config.zoom then
local maskArea = getStatementValue( getBestStatement(maskItem, 'P2046') )
if maskArea and maskArea.amount then
if maskArea.unit == 'http://www.wikidata.org/entity/Q712226' then -- square kilometre
args.zoom = getZoom(maskArea.amount, 'km2')
elseif maskArea.unit == 'http://www.wikidata.org/entity/Q232291' then -- square mile
args.zoom = getZoom(maskArea.amount, 'mi2')
end
end
end
incrementArgNumber()
end
-- Shape (or shape-inverse)
if useWikidata and shouldShowShape then
args["type"..argNumber] = shapeType
if config.id then args["id"..argNumber] = config.id end
args["stroke-width"..argNumber] = config["shape-stroke-width"] or config["stroke-width"] or DEFAULT_SHAPE_STROKE_WIDTH
args["stroke-color"..argNumber] = config["shape-stroke-color"] or config["shape-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_SHAPE_STROKE_COLOR
args["fill"..argNumber] = config["shape-fill"] or DEFAULT_SHAPE_FILL
args["fill-opacity"..argNumber] = config["shape-fill-opacity"] or DEFAULT_SHAPE_FILL_OPACITY
incrementArgNumber()
end
-- Line
if useWikidata and shouldShowLine then
args["type"..argNumber] = "line"
if config.id then args["id"..argNumber] = config.id end
args["stroke-width"..argNumber] = config["line-stroke-width"] or config["stroke-width"] or DEFAULT_LINE_STROKE_WIDTH
args["stroke-color"..argNumber] = config["line-stroke-color"] or config["line-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_LINE_STROKE_COLOR
incrementArgNumber()
end
-- Point
if shouldShowPointMarker then
args["type"..argNumber] = "point"
if config.id then args["id"..argNumber] = config.id end
if config.coord then args["coord"..argNumber] = config.coord end
if config.marker then args["marker"..argNumber] = config.marker end
args["marker-color"..argNumber] = config["marker-color"] or config["marker-colour"] or DEFAULT_MARKER_COLOR
incrementArgNumber()
end
local mapframe = mf._main(args)
local tracking = hasOsmRelationId and '' or '[[Category:Infobox mapframe without OSM relation ID on Wikidata]]'
return mapframe .. tracking
end
return p