X

Track changes made to this page

If you find this page useful and would like to be notified of changes made to this page, start by inputting your email below.



Privacy policy
Close this window

powered by ChangeDetection

Difference between revisions of "Location map"

m (1 revision)
(handle marks placed to the left of the map more nicely)
Line 1: Line 1:
 +
require('Module:No globals')
 +
 
local p = {}
 
local p = {}
  
 
local getArgs = require('Module:Arguments').getArgs
 
local getArgs = require('Module:Arguments').getArgs
 
local function round(n, digits)
 
local mult = math.pow(10, digits)
 
return math.floor(n * mult + 0.5) / mult
 
end
 
  
 
local function getMapParams(map, frame)
 
local function getMapParams(map, frame)
if mw.title.new('Module:Location map/data/' .. map).exists then
+
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)
 
local mapData = mw.loadData('Module:Location map/data/' .. map)
 
return function(name, params)
 
return function(name, params)
Line 15: Line 18:
 
return ''
 
return ''
 
elseif params then
 
elseif params then
return mw.message.newRawMessage(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
 
end
 
end
else
+
elseif mw.title.new('Template:Location map ' .. map).exists then
 
local cache = {}
 
local cache = {}
 
return function(name, params)
 
return function(name, params)
Line 32: Line 35:
 
end
 
end
 
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
 
end
 
end
  
local function decdeg(degrees, minutes, seconds, hemisphere, digits, decimal)
+
function p.data(frame, args, map)
if degrees == nil then
+
if not args then
return tonumber(decimal)
+
args = getArgs(frame)
 
end
 
end
decimal = (degrees or 0) + (minutes or 0)/60 + (seconds or 0)/3600
+
if not map then
if hemisphere == 'W' or hemisphere == 'w' or hemisphere == 'S' or  hemisphere == 's' then
+
map = getMapParams(args[1], frame)
decimal = -decimal
 
 
end
 
end
if digits == nil then
+
local params = {}
digits = 7
+
for k,v in ipairs(args) do
 +
if k > 2 then
 +
params[k-2] = v
 +
end
 
end
 
end
return round(decimal, digits)
+
return map(args[2], #params 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
 
end
  
Line 57: Line 101:
 
end
 
end
 
local width
 
local width
if args.width then
+
if not args.width then
width = mw.ustring.gsub(args.width, 'px', '')
+
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
 
else
width = round((args.default_width or 240) * (tonumber(map('defaultscale')) or 1), 0)
+
width = args.width
end
 
local retval = ''
 
if args.float == 'center' then
 
retval = retval .. '<div class="center">'
 
 
end
 
end
 +
local retval = args.float == 'center' and '<div class="center">' or ''
 
if args.caption then
 
if args.caption then
 
retval = retval .. '<div class="thumb '
 
retval = retval .. '<div class="thumb '
Line 75: Line 118:
 
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; '
+
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 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'
 
elseif args.float == '"center"' or args.float == 'center' then
 
elseif args.float == '"center"' or args.float == 'center' then
retval = retval .. 'float: none; clear: both; margin-left: auto; margin-right: auto'
+
retval = retval .. 'float:none;clear:both;margin-left:auto;margin-right:auto'
 
elseif args.float == '"none"' or args.float == 'none' then
 
elseif args.float == '"none"' or args.float == 'none' then
retval = retval .. 'float: none; clear: none'
+
retval = retval .. 'float:none;clear:none'
 
else
 
else
retval = retval .. 'float: right; clear: right'
+
retval = retval .. 'float:right;clear:right'
 
end
 
end
retval = retval .. '"><div style="width:' .. width .. 'px; padding:0"><div style="position: relative; ">'
+
retval = retval .. '"><div style="width:' .. width .. 'px;padding:0"><div style="position:relative;width:' .. width .. 'px">'
 
end
 
end
 
local image
 
local image
Line 109: Line 148:
 
retval = retval .. '[[File:' .. image .. '|' .. width .. 'px|' .. (args.alt or ((args.label or mw.title.getCurrentTitle().text) .. ' is located in ' .. map('name'))) .. ']]'
 
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|link=File:' .. image .. ']]</div>'
 +
else
 +
return retval
 
