Module:Sandbox/Razimantv/Location map

require('strict')

local p = {}

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

function p.getMapParams(map, frame)

mw.log('test')

error('The name of the location map definition to use must be specified', 2)

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 '

' or ''

if args.caption and args.caption ~= '' then

retval = retval .. '

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 .. '">

' or '">')

else

retval = retval .. '

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 .. '">

'

end

local image = getContainerImage(args, map)

retval = string.format(

'%sFile:%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 .. '

'

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 = '

'

if not args.caption then

retval = retval .. '

'

.. ((args.label or mw.title.getCurrentTitle().text) .. ' (' .. map('name') .. ')')

.. '

'

elseif args.caption == '' then

retval = retval .. '

'

else

retval = retval .. '

'

end

retval = retval .. '

'

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 .. 'Page using removed parameter'

end

if args.float == 'center' then

retval = retval .. '

'

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',

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('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('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('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 'Page using removed parameter' or '')

end

return p