Module:Location map: Difference between revisions

Jump to navigation Jump to search
imported>Ktr101
(Undid revision 605196190 by Jackmcbarn (talk) fix for now, as it is causing all of the coordinates to go flush to the left)
m (Protected "Module:Location map": Important page ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite)))
 
(75 intermediate revisions by 17 users not shown)
Line 1: Line 1:
require('strict')
local p = {}
local p = {}


local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs


local function round(n, digits)
local function round(n, decimals)
local mult = math.pow(10, digits)
local pow = 10^(decimals or 0)
return math.floor(n * mult + 0.5) / mult
return math.floor(n * pow + 0.5) / pow
end
end


local function getMapParams(map, frame)
function p.getMapParams(map, frame)
if not map then
if not map then
error('The name of the location map definition to use must be specified', 2)
error('The name of the location map definition to use must be specified', 2)
Line 14: Line 16:
local moduletitle = mw.title.new('Module:Location map/data/' .. map)
local moduletitle = mw.title.new('Module:Location map/data/' .. map)
if not moduletitle then
if not moduletitle then
error('"' .. map .. '" is not a valid name for a location map definition', 2)
error(string.format('%q is not a valid name for a location map definition', map), 2)
elseif moduletitle.exists then
elseif moduletitle.exists then
local mapData = mw.loadData('Module:Location map/data/' .. map)
local mapData = mw.loadData('Module:Location map/data/' .. map)
return function(name, params)
return function(name, params)
if mapData[name] == nil then
if name == nil then
return 'Module:Location map/data/' .. map
elseif mapData[name] == nil then
return ''
return ''
elseif params and params[1] then
elseif params then
return mw.message.newRawMessage(tostring(mapData[name]), unpack(params)):plain()
return mw.message.newRawMessage(tostring(mapData[name]), unpack(params)):plain()
else
else
return mapData[name]
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
end
end
else
else
error('Unable to find the specified location map definition. Neither "Module:Location map/data/' .. map .. '" nor "Template:Location map ' .. map .. '" exists', 2)
error('Unable to find the specified location map definition: "Module:Location map/data/' .. map .. '" does not exist', 2)
end
end
end
end
Line 45: Line 37:
function p.data(frame, args, map)
function p.data(frame, args, map)
if not args then
if not args then
args = getArgs(frame)
args = getArgs(frame, {frameOnly = true})
end
end
if not map then
if not map then
map = getMapParams(args[1], frame)
map = p.getMapParams(args[1], frame)
end
end
local params = {}
local params = {}
Line 56: Line 48:
end
end
end
end
return map(args[2], params)
return map(args[2], #params ~= 0 and params)
end
end


Line 64: Line 56:
}
}


local function decdeg(degrees, minutes, seconds, hemisphere, digits, decimal, direction)
local function decdeg(degrees, minutes, seconds, hemisphere, decimal, direction)
if not degrees then
if decimal then
if not decimal then
if degrees then
error('No value was provided for ' .. direction, 2)
error('Decimal and DMS degrees cannot both be provided for ' .. direction, 2)
elseif minutes then
error('Minutes can only be provided with DMS degrees for ' .. direction, 2)
elseif seconds then
error('Seconds can only be provided with DMS degrees for ' .. direction, 2)
elseif hemisphere then
error('A hemisphere can only be provided with DMS degrees for ' .. direction, 2)
end
end
local retval = tonumber(decimal)
local retval = tonumber(decimal)
Line 74: Line 72:
end
end
error('The value "' .. decimal .. '" provided for ' .. direction .. ' is not valid', 2)
error('The value "' .. decimal .. '" provided for ' .. direction .. ' is not valid', 2)
elseif seconds and not minutes then
error('Seconds were provided for ' .. direction .. ' without minutes also being provided', 2)
elseif not degrees then
if minutes then
error('Minutes were provided for ' .. direction .. ' without degrees also being provided', 2)
elseif hemisphere then
error('A hemisphere was provided for ' .. direction .. ' without degrees also being provided', 2)
end
return nil
end
end
decimal = tonumber(degrees)
decimal = tonumber(degrees)
if not decimal then
if not decimal then
error('The degree value "' .. degrees .. '" provided for ' .. direction .. ' is not valid', 2)
error('The degree value "' .. degrees .. '" provided for ' .. direction .. ' is not valid', 2)
end
elseif minutes and not tonumber(minutes) then
if minutes and not tonumber(minutes) then
error('The minute value "' .. minutes .. '" provided for ' .. direction .. ' is not valid', 2)
error('The minute value "' .. minutes .. '" provided for ' .. direction .. ' is not valid', 2)
end
elseif seconds and not tonumber(seconds) then
if seconds and not tonumber(seconds) then
error('The second value "' .. seconds .. '" provided for ' .. direction .. ' is not valid', 2)
error('The second value "' .. seconds .. '" provided for ' .. direction .. ' is not valid', 2)
end
end
Line 93: Line 98:
decimal = decimal * multiplier
decimal = decimal * multiplier
end
end
if not digits then
return decimal
digits = 7
end
elseif not tonumber(digits) then
 
error('The value "' .. digits .. '" provided for number of digits in ' .. direction .. ' is not valid', 2)
-- Finds a parameter in a transclusion of {{Coord}}.
local function coord2text(para,coord) -- this should be changed for languages which do not use Arabic numerals or the degree sign
local lat, long = mw.ustring.match(coord,'<span class="p%-latitude latitude">([^<]+)</span><span class="p%-longitude longitude">([^<]+)</span>')
if lat then
return tonumber(para == 'longitude' and long or lat)
end
local result = mw.text.split(mw.ustring.match(coord,'%-?[%.%d]+°[NS] %-?[%.%d]+°[EW]') or '', '[ °]')
if para == 'longitude' then result = {result[3], result[4]} end
if not tonumber(result[1]) or not result[2] then
mw.log('Malformed coordinates value')
mw.logObject(para, 'para')
mw.logObject(coord, 'coord')
return error('Malformed coordinates value', 2)
end
return tonumber(result[1]) * hemisphereMultipliers[para][result[2]]
end
 