end
 
end
return retval
 
 
end
 
end
  
Line 121: Line 161:
 
map = getMapParams(args[1], frame)
 
map = getMapParams(args[1], frame)
 
end
 
end
local retval = ''
+
local retval = '</div><div ' .. (args.caption and 'class="thumbcaption">' or 'style="font-size:90%;padding-top:3px">')
retval = retval .. '</div><div ' .. (args.caption and 'class="thumbcaption"' or 'style="font-size: 90%; padding-top:3px"') .. '>'
 
 
local caption = frame.args.caption or frame:getParent().args.caption
 
local caption = frame.args.caption or frame:getParent().args.caption
 
if caption and not args.caption_undefined then
 
if caption and not args.caption_undefined then
Line 132: Line 171:
 
if args.float == 'center' then
 
if args.float == 'center' then
 
retval = retval .. '</div>'
 
retval = retval .. '</div>'
 +
end
 +
if args.caption_undefined then
 +
retval = retval .. '[[Category:Location maps using skew|!]]'
 
end
 
end
 
return retval
 
return retval
Line 151: Line 193:
  
 
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 retval = '<div style="position:relative;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"' .. (title and (' title="' .. title .. '">') or '>')
if title then
 
retval = retval .. ' title="' .. title .. '"'
 
end
 
retval = retval .. '>'
 
 
if marksize ~= 0 then
 
if marksize ~= 0 then
 
retval = retval .. '[[File:' .. mark .. '|' .. marksize .. 'x' .. marksize .. 'px|' .. label .. '|link=' .. link
 
retval = retval .. '[[File:' .. mark .. '|' .. marksize .. 'x' .. marksize .. 'px|' .. label .. '|link=' .. link
Line 184: Line 222:
 
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
  
Line 205: Line 246:
 
map = getMapParams(args[1], frame)
 
map = getMapParams(args[1], frame)
 
end
 
end
local x, y, longitude, latitide
+
local x, y, longitude, latitude
longitude = decdeg(args.lon_deg, args.lon_min, args.lon_sec, args.lon_dir, nil, args.long)
+
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 = decdeg(args.lat_deg, args.lat_min, args.lat_sec, args.lat_dir, args.lat, 'latitude')
 +
local retval = ''
 +
if args.skew or args.lon_shift or (map('skew') ~= '') or (map('lat_skew') ~= '') or (map('crosses180') ~= '') then
 +
retval = retval .. '[[Category:Location maps using skew|' .. args[1] .. ']]'
 +
end
 
if map('x') ~= '' then
 
if map('x') ~= '' then
 
x = frame:callParserFunction('#expr', map('x', { latitude, longitude }))
 
x = frame:callParserFunction('#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 = getX(longitude, map('left'), map('right'))
 
end
 
end
 
if map('y') ~= '' then
 
if map('y') ~= '' then
Line 226: Line 271:
 
divContent = divContent .. markLabelDiv(args.label, args.label_size or 90, args.label_width or 6, args.position, args.background, x)
 
divContent = divContent .. markLabelDiv(args.label, args.label_size or 90, args.label_width or 6, args.position, args.background, x)
 
end
 
end
return markOuterDiv(x, y, divContent)
+
return retval .. markOuterDiv(x, y, divContent)
 
end
 
end
  
Line 240: Line 285:
 
end
 
end
 
return p.top(frame, args, map) .. p.mark(frame, args, map) .. p.bottom(frame, args, map)
 
return p.top(frame, args, map) .. 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'],
 +
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)
 +
end
 +
if not args[1] then
 +
args[1] = 'World'
 +
end
 +
if not map then
 +
map = getMapParams(args[1], frame)
 +
end
 +
local marks = {}
 +
local markhigh = args.markhigh
 +
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 .. p.mark(frame, manyMakeArgs(args, v), map)
 +
if args['mark' .. v .. 'high'] then
 +
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 map many using markhigh parameter]]' or '')
 
end
 
end
  
 
return p
 
return p

Revision as of 12:59, 5 May 2014

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

local function 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)
	end
	if not map then
		map = 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 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

