Module:Location map
Jump to navigation
Jump to search
![]() | 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. |
Usage
This module implements the {{Location map}}, {{Location map+}}, {{Location map~}}, and {{Location map many}} templates. Please see the template pages for usage instructions.
require('Module:No globals')
local p = {}
local getArgs = require('Module:Arguments').getArgs
function p.getMapParams(map, frame)
if not map then
error('The name of the location map definition to use must be specified', 2)
end
local moduletitle = mw.title.new('Module:Location map/data/' .. map)
if not moduletitle then
error('"' .. map .. '" is not a valid name for a location map definition', 2)
elseif moduletitle.exists then
local mapData = mw.loadData('Module:Location map/data/' .. map)
return function(name, params)
if mapData[name] == nil then
return ''
elseif params then
return mw.message.newRawMessage(tostring(mapData[name]), unpack(params)):plain()
else
return mapData[name]
end
end
elseif mw.title.new('Template:Location map ' .. map).exists then
local cache = {}
return function(name, params)
if params then
return frame:expandTemplate{title = 'Location map ' .. map, args = { name, unpack(params) }}
else
if cache[name] == nil then
cache[name] = frame:expandTemplate{title = 'Location map ' .. map, args = { name }}
end
return cache[name]
end
end
else
error('Unable to find the specified location map definition. Neither "Module:Location map/data/' .. map .. '" nor "Template:Location map ' .. map .. '" exists', 2)
end
end
function p.data(frame, args, map)
if not args then
args = getArgs(frame, {frameOnly = true})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local params = {}
for k,v in ipairs(args) do
if k > 2 then
params[k-2] = v
end
end
return map(args[2], #params ~= 0 and params)
end
local hemisphereMultipliers = {
longitude = { W = -1, w = -1, E = 1, e = 1 },
latitude = { S = -1, s = -1, N = 1, n = 1 }
}
local function decdeg(degrees, minutes, seconds, hemisphere, decimal, direction)
if not degrees then
if not decimal then
error('No value was provided for ' .. direction, 2)
end
local retval = tonumber(decimal)
if retval then
return retval
end
error('The value "' .. decimal .. '" provided for ' .. direction .. ' is not valid', 2)
end
decimal = tonumber(degrees)
if not decimal then
error('The degree value "' .. degrees .. '" provided for ' .. direction .. ' is not valid', 2)
end
if minutes and not tonumber(minutes) then
error('The minute value "' .. minutes .. '" provided for ' .. direction .. ' is not valid', 2)
end
if seconds and not tonumber(seconds) then
error('The second value "' .. seconds .. '" provided for ' .. direction .. ' is not valid', 2)
end
decimal = decimal + (minutes or 0)/60 + (seconds or 0)/3600
if hemisphere then
local multiplier = hemisphereMultipliers[direction][hemisphere]
if not multiplier then
error('The hemisphere "' .. hemisphere .. '" provided for ' .. direction .. ' is not valid', 2)
end
decimal = decimal * multiplier
end
return decimal
end
-- effectively make removeBlanks false for caption and maplink, and true for everything else
-- p.top, p.bottom, and their callers need to use this
local function valueFunc(key, value)
if value then
value = mw.text.trim(value)
end
if value ~= '' or key == 'caption' or key == 'maplink' then
return value
end
end
local function getContainerImage(args, map)
if args.AlternativeMap then
return args.AlternativeMap
elseif args.relief and map('image1') ~= '' then
return map('image1')
else
return map('image')
end
end
function p.top(frame, args, map)
if not args then
args = getArgs(frame, {frameOnly = true, valueFunc = valueFunc})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local width
if not args.width then
width = math.floor((args.default_width or 240) * (tonumber(map('defaultscale')) or 1) + 0.5)
elseif mw.ustring.sub(args.width, -2) == 'px' then
width = mw.ustring.sub(args.width, 1, -3)
else
width = args.width
end
local retval = args.float == 'center' and '<div class="center">' or ''
if args.caption and args.caption ~= '' then
retval = retval .. '<div class="noviewer thumb '
if args.float == '"left"' or args.float == 'left' then
retval = retval .. 'tleft'
elseif args.float == '"center"' or args.float == 'center' or args.float == '"none"' or args.float == 'none' then
retval = retval .. 'tnone'
else
retval = retval .. 'tright'
end
retval = retval .. '"><div class="thumbinner" style="width:' .. (width + 2) .. 'px'
if args.border == 'none' then
retval = retval .. ';border:none'
elseif args.border then
retval = retval .. ';border-color:' .. args.border
end
retval = retval .. '"><div style="position:relative;width:' .. width .. 'px' .. (args.border ~= 'none' and ';border:1px solid lightgray">' or '">')
else
retval = retval .. '<div style="width:' .. width .. 'px;'
if args.float == '"left"' or args.float == 'left' then
retval = retval .. 'float:left;clear:left'
elseif args.float == '"center"' or args.float == 'center' then
retval = retval .. 'float:none;clear:both;margin-left:auto;margin-right:auto'
elseif args.float == '"none"' or args.float == 'none' then
retval = retval .. 'float:none;clear:none'
else
retval = retval .. 'float:right;clear:right'
end
retval = retval .. '"><div style="width:' .. width .. 'px;padding:0"><div style="position:relative;width:' .. width .. 'px">'
end
local image = getContainerImage(args, map)
retval = string.format(
'%s[[File:%s|%spx|%s%s]]',
retval,
image,
width,
args.alt or ((args.label or mw.title.getCurrentTitle().text) .. ' is located in ' .. map('name')),
args.maplink and ('|link=' .. args.maplink) or ''
)
if args.overlay_image then
return retval .. '<div style="position:absolute;top:0;left:0">[[File:' .. args.overlay_image .. '|' .. width .. 'px]]</div>'
else
return retval
end
end
function p.bottom(frame, args, map)
if not args then
args = getArgs(frame, {frameOnly = true, valueFunc = valueFunc})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local retval = '</div>'
if not args.caption then
retval = retval .. '<div style="font-size:90%;padding-top:3px">'
.. ((args.label or mw.title.getCurrentTitle().text) .. ' (' .. map('name') .. ')')
.. '</div>'
elseif args.caption == '' then
retval = retval .. '<div style="font-size:90%;padding-top:3px"></div>'
else
retval = retval .. '<div class="thumbcaption"><div class="magnify">[[File:Magnify-clip.png|link=File:' .. getContainerImage(args, map) .. ']]</div>' .. args.caption .. '</div>'
end
retval = retval .. '</div></div>'
if args.caption_undefined then
mw.log('Removed parameter caption_undefined used.')
local parent = frame:getParent()
if parent then
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
retval = retval .. '[[Category:Location maps with possible errors|Page using removed parameter]]'
end
if args.float == 'center' then
retval = retval .. '</div>'
end
return retval
end
function p.container(frame, args, map)
if not args then
args = getArgs(frame, {wrappers = 'Template:Location map+', valueFunc = valueFunc})
end
if not map then
map = p.getMapParams(args[1], frame)
end
return p.top(frame, args, map) .. (args.places or '') .. p.bottom(frame, args, map)
end
local function markOuterDiv(x, y, imageDiv, labelDiv)
return mw.html.create('div')
:cssText('position:absolute;top:' .. y .. '%;left:' .. x .. '%;height:0;width:0;margin:0;padding:0')
:node(imageDiv)
:node(labelDiv)
end
local function markImageDiv(mark, marksize, label, link, alt, title)
local builder = mw.html.create('div')
:cssText('position:absolute;text-align:center;left:-' .. math.floor(marksize / 2 + 0.5) .. 'px;top:-' .. math.floor(marksize / 2 + 0.5) .. 'px;width:' .. marksize .. 'px;font-size:' .. marksize .. 'px;line-height:0')
:attr('title', title)
if marksize ~= 0 then
builder:wikitext(string.format(
'[[File:%s|%dx%dpx|%s|link=%s%s]]',
mark,
marksize,
marksize,
label,
link,
alt and ('|alt=' .. alt) or ''
))
end
return builder
end
local function markLabelDiv(label, label_size, label_width, position, background, x, marksize)
local builder = mw.html.create('div')
:cssText('font-size:' .. label_size .. '%;line-height:110%;position:absolute;width:' .. label_width .. 'em')
local distance = math.floor(marksize / 2 + 1.5)
local spanCss
if position == 'top' then -- specified top
builder:cssText('bottom:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em;text-align:center')
elseif position == 'bottom' then -- specified bottom
builder:cssText('top:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em;text-align:center')
elseif position == 'left' or (tonumber(x) > 70 and position ~= 'right') then -- specified left or autodetected to left
builder:cssText('top:-0.75em;right:' .. distance .. 'px;text-align:right')
spanCss = 'float:right'
else -- specified right or autodetected to right
builder:cssText('top:-0.75em;left:' .. distance .. 'px;text-align:left')
spanCss = 'float:left'
end
builder = builder:tag('span')
:cssText('padding:1px')
:cssText(spanCss)
:wikitext(label)
if background then
builder:cssText('background-color:' .. background)
end
return builder:done()
end
local function getX(longitude, left, right)
local width = (right - left) % 360
if width == 0 then
width = 360
end
local distanceFromLeft = (longitude - left) % 360
-- the distance needed past the map to the right equals distanceFromLeft - width. the distance needed past the map to the left equals 360 - distanceFromLeft. to minimize page stretching, go whichever way is shorter
if distanceFromLeft - width / 2 >= 180 then
distanceFromLeft = distanceFromLeft - 360
end
return 100 * distanceFromLeft / width
end
local function getY(latitude, top, bottom)
return 100 * (top - latitude) / (top - bottom)
end
function p.mark(frame, args, map)
if not args then
args = getArgs(frame, {wrappers = 'Template:Location map~'})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local x, y, longitude, latitude
longitude = decdeg(args.lon_deg, args.lon_min, args.lon_sec, args.lon_dir, args.long, 'longitude')
latitude = decdeg(args.lat_deg, args.lat_min, args.lat_sec, args.lat_dir, args.lat, 'latitude')
local builder = mw.html.create()
if args.skew or args.lon_shift or args.markhigh then
mw.log('Removed parameter used in invocation.')
local parent = frame:getParent()
if parent then
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
builder:wikitext('[[Category:Location maps with possible errors|Page using removed parameter]]')
end
if (map('skew') ~= '') or (map('lat_skew') ~= '') or (map('crosses180') ~= '') then
mw.log('Removed parameter used in map definition. Map definition name is ' .. args[1])
builder:wikitext('[[Category:Location maps with possible errors|Map using removed parameter]]')
end
if map('x') ~= '' then
x = tonumber(mw.ext.ParserFunctions.expr(map('x', { latitude, longitude })))
else
x = tonumber(getX(longitude, map('left'), map('right')))
end
if map('y') ~= '' then
y = tonumber(mw.ext.ParserFunctions.expr(map('y', { latitude, longitude })))
else
y = tonumber(getY(latitude, map('top'), map('bottom')))
end
if (x < 0 or x > 100 or y < 0 or y > 100) and not args.outside then
mw.log('Mark placed outside map boundaries without outside flag set. x = ' .. x .. ', y = ' .. y)
local parent = frame:getParent()
if parent then
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
builder:wikitext('[[Category:Location maps with possible errors|Outside flag not set with mark outside map]]')
end
local mark = args.mark or map('mark')
if mark == '' then
mark = 'Red pog.svg'
end
local marksize = tonumber(args.marksize) or tonumber(map('marksize')) or 8
local imageDiv = markImageDiv(mark, marksize, args.label or mw.title.getCurrentTitle().text, args.link or '', args.alt, args[2])
local labelDiv
if args.label and args.position ~= 'none' then
labelDiv = markLabelDiv(args.label, args.label_size or 90, args.label_width or 6, args.position, args.background, x, marksize)
end
return builder:node(markOuterDiv(x, y, imageDiv, labelDiv))
end
function p.main(frame, args, map)
if not args then
args = getArgs(frame, {wrappers = 'Template:Location map', valueFunc = valueFunc})
end
if not args[1] then
args[1] = 'World'
end
if not map then
map = p.getMapParams(args[1], frame)
end
return p.top(frame, args, map) .. tostring( p.mark(frame, args, map) ) .. p.bottom(frame, args, map)
end
local function manyMakeArgs(fullArgs, n)
if n == 1 then
return {
fullArgs[1],
lat = fullArgs.lat1 or fullArgs.lat,
long = fullArgs.long1 or fullArgs.long,
lat_deg = fullArgs.lat1_deg or fullArgs.lat_deg,
lat_min = fullArgs.lat1_min or fullArgs.lat_min,
lat_sec = fullArgs.lat1_sec or fullArgs.lat_sec,
lat_dir = fullArgs.lat1_dir or fullArgs.lat_dir,
lon_deg = fullArgs.lon1_deg or fullArgs.lon_deg,
lon_min = fullArgs.lon1_min or fullArgs.lon_min,
lon_sec = fullArgs.lon1_sec or fullArgs.lon_sec,
lon_dir = fullArgs.lon1_dir or fullArgs.lon_dir,
mark = fullArgs.mark1 or fullArgs.mark,
marksize = fullArgs.mark1size or fullArgs.marksize,
link = fullArgs.link1 or fullArgs.link,
label = fullArgs.label1 or fullArgs.label,
label_size = fullArgs.label1_size or fullArgs.label_size,
position = fullArgs.position1 or fullArgs.pos1 or fullArgs.position or fullArgs.pos,
background = fullArgs.background1 or fullArgs.bg1 or fullArgs.background or fullArgs.bg
}
else
return {
fullArgs[1],
lat = fullArgs['lat' .. n],
long = fullArgs['long' .. n],
lat_deg = fullArgs['lat' .. n .. '_deg'],
lat_min = fullArgs['lat' .. n .. '_min'],
lat_sec = fullArgs['lat' .. n .. '_sec'],
lat_dir = fullArgs['lat' .. n .. '_dir'],
lon_deg = fullArgs['lon' .. n .. '_deg'],
lon_min = fullArgs['lon' .. n .. '_min'],
lon_sec = fullArgs['lon' .. n .. '_sec'],
lon_dir = fullArgs['lon' .. n .. '_dir'],
outside = fullArgs['outside' .. n],
mark = fullArgs['mark' .. n],
marksize = fullArgs['mark' .. n .. 'size'],
link = fullArgs['link' .. n],
label = fullArgs['label' .. n],
label_size = fullArgs['label' .. n .. '_size'],
position = fullArgs['position' .. n] or fullArgs['pos' .. n],
background = fullArgs['background' .. n] or fullArgs['bg' .. n]
}
end
end
function p.many(frame, args, map)
if not args then
args = getArgs(frame, {wrappers = 'Template:Location map many', valueFunc = valueFunc})
end
if not args[1] then
args[1] = 'World'
end
if not map then
map = p.getMapParams(args[1], frame)
end
local marks = {}
local markhigh
if args.markhigh then
mw.log('Removed parameter markhigh used.')
local parent = frame:getParent()
if parent then
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
markhigh = true
end
for k, v in pairs(args) do -- @todo change to uargs once we have that
if v then
if string.sub(k, -4) == '_deg' then
k = string.sub(k, 1, -5)
end
if string.sub(k, 1, 3) == 'lat' then
k = tonumber(string.sub(k, 4))
if k then
table.insert(marks, k)
end
end
end
end
table.sort(marks)
if marks[1] ~= 1 and (args.lat or args.lat_deg) then
table.insert(marks, 1, 1)
end
local body = ''
for _, v in ipairs(marks) do
-- don't try to consolidate this into the above loop. ordering of elements from pairs() is unspecified
body = body .. tostring( p.mark(frame, manyMakeArgs(args, v), map) )
if args['mark' .. v .. 'high'] then
mw.log('Removed parameter mark' .. v .. 'high used.')
local parent = frame:getParent()
if parent then
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
markhigh = true
end
end
args.label = nil -- there is no global label
return p.top(frame, args, map) .. body .. p.bottom(frame, args, map) .. (markhigh and '[[Category:Location maps with possible errors|Page using removed parameter]]' or '')
end
return p