-- effectively make removeBlanks false for caption and maplink, and true for everything else
-- if useWikidata is present but blank, convert it to false instead of nil
-- p.top, p.bottom, and their callers need to use this
function p.valueFunc(key, value)
if value then
value = mw.text.trim(value)
end
if value ~= '' or key == 'caption' or key == 'maplink' then
return value
elseif key == 'useWikidata' then
return false
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
return round(decimal, digits)
end
end


function p.top(frame, args, map)
function p.top(frame, args, map)
if not args then
if not args then
args = getArgs(frame)
args = getArgs(frame, {frameOnly = true, valueFunc = p.valueFunc})
end
end
if not map then
if not map then
map = getMapParams(args[1], frame)
map = p.getMapParams(args[1], frame)
end
end
local width
local width
if args.width then
local default_as_number = tonumber(mw.ustring.match(tostring(args.default_width),"%d*"))
width = mw.ustring.gsub(args.width, 'px', '')
if not args.width then
width = round((default_as_number or 240) * (tonumber(map('defaultscale')) or 1))
elseif mw.ustring.sub(args.width, -2) == 'px' then
width = mw.ustring.sub(args.width, 1, -3)
else
else
width = round((args.default_width or 240) * (tonumber(map('defaultscale')) or 1), 0)
width = args.width
end
end
local retval = ''
local width_as_number = tonumber(mw.ustring.match(tostring(width),"%d*")) or 0;
    if width_as_number == 0 then
    -- check to see if width is junk. If it is, then use default calculation
    width = round((default_as_number or 240) * (tonumber(map('defaultscale')) or 1))
    width_as_number = tonumber(mw.ustring.match(tostring(width),"%d*")) or 0;
    end
    if args.max_width ~= "" and args.max_width ~= nil then
        -- check to see if width bigger than max_width
        local max_as_number = tonumber(mw.ustring.match(args.max_width,"%d*")) or 0;
        if width_as_number>max_as_number and max_as_number>0 then
            width = args.max_width;
        end
    end
local retval = frame:extensionTag{name = 'templatestyles', args = {src = 'Module:Location map/styles.css'}}
if args.float == 'center' then
if args.float == 'center' then
retval = retval .. '<div class="center">'
retval = retval .. '<div class="center">'
end
end
if args.caption then
if args.caption and args.caption ~= '' and args.border ~= 'infobox' then
retval = retval .. '<div class="thumb '
retval = retval .. '<div class="locmap noviewer thumb '
if args.float == '"left"' or args.float == 'left' then
if args.float == '"left"' or args.float == 'left' then
retval = retval .. 'tleft'
retval = retval .. 'tleft'
Line 127: Line 184:
retval = retval .. 'tright'
retval = retval .. 'tright'
end
end
retval = retval .. '"><div class="thumbinner" style="width:' .. (width + 2) .. 'px;'
retval = retval .. '"><div class="thumbinner" style="width:' .. (width + 2) .. 'px'
if args.border == 'none' then
if args.border == 'none' then
retval = retval .. 'border:none;'
retval = retval .. ';border:none'
elseif args.border then
elseif args.border then
retval = retval .. 'border-color:' .. args.border .. ';'
retval = retval .. ';border-color:' .. args.border
end
end
retval = retval .. '"><div style="position:relative;width:' .. width .. 'px'
retval = retval .. '"><div style="position:relative;width:' .. width .. 'px' .. (args.border ~= 'none' and ';border:1px solid lightgray">' or '">')
if args.border ~= 'none' then
retval = retval .. ';border:1px solid lightgray'
end
retval = retval .. '">'
else
else
retval = retval .. '<div style="width:' .. width .. 'px;'
retval = retval .. '<div class="locmap" style="width:' .. width .. 'px;'
if args.float == '"left"' or args.float == 'left' then
if args.float == '"left"' or args.float == 'left' then
retval = retval .. 'float:left;clear:left'
retval = retval .. 'float:left;clear:left'
Line 151: Line 204:
retval = retval .. '"><div style="width:' .. width .. 'px;padding:0"><div style="position:relative;width:' .. width .. 'px">'
retval = retval .. '"><div style="width:' .. width .. 'px;padding:0"><div style="position:relative;width:' .. width .. 'px">'
end
end
local image
local image = getContainerImage(args, map)
if args.AlternativeMap then
local currentTitle = mw.title.getCurrentTitle()
image = args.AlternativeMap
retval = string.format(
elseif args.relief and map('image1') ~= '' then
'%s[[File:%s|%spx|%s%s|class=notpageimage]]',
image = map('image1')
retval,
else
image,
image = map('image')
width,
args.alt or ((args.label or currentTitle.text) .. ' is located in ' .. map('name')),
args.maplink and ('|link=' .. args.maplink) or ''
)
if args.caption and args.caption ~= '' then
if (currentTitle.namespace == 0) and mw.ustring.find(args.caption, '##') then
retval = retval .. '[[Category:Pages using location map with a double number sign in the caption]]'
end
end
end
retval = retval .. '[[File:' .. image .. '|' .. width .. 'px|' .. (args.alt or ((args.label or mw.title.getCurrentTitle().text) .. ' is located in ' .. map('name'))) .. ']]'
if args.overlay_image then
if args.overlay_image then
retval = retval .. '<div style="position:absolute; top: 0; left: 0">[[File:' .. args.overlay_image .. '|' .. width .. 'px|link=File:' .. image .. ']]</div>'
return retval .. '<div style="position:absolute;top:0;left:0">[[File:' .. args.overlay_image .. '|' .. width .. 'px|class=notpageimage]]</div>'
else
return retval
end
end
return retval
end
end