function p.top(frame, args, map)
	if not args then
		args = getArgs(frame)
	end
	if not map then
		map = 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 then
		retval = retval .. '<div class="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
	if args.AlternativeMap then
		image = args.AlternativeMap
	elseif args.relief and map('image1') ~= '' then
		image = map('image1')
	else
		image = map('image')
	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
		return retval .. '<div style="position:absolute;top:0;left:0">[[File:' .. args.overlay_image .. '|' .. width .. 'px|link=File:' .. image .. ']]</div>'
	else
		return retval
	end
end

function p.bottom(frame, args, map)
	if not args then
		args = getArgs(frame)
	end
	if not map then
		map = getMapParams(args[1], frame)
	end
	local retval = '</div><div ' .. (args.caption and 'class="thumbcaption">' or 'style="font-size:90%;padding-top:3px">')
	local caption = frame.args.caption or frame:getParent().args.caption
	if caption and not args.caption_undefined then
		retval = retval .. mw.text.trim(caption)
	else
		retval = retval .. (args.label or mw.title.getCurrentTitle().text) .. ' (' .. map('name') .. ')'
	end
	retval = retval .. '</div></div></div>'
	if args.float == 'center' then
		retval = retval .. '</div>'
	end
	if args.caption_undefined then
		retval = retval .. '[[Category:Location maps using skew|!]]'
	end
	return retval
end

function p.container(frame, args, map)
	if not args then
		args = getArgs(frame)
	end
	if not map then
		map = 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, content)
	return '<div style="position:absolute;top:' .. y .. '%;left:' .. x .. '%;height:0;width:0;margin:0;padding:0">' .. content .. '</div>'
end

local function markImageDiv(mark, marksize, label, link, alt, title)
	local retval = '<div style="position:relative;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"' .. (title and (' title="' .. title .. '">') or '>')
	if marksize ~= 0 then
		retval = retval .. '[[File:' .. mark .. '|' .. marksize .. 'x' .. marksize .. 'px|' .. label .. '|link=' .. link
		if alt then
			retval = retval .. '|alt=' .. alt
		end
		retval = retval .. ']]'
	end
	return retval .. '</div>'
end

local function markLabelDiv(label, label_size, label_width, position, background, x)
	local retval = '<div style="font-size:' .. label_size .. '%;line-height:110%;position:relative;top:-1.5em;width:' .. label_width .. 'em;'
	if position == 'top' then -- specified top
		retval = retval .. 'top:-2.65em;left:-3em;text-align:center'
	elseif position == 'bottom' then -- specified bottom
		retval = retval .. 'top:-0.15em;left:-3em;text-align:center'
	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'
	else -- specified right or autodetected to right
		retval = retval .. 'left:0.5em;text-align:left'
	end
	retval = retval .. '"><span style="padding:1px'
	if background then
		retval = retval .. ';background-color:' .. background
	end
	return retval .. '">' .. label .. '</span></div>'
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)
	end
	if not map then
		map = 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 retval = ''
	if args.skew or args.lon_shift or (map('skew') ~= '') or (map('lat_skew') ~= '') or (map('crosses180') ~= '') then
		retval = retval .. '[[Category:Location maps using skew|' .. args[1] .. ']]'
	end
	if map('x') ~= '' then
		x = frame:callParserFunction('#expr', map('x', { latitude, longitude }))
	else
		x = getX(longitude, map('left'), map('right'))
	end
	if map('y') ~= '' then
		y = frame:callParserFunction('#expr', map('y', { latitude, longitude }))
	else
		y = getY(latitude, map('top'), map('bottom'))
	end
	local mark = args.mark or map('mark')
	if mark == '' then
		mark = 'Red pog.svg'
	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])
	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)
	end
	return retval .. markOuterDiv(x, y, divContent)
end

function p.main(frame, args, map)
	if not args then
		args = getArgs(frame)
	end
	if not args[1] then
		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

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'],
			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)
	end
	if not args[1] then
		args[1] = 'World'
	end
	if not map then
		map = getMapParams(args[1], frame)
	end
	local marks = {}
	local markhigh = args.markhigh
	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 .. p.mark(frame, manyMakeArgs(args, v), map)
		if args['mark' .. v .. 'high'] then
			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 map many using markhigh parameter]]' or '')
end

return p