function p.bottom(frame, args, map)
function p.bottom(frame, args, map)
if not args then
if not args then
args = getArgs(frame)
args = getArgs(frame, {frameOnly = true, valueFunc = p.valueFunc})
end
end
if not map then
if not map then
map = getMapParams(args[1], frame)
map = p.getMapParams(args[1], frame)
end
local retval = '</div>'
local currentTitle = mw.title.getCurrentTitle()
if not args.caption or args.border == 'infobox' then
if args.border then
retval = retval .. '<div style="padding-top:0.2em">'
else
retval = retval .. '<div style="font-size:91%;padding-top:3px">'
end
retval = retval
.. (args.caption or (args.label or currentTitle.text) .. ' (' .. map('name') .. ')')
.. '</div>'
elseif args.caption ~= ''  then
-- This is not the pipe trick. We're creating a link with no text on purpose, so that CSS can give us a nice image
retval = retval .. '<div class="thumbcaption"><div class="magnify">[[:File:' .. getContainerImage(args, map) .. '|class=notpageimage| ]]</div>' .. args.caption .. '</div>'
end
 
if args.switcherLabel then
retval = retval .. '<span class="switcher-label" style="display:none">' .. args.switcherLabel .. '</span>'
elseif args.autoSwitcherLabel then
retval = retval .. '<span class="switcher-label" style="display:none">Show map of ' .. map('name') .. '</span>'
end
end
local retval = ''
retval = retval .. '</div><div ' .. (args.caption and 'class="thumbcaption"' or 'style="font-size:90%;padding-top:3px"') .. '>'
retval = retval .. '</div></div>'
local caption = frame.args.caption or frame:getParent().args.caption
if args.caption_undefined then
if caption and not args.caption_undefined then
mw.log('Removed parameter caption_undefined used.')
retval = retval .. mw.text.trim(caption)
local parent = frame:getParent()
else
if parent then
retval = retval .. (args.label or mw.title.getCurrentTitle().text) .. ' (' .. map('name') .. ')'
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
if currentTitle.namespace == 0 then
    retval = retval .. '[[Category:Location maps with removed parameters|caption_undefined]]'
end
end
if map('skew') ~= '' or map('lat_skew') ~= '' or map('crosses180') ~= '' or map('type') ~= '' then
mw.log('Removed parameter used in map definition ' .. map())
if currentTitle.namespace == 0 then
    local key = (map('skew') ~= '' and 'skew' or '') ..
(map('lat_skew') ~= '' and 'lat_skew' or '') ..
(map('crosses180') ~= '' and 'crosses180' or '') ..
(map('type') ~= '' and 'type' or '')
    retval = retval .. '[[Category:Location maps with removed parameters|' .. key .. ' ]]'
end
end
if string.find(map('name'), '|', 1, true) then
mw.log('Pipe used in name of map definition ' .. map())
if currentTitle.namespace == 0 then
  retval = retval .. '[[Category:Location maps with a name containing a pipe]]'
end
end
end
retval = retval .. '</div></div></div>'
if args.float == 'center' then
if args.float == 'center' then
retval = retval .. '</div>'
retval = retval .. '</div>'
Line 188: Line 289:
end
end


function p.container(frame, args, map)
local function markOuterDiv(x, y, imageDiv, labelDiv)
if not args then
return mw.html.create('div')
args = getArgs(frame)
:addClass('od')
end
:cssText('top:' .. round(y, 3) .. '%;left:' .. round(x, 3) .. '%')
if not map then
:node(imageDiv)
map = getMapParams(args[1], frame)
:node(labelDiv)
end
return p.top(frame, args, map) .. (args.places or '') .. p.bottom(frame, args, map)
end
 
local function markOuterDiv(x, y, content)
return '<div style="position:absolute;top:' .. y .. '%;left:' .. x .. '%;height:0;width:0;margin:0;padding:0">' .. content .. '</div>'
end
end


local function markImageDiv(mark, marksize, label, link, alt, title)
local function markImageDiv(mark, marksize, label, link, alt, title)
local retval = '<div style="position:relative;text-align:center;left:-' .. round(marksize / 2, 0) .. 'px;top:-' .. round(marksize / 2, 0) .. 'px;width:' .. marksize .. 'px;font-size:' .. marksize .. 'px;line-height:0"'
local builder = mw.html.create('div')
if title then
:addClass('id')
retval = retval .. ' title="' .. title .. '"'
:cssText('left:-' .. round(marksize / 2) .. 'px;top:-' .. round(marksize / 2) .. 'px')
end
:attr('title', title)
retval = retval .. '>'
if marksize ~= 0 then
if marksize ~= 0 then
retval = retval .. '[[File:' .. mark .. '|' .. marksize .. 'x' .. marksize .. 'px|' .. label .. '|link=' .. link
builder:wikitext(string.format(
if alt then
'[[File:%s|%dx%dpx|%s|link=%s%s|class=notpageimage]]',
retval = retval .. '|alt=' .. alt
mark,
end
marksize,
retval = retval .. ']]'
marksize,
label,
link,
alt and ('|alt=' .. alt) or ''
))
end
end
return retval .. '</div>'
return builder
end
end


local function markLabelDiv(label, label_size, label_width, position, background, x)
local function markLabelDiv(label, label_size, label_width, position, background, x, marksize)
local retval = '<div style="font-size:' .. label_size .. '%;line-height:110%;position:relative;top:-1.5em;width:' .. label_width .. 'em;'
if tonumber(label_size) == 0 then
return mw.html.create('div'):addClass('l0'):wikitext(label)
end
local builder = mw.html.create('div')
:cssText('font-size:' .. label_size .. '%;width:' .. label_width .. 'em')
local distance = round(marksize / 2 + 1)
if position == 'top' then -- specified top
if position == 'top' then -- specified top
retval = retval .. 'top:-2.65em;left:-3em;text-align:center'
builder:addClass('pv'):cssText('bottom:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em')
elseif position == 'bottom' then -- specified bottom
elseif position == 'bottom' then -- specified bottom
retval = retval .. 'top:-0.15em;left:-3em;text-align:center'
builder:addClass('pv'):cssText('top:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em')
elseif position == 'left' or (tonumber(x) > 70 and position ~= 'right') then -- specified left or autodetected to left
elseif position == 'left' or (tonumber(x) > 70 and position ~= 'right') then -- specified left or autodetected to left
retval = retval .. 'left:-6.5em;text-align:right'
builder:addClass('pl'):cssText('right:' .. distance .. 'px')
else -- specified right or autodetected to right
else -- specified right or autodetected to right
retval = retval .. 'left:0.5em;text-align:left'
builder:addClass('pr'):cssText('left:' .. distance .. 'px')
end
end
retval = retval .. '"><span style="padding:1px'
builder = builder:tag('div')
:wikitext(label)
if background then
if background then
retval = retval .. ';background-color:' .. background
builder:cssText('background-color:' .. background)
end
end
return retval .. '">' .. label .. '</span></div>'
return builder:done()
end
end


local function getX(longitude, latitude, lon_dir, left, right, top, bottom, crosses180, skew, lon_shift)
local function getX(longitude, left, right)
local crosses180_correction = crosses180 and lon_dir == 'W' and (-36000/(left - right)) or 0
local width = (right - left) % 360
if skew then
if width == 0 then
local lat_ratio = (top - latitude)/(top - bottom)
width = 360
local skew_factor = (1 - skew) * lat_ratio + skew
end
longitude = (longitude - 0.5 * (right + left) + lon_shift) * skew_factor + 0.5 * (right + left) - lon_shift
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
end
return round(crosses180_correction + 100 * (longitude - left) / (right - left), 1)
return 100 * distanceFromLeft / width
end
end


local function getY(latitude, top, bottom)
local function getY(latitude, top, bottom)
return round(100 * (top - latitude) / (top - bottom), 1)
return 100 * (top - latitude) / (top - bottom)
end
end


function p.mark(frame, args, map)
function p.mark(frame, args, map)
if not args then
if not args then
args = getArgs(frame)
args = getArgs(frame, {wrappers = 'Template:Location map~'})
end
end
local mapnames = {}
if not map then
if not map then
map = getMapParams(args[1], frame)
if args[1] then
map = {}
for mapname in mw.text.gsplit(args[1], '#', true) do
map[#map + 1] = p.getMapParams(mw.ustring.gsub(mapname, '^%s*(.-)%s*$', '%1'), frame)
mapnames[#mapnames + 1] = mapname
end
if #map == 1 then map = map[1] end
else
map = p.getMapParams('World', frame)
args[1] = 'World'
end
end
if type(map) == 'table' then
local outputs = {}
local oldargs = args[1]
for k,v in ipairs(map) do
args[1] = mapnames[k]
outputs[k] = tostring(p.mark(frame, args, v))
end
args[1] = oldargs
return table.concat(outputs, '#PlaceList#') .. '#PlaceList#'
end
end
local x, y, longitude, latitude
local x, y, longitude, latitude
longitude = decdeg(args.lon_deg, args.lon_min, args.lon_sec, args.lon_dir, nil, args.long, 'longitude')
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, nil, args.lat, 'latitude')
latitude = decdeg(args.lat_deg, args.lat_min, args.lat_sec, args.lat_dir, args.lat, 'latitude')
local retval = ''
if args.excludefrom then
if args.skew or args.lon_shift or (map('skew') ~= '') or (map('lat_skew') ~= '') then
-- If this mark is to be excluded from certain maps entirely (useful in the context of multiple maps)
retval = retval .. '[[Category:Location maps using skew|' .. (tostring(args[1]) or '') .. ']]'
for exclusionmap in mw.text.gsplit(args.excludefrom, '#', true) do
-- Check if this map is excluded. If so, return an empty string.
if args[1] == exclusionmap then
return ''
end
end
end
local builder = mw.html.create()
local currentTitle = mw.title.getCurrentTitle()
if args.coordinates then
-- Temporarily removed to facilitate infobox conversion. See [[Wikipedia:Coordinates in infoboxes]]
 
-- if longitude or latitude then
-- error('Coordinates from [[Module:Coordinates]] and individual coordinates cannot both be provided')
-- end
longitude = coord2text('longitude', args.coordinates)
latitude = coord2text('latitude', args.coordinates)
elseif not longitude and not latitude and args.useWikidata then
-- If they didn't provide either coordinate, try Wikidata. If they provided one but not the other, don't.
local entity = mw.wikibase.getEntity()
if entity and entity.claims and entity.claims.P625 and entity.claims.P625[1].mainsnak.snaktype == 'value' then
local value = entity.claims.P625[1].mainsnak.datavalue.value
longitude, latitude = value.longitude, value.latitude
end
if args.link and (currentTitle.namespace == 0) then
builder:wikitext('[[Category:Location maps with linked markers with coordinates from Wikidata]]')
end
end
if not longitude then
error('No value was provided for longitude')
elseif not latitude then
error('No value was provided for latitude')
end
if currentTitle.namespace > 0 then
if (not args.lon_deg) ~= (not args.lat_deg) then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Degrees]]')
elseif (not args.lon_min) ~= (not args.lat_min) then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Minutes]]')
elseif (not args.lon_sec) ~= (not args.lat_sec) then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Seconds]]')
elseif (not args.lon_dir) ~= (not args.lat_dir) then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Hemisphere]]')
elseif (not args.long) ~= (not args.lat) then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Decimal]]')
end
end
if ((tonumber(args.lat_deg) or 0) < 0) and ((tonumber(args.lat_min) or 0) ~= 0 or (tonumber(args.lat_sec) or 0) ~= 0 or (args.lat_dir and args.lat_dir ~='')) then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
if ((tonumber(args.lon_deg) or 0) < 0) and ((tonumber(args.lon_min) or 0) ~= 0 or (tonumber(args.lon_sec) or 0) ~= 0 or (args.lon_dir and args.lon_dir ~= '')) then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
if (((tonumber(args.lat_min) or 0) < 0) or ((tonumber(args.lat_sec) or 0) < 0)) then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
if (((tonumber(args.lon_min) or 0) < 0) or ((tonumber(args.lon_sec) or 0) < 0)) then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
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')
if currentTitle.namespace == 0 then
local key = (args.skew and 'skew' or '') ..
(args.lon_shift and 'lon_shift' or '') ..
(args.markhigh and 'markhigh' or '')
builder:wikitext('[[Category:Location maps with removed parameters|' .. key ..' ]]')
end
end
end
if map('x') ~= '' then
if map('x') ~= '' then
x = frame:callParserFunction('#expr', map('x', { latitude, longitude }))
x = tonumber(mw.ext.ParserFunctions.expr(map('x', { latitude, longitude })))
else
else
x = getX(longitude, latitude, args.lon_dir, map('left'), map('right'), map('top'), map('bottom'), map('crosses180') ~= '', tonumber(args.skew or map('skew')), args.lon_shift or 0)
x = tonumber(getX(longitude, map('left'), map('right')))
end
end
if map('y') ~= '' then
if map('y') ~= '' then
y = frame:callParserFunction('#expr', map('y', { latitude, longitude }))
y = tonumber(mw.ext.ParserFunctions.expr(map('y', { latitude, longitude })))
else
else
y = getY(latitude, map('top'), map('bottom'))
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')
if currentTitle.namespace == 0 then
local key = currentTitle.prefixedText
builder:wikitext('[[Category:Location maps with marks outside map and outside parameter not set|' .. key .. ' ]]')
end
end
end
local mark = args.mark or map('mark')
local mark = args.mark or map('mark')
Line 278: Line 489:
mark = 'Red pog.svg'
mark = 'Red pog.svg'
end
end
local divContent = markImageDiv(mark, tonumber(args.marksize) or tonumber(map('marksize')) or 8, args.label or mw.title.getCurrentTitle().text, args.link or '', args.alt, args[2])
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
if args.label and args.position ~= 'none' then
divContent = divContent .. markLabelDiv(args.label, args.label_size or 90, args.label_width or 6, args.position, args.background, x)
labelDiv = markLabelDiv(args.label, args.label_size or 91, args.label_width or 6, args.position, args.background, x, marksize)
end
end
return retval .. markOuterDiv(x, y, divContent)
return builder:node(markOuterDiv(x, y, imageDiv, labelDiv))
end
end


function p.main(frame, args, map)
local function switcherSeparate(s)
if not args then
if s == nil then return {} end
args = getArgs(frame)
local retval = {}
for i in string.gmatch(s .. '#', '([^#]*)#') do
i = mw.text.trim(i)
retval[#retval + 1] = (i ~= '' and i)
end
end
if not args[1] then
return retval
args[1] = 'World'
end
if not map then
map = getMapParams(args[1], frame)
end
return p.top(frame, args, map) .. p.mark(frame, args, map) .. p.bottom(frame, args, map)
end
end


local function manyMakeArgs(fullArgs, n)
function p.main(frame, args, map)
if n == 1 then
local caption_list = {}
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'],
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
if not args then
args = getArgs(frame)
args = getArgs(frame, {wrappers = 'Template:Location map', valueFunc = p.valueFunc})
end
end
if not args[1] then
if args.useWikidata == nil then
args[1] = 'World'
args.useWikidata = true
end
end
if not map then
if not map then
map = getMapParams(args[1], frame)
if args[1] then
end
map = {}
local marks = {}
for mapname in string.gmatch(args[1], '[^#]+') do
local markhigh = args.markhigh
map[#map + 1] = p.getMapParams(mw.ustring.gsub(mapname, '^%s*(.-)%s*$', '%1'), frame)
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
end
if string.sub(k, 1, 3) == 'lat' then
if args['caption'] then
k = tonumber(string.sub(k, 4))
if args['caption'] == "" then
if k then
while #caption_list < #map do
table.insert(marks, k)
caption_list[#caption_list + 1] = args['caption']
end
else
for caption in mw.text.gsplit(args['caption'], '##', true) do
caption_list[#caption_list + 1] = caption
end
end
end
end
end
if #map == 1 then map = map[1] end
else
map = p.getMapParams('World', frame)
end
end
end
end
table.sort(marks)
if type(map) == 'table' then
if marks[1] ~= 1 and (args.lat or args.lat_deg) then
local altmaps = switcherSeparate(args.AlternativeMap)
table.insert(marks, 1, 1)
if #altmaps > #map then
end
error(string.format('%d AlternativeMaps were provided, but only %d maps were provided', #altmaps, #map))
local body = ''
end
for _, v in ipairs(marks) do
local overlays = switcherSeparate(args.overlay_image)
-- don't try to consolidate this into the above loop. ordering of elements from pairs() is unspecified
if #overlays > #map then
body = body .. p.mark(frame, manyMakeArgs(args, v), map)
error(string.format('%d overlay_images were provided, but only %d maps were provided', #overlays, #map))
if args['mark' .. v .. 'high'] then
end
markhigh = true
if #caption_list > #map then
error(string.format('%d captions were provided, but only %d maps were provided', #caption_list, #map))
end
local outputs = {}
args.autoSwitcherLabel = true
for k,v in ipairs(map) do
args.AlternativeMap = altmaps[k]
args.overlay_image = overlays[k]
args.caption = caption_list[k]
outputs[k] = p.main(frame, args, v)
end
end
return '<div class="switcher-container">' .. table.concat(outputs) .. '</div>'
else
return p.top(frame, args, map) .. tostring( p.mark(frame, args, map) ) .. p.bottom(frame, args, map)
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 map many using markhigh parameter]]' or '')
end
end


return p
return p

Latest revision as of 03:32, 15 September 2023

Documentation for this module may be created at Module:Location map/doc

require('strict')

local p = {}

local getArgs = require('Module:Arguments').getArgs

local function round(n, decimals)
	local pow = 10^(decimals or 0)
	return math.floor(n * pow + 0.5) / pow
end

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(string.format('%q is not a valid name for a location map definition', map), 2)
	elseif moduletitle.exists then
		local mapData = mw.loadData('Module:Location map/data/' .. map)
		return function(name, params)
			if name == nil then
				return 'Module:Location map/data/' .. map
			elseif mapData[name] == nil then
				return ''
			elseif params then
				return mw.message.newRawMessage(tostring(mapData[name]), unpack(params)):plain()
			else
				return mapData[name]
			end
		end
	else
		error('Unable to find the specified location map definition: "Module:Location map/data/' .. map .. '" does not exist', 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 decimal then
		if degrees then
			error('Decimal and DMS degrees cannot both be provided for ' .. direction, 2)
		elseif minutes then
			error('Minutes can only be provided with DMS degrees for ' .. direction, 2)
		elseif seconds then
			error('Seconds can only be provided with DMS degrees for ' .. direction, 2)
		elseif hemisphere then
			error('A hemisphere can only be provided with DMS degrees 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)
	elseif seconds and not minutes then
		error('Seconds were provided for ' .. direction .. ' without minutes also being provided', 2)
	elseif not degrees then
		if minutes then
			error('Minutes were provided for ' .. direction .. ' without degrees also being provided', 2)
		elseif hemisphere then
			error('A hemisphere was provided for ' .. direction .. ' without degrees also being provided', 2)
		end
		return nil
	end
	decimal = tonumber(degrees)
	if not decimal then
		error('The degree value "' .. degrees .. '" provided for ' .. direction .. ' is not valid', 2)
	elseif minutes and not tonumber(minutes) then
		error('The minute value "' .. minutes .. '" provided for ' .. direction .. ' is not valid', 2)
	elseif 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

-- Finds a parameter in a transclusion of {{Coord}}.
local function coord2text(para,coord) -- this should be changed for languages which do not use Arabic numerals or the degree sign
	local lat, long = mw.ustring.match(coord,'<span class="p%-latitude latitude">([^<]+)</span><span class="p%-longitude longitude">([^<]+)</span>')
	if lat then
		return tonumber(para == 'longitude' and long or lat)
	end
	local result = mw.text.split(mw.ustring.match(coord,'%-?[%.%d]+°[NS] %-?[%.%d]+°[EW]') or '', '[ °]')
	if para == 'longitude' then result = {result[3], result[4]} end
	if not tonumber(result[1]) or not result[2] then
		mw.log('Malformed coordinates value')
		mw.logObject(para, 'para')
		mw.logObject(coord, 'coord')
		return error('Malformed coordinates value', 2)
	end
	return tonumber(result[1]) * hemisphereMultipliers[para][result[2]]
end

-- effectively make removeBlanks false for caption and maplink, and true for everything else
-- if useWikidata is present but blank, convert it to false instead of nil
-- p.top, p.bottom, and their callers need to use this
function p.valueFunc(key, value)
	if value then
		value = mw.text.trim(value)
	end
	if value ~= '' or key == 'caption' or key == 'maplink' then
		return value
	elseif key == 'useWikidata' then
		return false
	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 = p.valueFunc})
	end
	if not map then
		map = p.getMapParams(args[1], frame)
	end
	local width
	local default_as_number = tonumber(mw.ustring.match(tostring(args.default_width),"%d*"))
	if not args.width then
		width = round((default_as_number or 240) * (tonumber(map('defaultscale')) or 1))
	elseif mw.ustring.sub(args.width, -2) == 'px' then
		width = mw.ustring.sub(args.width, 1, -3)
	else
		width = args.width
	end
	local width_as_number = tonumber(mw.ustring.match(tostring(width),"%d*")) or 0;
    if width_as_number == 0 then
    	-- check to see if width is junk. If it is, then use default calculation
    	width = round((default_as_number or 240) * (tonumber(map('defaultscale')) or 1))
    	width_as_number = tonumber(mw.ustring.match(tostring(width),"%d*")) or 0;
    end	
    if args.max_width ~= "" and args.max_width ~= nil then
        -- check to see if width bigger than max_width
        local max_as_number = tonumber(mw.ustring.match(args.max_width,"%d*")) or 0;
        if width_as_number>max_as_number and max_as_number>0 then
            width = args.max_width;
        end
    end
	local retval = frame:extensionTag{name = 'templatestyles', args = {src = 'Module:Location map/styles.css'}}
	if args.float == 'center' then
		retval = retval .. '<div class="center">'
	end
	if args.caption and args.caption ~= '' and args.border ~= 'infobox' then
		retval = retval .. '<div class="locmap 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 class="locmap" 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)
	local currentTitle = mw.title.getCurrentTitle()
	retval = string.format(
		'%s[[File:%s|%spx|%s%s|class=notpageimage]]',
		retval,
		image,
		width,
		args.alt or ((args.label or currentTitle.text) .. ' is located in ' .. map('name')),
		args.maplink and ('|link=' .. args.maplink) or ''
	)
	if args.caption and args.caption ~= '' then
		if (currentTitle.namespace == 0) and mw.ustring.find(args.caption, '##') then
			retval = retval .. '[[Category:Pages using location map with a double number sign in the caption]]'
		end
	end
	if args.overlay_image then
		return retval .. '<div style="position:absolute;top:0;left:0">[[File:' .. args.overlay_image .. '|' .. width .. 'px|class=notpageimage]]</div>'
	else
		return retval
	end
end

function p.bottom(frame, args, map)
	if not args then
		args = getArgs(frame, {frameOnly = true, valueFunc = p.valueFunc})
	end
	if not map then
		map = p.getMapParams(args[1], frame)
	end
	local retval = '</div>'
	local currentTitle = mw.title.getCurrentTitle()
	if not args.caption or args.border == 'infobox' then
		if args.border then
			retval = retval .. '<div style="padding-top:0.2em">'
		else
			retval = retval .. '<div style="font-size:91%;padding-top:3px">'
		end
		retval = retval
		.. (args.caption or (args.label or currentTitle.text) .. ' (' .. map('name') .. ')')
		.. '</div>'
	elseif args.caption ~= ''  then
		-- This is not the pipe trick. We're creating a link with no text on purpose, so that CSS can give us a nice image
		retval = retval .. '<div class="thumbcaption"><div class="magnify">[[:File:' .. getContainerImage(args, map) .. '|class=notpageimage| ]]</div>' .. args.caption .. '</div>'
	end

	if args.switcherLabel then
		retval = retval .. '<span class="switcher-label" style="display:none">' .. args.switcherLabel .. '</span>'
	elseif args.autoSwitcherLabel then
		retval = retval .. '<span class="switcher-label" style="display:none">Show map of ' .. map('name') .. '</span>'
	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')
		if currentTitle.namespace == 0 then
		    retval = retval .. '[[Category:Location maps with removed parameters|caption_undefined]]'
		end
	end
	if map('skew') ~= '' or map('lat_skew') ~= '' or map('crosses180') ~= '' or map('type') ~= '' then
		mw.log('Removed parameter used in map definition ' .. map())
		if currentTitle.namespace == 0 then
		    local key = (map('skew') ~= '' and 'skew' or '') ..
					(map('lat_skew') ~= '' and 'lat_skew' or '') ..
					(map('crosses180') ~= '' and 'crosses180' or '') ..
					(map('type') ~= '' and 'type' or '')
		    retval = retval .. '[[Category:Location maps with removed parameters|' .. key .. ' ]]'
		end
	end
	if string.find(map('name'), '|', 1, true) then
		mw.log('Pipe used in name of map definition ' .. map())
		if currentTitle.namespace == 0 then
		   retval = retval .. '[[Category:Location maps with a name containing a pipe]]'
		end
	end
	if args.float == 'center' then
		retval = retval .. '</div>'
	end
	return retval
end

local function markOuterDiv(x, y, imageDiv, labelDiv)
	return mw.html.create('div')
		:addClass('od')
		:cssText('top:' .. round(y, 3) .. '%;left:' .. round(x, 3) .. '%')
		:node(imageDiv)
		:node(labelDiv)
end

local function markImageDiv(mark, marksize, label, link, alt, title)
	local builder = mw.html.create('div')
		:addClass('id')
		:cssText('left:-' .. round(marksize / 2) .. 'px;top:-' .. round(marksize / 2) .. 'px')
		:attr('title', title)
	if marksize ~= 0 then
		builder:wikitext(string.format(
			'[[File:%s|%dx%dpx|%s|link=%s%s|class=notpageimage]]',
			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)
	if tonumber(label_size) == 0 then
		return mw.html.create('div'):addClass('l0'):wikitext(label)
	end
	local builder = mw.html.create('div')
		:cssText('font-size:' .. label_size .. '%;width:' .. label_width .. 'em')
	local distance = round(marksize / 2 + 1)
	if position == 'top' then -- specified top
		builder:addClass('pv'):cssText('bottom:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em')
	elseif position == 'bottom' then -- specified bottom
		builder:addClass('pv'):cssText('top:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em')
	elseif position == 'left' or (tonumber(x) > 70 and position ~= 'right') then -- specified left or autodetected to left
		builder:addClass('pl'):cssText('right:' .. distance .. 'px')
	else -- specified right or autodetected to right
		builder:addClass('pr'):cssText('left:' .. distance .. 'px')
	end
	builder = builder:tag('div')
		: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
	local mapnames = {}
	if not map then
		if args[1] then
			map = {}
			for mapname in mw.text.gsplit(args[1], '#', true) do
				map[#map + 1] = p.getMapParams(mw.ustring.gsub(mapname, '^%s*(.-)%s*$', '%1'), frame)
				mapnames[#mapnames + 1] = mapname
			end
			if #map == 1 then map = map[1] end
		else
			map = p.getMapParams('World', frame)
			args[1] = 'World'
		end
	end
	if type(map) == 'table' then
		local outputs = {}
		local oldargs = args[1]
		for k,v in ipairs(map) do
			args[1] = mapnames[k]
			outputs[k] = tostring(p.mark(frame, args, v))
		end
		args[1] = oldargs
		return table.concat(outputs, '#PlaceList#') .. '#PlaceList#'
	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')
	if args.excludefrom then
		-- If this mark is to be excluded from certain maps entirely (useful in the context of multiple maps)
		for exclusionmap in mw.text.gsplit(args.excludefrom, '#', true) do
			-- Check if this map is excluded. If so, return an empty string.
			if args[1] == exclusionmap then
				return ''
			end
		end
			
	end
	local builder = mw.html.create()
	local currentTitle = mw.title.getCurrentTitle()
	if args.coordinates then
--		Temporarily removed to facilitate infobox conversion. See [[Wikipedia:Coordinates in infoboxes]]

--		if longitude or latitude then
--			error('Coordinates from [[Module:Coordinates]] and individual coordinates cannot both be provided')
--		end
		longitude = coord2text('longitude', args.coordinates)
		latitude = coord2text('latitude', args.coordinates)
	elseif not longitude and not latitude and args.useWikidata then
		-- If they didn't provide either coordinate, try Wikidata. If they provided one but not the other, don't.
		local entity = mw.wikibase.getEntity()
		if entity and entity.claims and entity.claims.P625 and entity.claims.P625[1].mainsnak.snaktype == 'value' then
			local value = entity.claims.P625[1].mainsnak.datavalue.value
			longitude, latitude = value.longitude, value.latitude
		end
		if args.link and (currentTitle.namespace == 0) then
			builder:wikitext('[[Category:Location maps with linked markers with coordinates from Wikidata]]')	
		end
	end
	if not longitude then
		error('No value was provided for longitude')
	elseif not latitude then
		error('No value was provided for latitude')
	end
	if currentTitle.namespace > 0 then
		if (not args.lon_deg) ~= (not args.lat_deg) then
			builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Degrees]]')
		elseif (not args.lon_min) ~= (not args.lat_min) then
			builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Minutes]]')
		elseif (not args.lon_sec) ~= (not args.lat_sec) then
			builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Seconds]]')
		elseif (not args.lon_dir) ~= (not args.lat_dir) then
			builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Hemisphere]]')
		elseif (not args.long) ~= (not args.lat) then
			builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Decimal]]')
		end
	end
	if ((tonumber(args.lat_deg) or 0) < 0) and ((tonumber(args.lat_min) or 0) ~= 0 or (tonumber(args.lat_sec) or 0) ~= 0 or (args.lat_dir and args.lat_dir ~='')) then
		builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
	end
	if ((tonumber(args.lon_deg) or 0) < 0) and ((tonumber(args.lon_min) or 0) ~= 0 or (tonumber(args.lon_sec) or 0) ~= 0 or (args.lon_dir and args.lon_dir ~= '')) then
		builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
	end
	if (((tonumber(args.lat_min) or 0) < 0) or ((tonumber(args.lat_sec) or 0) < 0)) then
		builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
	end
	if (((tonumber(args.lon_min) or 0) < 0) or ((tonumber(args.lon_sec) or 0) < 0)) then
		builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
	end
	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')
		if currentTitle.namespace == 0 then
			local key = (args.skew and 'skew' or '') ..
						(args.lon_shift and 'lon_shift' or '') ..
						(args.markhigh and 'markhigh' or '')
			builder:wikitext('[[Category:Location maps with removed parameters|' .. key ..' ]]')
		end
	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')
		if currentTitle.namespace == 0 then
			local key = currentTitle.prefixedText
			builder:wikitext('[[Category:Location maps with marks outside map and outside parameter not set|' .. key .. ' ]]')
		end
	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 91, args.label_width or 6, args.position, args.background, x, marksize)
	end
	return builder:node(markOuterDiv(x, y, imageDiv, labelDiv))
end

local function switcherSeparate(s)
	if s == nil then return {} end
	local retval = {}
	for i in string.gmatch(s .. '#', '([^#]*)#') do
		i = mw.text.trim(i)
		retval[#retval + 1] = (i ~= '' and i)
	end
	return retval
end

function p.main(frame, args, map)
	local caption_list = {}
	if not args then
		args = getArgs(frame, {wrappers = 'Template:Location map', valueFunc = p.valueFunc})
	end
	if args.useWikidata == nil then
		args.useWikidata = true
	end
	if not map then
		if args[1] then
			map = {}
			for mapname in string.gmatch(args[1], '[^#]+') do
				map[#map + 1] = p.getMapParams(mw.ustring.gsub(mapname, '^%s*(.-)%s*$', '%1'), frame)
			end
			if args['caption'] then
				if args['caption'] == "" then
					while #caption_list < #map do
						caption_list[#caption_list + 1] = args['caption']
					end
				else
					for caption in mw.text.gsplit(args['caption'], '##', true) do
						caption_list[#caption_list + 1] = caption
					end
				end
			end
			if #map == 1 then map = map[1] end
		else
			map = p.getMapParams('World', frame)
		end
	end
	if type(map) == 'table' then
		local altmaps = switcherSeparate(args.AlternativeMap)
		if #altmaps > #map then
			error(string.format('%d AlternativeMaps were provided, but only %d maps were provided', #altmaps, #map))
		end
		local overlays = switcherSeparate(args.overlay_image)
		if #overlays > #map then
			error(string.format('%d overlay_images were provided, but only %d maps were provided', #overlays, #map))
		end
		if #caption_list > #map then
			error(string.format('%d captions were provided, but only %d maps were provided', #caption_list, #map))
		end
		local outputs = {}
		args.autoSwitcherLabel = true
		for k,v in ipairs(map) do
			args.AlternativeMap = altmaps[k]
			args.overlay_image = overlays[k]
			args.caption = caption_list[k]
			outputs[k] = p.main(frame, args, v)
		end
		return '<div class="switcher-container">' .. table.concat(outputs) .. '</div>'
	else
		return p.top(frame, args, map) .. tostring( p.mark(frame, args, map) ) .. p.bottom(frame, args, map)
	end
end

return p