نماد توضیحات توضیحات پودمان[ایجاد]
local locMap = {}

-- Internationalization

local api = {
    apiLocationMap    = 'locationMap',
    apiAddLocation    = 'addLocation',
    apiAddObject      = 'addObject',
    apiGetMapValue    = 'getMapValue',
    apiGetMapValueSet = 'getMapValueSet',
}

local quickbarMapType = 'relief'

local errMsgs = {
    anError       = 'Fehler',
    unknownMap    = 'Keine Karte für Region <em>%s</em> vorhanden',
    noMapImage    = 'Kein Kartenbild spezifiziert',
    wrongLat      = 'Breite %f liegt außerhalb der Kartenbegrenzungen',
    wrongLong     = 'Länge %f liegt außerhalb der Kartenbegrenzungen',
    wrongXBorders = 'Fehlende oder falsche horizontale Kartengrenzen',
    wrongYBorders = 'Fehlende oder falsche vertikale Kartengrenzen',
    noObject      = 'Kein Objekt angegeben',
    noXPos        = 'Keine horizontale Lage angegeben',
    noYPos        = 'Keine vertikale Lage angegeben',
    noParam       = 'Kein Parameter für getMapValue angegeben',
    notDefined    = 'Parameter nicht definiert',
}

local mapDocs = {
    tableClass    = 'prettytable',
    name          = 'Name',
    description   = 'Beschreibung',
    projection    = 'Projektion',
    top           = 'oben',
    bottom        = 'unten',
    left          = 'links',
    right         = 'rechts',
    default       = 'Standardkarte',
    relief        = 'Physische Karte',
    quickbar      = 'Standardkarte Quickbar',
    mark          = 'Marker',
    marksize      = 'Markergröße',
    linear        = '[[:w:Plattkarte|Plattkarte]]',
    nonlinear     = 'Nichtlineare Projektion',
}

-- Style aliases

local mapStyles = {
    mitte         = "margin: 0 auto !important",
    center        = "margin: 0 auto !important",
    left          = "clear: left; margin: 0 1em 1em 0; float: left",
    links         = "clear: left; margin: 0 1em 1em 0; float: left",
    right         = "clear: right; margin: 0 0 1em 1em; float: right",
    rechts        = "clear: right; margin: 0 0 1em 1em; float: right",
}

local labelStyles = {
    bold          = "font-weight: bold",
    fett          = "font-weight: bold",
    italic        = "font-style: italic",
    kursiv        = "font-style: italic",
    underline     = "text-decoration: underline",
    letterspacing = "letter-spacing: 0.1em",
    wordspacing   = "word-spacing: 0.5em",
    smallcaps     = "font-variant: small-caps",
    uppercase     = "text-transform: uppercase",
    region        = "font-weight: bold; text-transform: uppercase; color: #646464",
    subregion     = "font-weight: bold; color: #646464",
    waterbody     = "font-weight: bold; font-style: italic; letter-spacing: 0.1em; text-transform: uppercase; color: #2A6DB5",
    mountain      = "font-weight: bold; font-style: italic; letter-spacing: 0.1em; color: #704040",
}

local labelPositions1 = {
    ["1"]         = "left: 0; bottom: msize_px;",
    ["2"]         = "left: msize3_px; bottom: -2px;",
    ["3"]         = "left: msize5_px; top: -2em; height: 4em;",
    right         = "left: msize5_px; top: -2em; height: 4em;",
    rechts        = "left: msize5_px; top: -2em; height: 4em;",
    ["4"]         = "left: msize3_px; top: -2px;",
    ["5"]         = "left: 0; top: msize_px;",
    ["6"]         = "left: -3em; top: msize3_px;",
    bottom        = "left: -3em; top: msize3_px;",
    unten         = "left: -3em; top: msize3_px;",
    ["7"]         = "right: 0; top: msize_px;",
    ["8"]         = "right: msize3_px; top: -2px;",
    ["9"]         = "right: msize5_px; top: -2em; height: 4em;",
    left          = "right: msize5_px; top: -2em; height: 4em;",
    links         = "right: msize5_px; top: -2em; height: 4em;",
    ["10"]        = "right: msize3_px; bottom: -2px;",
    ["11"]        = "right: 0; bottom: msize_px;",
    ["12"]        = "left: -3em; bottom: msize3_px;",
    top           = "left: -3em; bottom: msize3_px;",
    oben          = "left: -3em; bottom: msize3_px;",
    center        = "top: -2em; height: 4em; left: -3em;",
    mitte         = "top: -2em; height: 4em; left: -3em;",
}

local labelPositions2 = {
    ["1"]         = "text-align: left;",
    ["2"]         = "text-align: left;",
    ["3"]         = "text-align: left; height: 4em;",
    right         = "text-align: left; height: 4em;",
    rechts        = "text-align: left; height: 4em;",
    ["4"]         = "text-align: left;",
    ["5"]         = "text-align: left;",
    ["6"]         = "text-align: center;",
    bottom        = "text-align: center;",
    unten         = "text-align: center;",
    ["7"]         = "text-align: right;",
    ["8"]         = "text-align: right;",
    ["9"]         = "text-align: right; height: 4em;",
    left          = "text-align: right; height: 4em;",
    links         = "text-align: right; height: 4em;",
    ["10"]        = "text-align: right;",
    ["11"]        = "text-align: right;",
    ["12"]        = "text-align: center;",
    top           = "text-align: center;",
    oben          = "text-align: center;",
    center        = "text-align: center; height: 4em;",
    mitte         = "text-align: center; height: 4em;",
}

-- Local functions, please do not call them directly

local function split(s)
    local split = mw.text.split(s, ';')
    local result = {}
    for key,value in pairs(split) do
        tr = mw.text.trim(value)
        if tr ~= '' then table.insert(result, tr) end
    end
    return result;
end

local function analyzeLabelStyle(style)
    local split = split(style)
    for key,value in pairs(split) do
        if labelStyles[value] ~= nil then split[key] = labelStyles[value] end
    end
    return table.concat(split, '; ') .. ';'
end

local function analyzeMapStyle(style)
    local split = split(style)
    for key,value in pairs(split) do
        if mapStyles[value] ~= nil then split[key] = mapStyles[value] end
    end
    return table.concat(split, '; ') .. ';'
end

local function setLocation(x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition)
    local lmarksize = math.floor(marksize + 0.5)
    local msize = math.floor((marksize - 1) / 2 + 0.5)
    local msize3 = math.floor((marksize + 2) / 2 + 0.5)
    local msize5 = math.floor((marksize + 4) / 2 + 0.5)
    local halfMarkSize = -msize .. 'px'

    -- Setting a marker
    local sCode = '<div style="position: absolute; border-style: none; padding: 0; overflow: visible; top: '
        .. y*100 .. '%; left: ' .. x*100 .. '%;">'
    if mark ~= 'none' then
        sCode = sCode .. '<div style="position: absolute; padding: 0; top: ' .. halfMarkSize .. '; left: '
        .. halfMarkSize .. '; min-width: ' .. lmarksize .. 'px; min-height: ' .. lmarksize .. 'px;">'
        .. '[[Image:' .. mark .. '|' .. lmarksize .. 'x' .. lmarksize
        .. 'px|top|link=' .. name .. '|' .. name .. ']]'
        .. '</div>'
    end

    -- Adding a label
    if (label ~= '') and (label ~= 'none') then
        sCode = sCode .. '<table style="position: absolute; border: none; margin: 0; background-color: transparent; border-collapse: collapse;'
        if (labelWrap ~= nil) and (labelWrap == 'manual') then
            sCode = sCode .. ' white-space: nowrap; width: 10em !important; '
        else
            sCode = sCode .. ' width: 6em !important; '
        end
        if labelPositions1[labelPosition] ~= nil then
            local posStyles = labelPositions1[labelPosition]
            posStyles = string.gsub(posStyles, 'msize_', msize)
            posStyles = string.gsub(posStyles, 'msize3_', msize3)
            posStyles = string.gsub(posStyles, 'msize5_', msize5)
            sCode = sCode .. posStyles .. '"><tr><td style="border: none; padding: 0; vertical-align: middle; '
                .. labelPositions2[labelPosition]
        else
            -- Automatic Estimation
            if y<=0.5 then
                sCode = sCode .. 'top: ' .. msize3 .. 'px; '
            else
                sCode = sCode .. 'bottom: ' .. msize3 .. 'px; '
            end
            if x<0.25 then
                sCode = sCode .. 'text-align: left; left: ' .. math.floor(3 - 60*x)/10 .. 'em;'
                    .. '"><tr><td style="border: none; padding: 0; vertical-align: middle; text-align: left;'
            else if x<0.75 then
                    sCode = sCode .. 'text-align: center; left: -3em;'
                        .. '"><tr><td style="border: none; padding: 0; vertical-align: middle; text-align: center;'
                else
                    sCode = sCode .. 'text-align: right; right: ' .. math.floor(10*(0.3 - (1 - x) * 6))/10 .. 'em;'
                        .. '"><tr><td style="border: none; padding: 0; vertical-align: middle; text-align: right;'
                end
            end
        end
        if (labelWrap ~= nil) and (labelWrap == 'manual') then
            sCode = sCode .. ' white-space: nowrap; width: 10em !important;'
        else
            sCode = sCode .. ' width: 6em !important;'
        end
        sCode = sCode .. '"><span style="' .. analyzeLabelStyle(labelStyle) .. '">'
            .. label .. '</span></td></tr></table>'
    end
    sCode = sCode .. '</div>'
    
    return sCode
end

local function baseMap(mapImage, description, mapStyle, width, caption, captionStyle,
	captionInnerBorder, captionOuterBorder, x, y, name, label, mark, marksize, labelStyle,
	labelWrap, labelPosition, places)
    
    local sCode = '<table class="locationMap" style="overflow: visible; max-width: none !important; border-collapse: collapse;'
    -- Test if fixed or variable width (..x..). Force width if fixed and a caption is specified
    if (string.find(width, 'x') == nil) and (caption ~= '') then
        sCode = sCode .. ' width: ' .. tostring(tonumber(width)+10) ..'px !important;'
    else
        sCode = sCode .. ' width: auto !important;'
    end
    if caption ~= '' then sCode = sCode .. ' border: ' .. captionOuterBorder .. ';' end
    sCode = sCode .. ' ' .. analyzeMapStyle(mapStyle) .. '"><tr><td class="thumb" style="overflow: visible; border: none; padding: '
    if caption ~= '' then sCode = sCode .. '3px 3px 0;">' else sCode = sCode .. '0;">' end
    sCode = sCode .. '<div class="noresize" style="overflow: visible; max-width: none !important; padding: 0; margin: 0 auto !important; position: relative; border: '
    if caption ~= '' then sCode = sCode .. captionInnerBorder .. ';' else sCode = sCode .. 'none;' end
    -- Test if fixed or variable width (..x..). Force width if fixed
    if string.find(width, 'x') == nil then sCode = sCode .. ' width: ' .. width ..'px !important;">' else sCode = sCode .. ' width: auto !important;">' end
    sCode = sCode .. '[[Image:' .. mapImage .. '|' .. width .. 'px|center|' .. description .. ']]'
    if (x<0) or (x>1) or (y<0) or (y>1) then
        sCode = sCode .. '<div style="position: absolute; width: 50%; top: 50%; left: 25%; color: #ff0000; font-weight: bold; text-align: center;">'
           .. 'Fehlerhafte Koordinate ' .. name .. '</div>'
    else
        sCode = sCode .. setLocation(x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition)
    end
    sCode = sCode .. places .. '</div></td>'
    if caption ~= '' then
        sCode = sCode .. '<tr><td class="thumbcaption"'
        if captionStyle ~= '' then
            sCode = sCode .. ' style="'.. captionStyle .. '"'
        end
        sCode = sCode .. '>' .. caption .. '</td></tr>'
    end
    sCode = sCode .. '</table>'
    return sCode
end

-- Handling regional map data

local function getMapData(id)
    local region = require('Modul:Location map data ' .. id)
    if (region ~= nil) and (region.data ~= nil) then
        region.data['id'] = id
        return region.data
    else
        return nil
    end
end

local function getMapObject(id)
    local region = require('Modul:Location map data ' .. id)
    if (region ~= nil) and (region.data ~= nil) then
        region.data['id'] = id
        return region
    else
        return nil
    end
end

local function linearX(data, long)
    local left = data['left']
    local right = data['right']
    if (data == nil) or (left == nil) or (right == nil) or  (left == right) then
        -- Error
        return -1
    else if left < right then
            return (long - left) / (right - left)
        else
            if long < 0 then
                return (360 + long - left) / (360 + right - left)
            else
                return (long - left) / (360 + right - left)
            end
        end
    end
end

local function linearY(data, lat)
    local top = data['top']
    local bottom = data['bottom']
    if (data == nil) or (top == nil) or (bottom == nil) or  (top == bottom) then
        -- Error
        return -1
    else
        return (lat - top) / (bottom - top)
    end
end

local function getX(anObject, long, lat)
    if anObject.x ~= nil then
        return anObject.x(lat, long)
    else
    	return linearX(anObject.data, long)
    end
end

local function getY(anObject, long, lat)
    if anObject.y ~= nil then
        return anObject.y(lat, long)
    else
    	return linearY(anObject.data, lat)
    end
end

local function getMapImage(data, which)
    local image = data['default']
    local w = which
    if w == 'quickbar' then
        w = quickbarMapType
        if (data['quickbar'] ~= nil) and (data['quickbar'] ~= '') then
            w = data['quickbar']
        end
    end
    if (w ~= '') and (data[w] ~= nil) and (data[w] ~= '') then
        image = data[w]
    end
    return image
end

-- Parameters and error handling

local function argCheck(param, altValue)
    if param == nil then
        return altValue
    else
        local val = mw.text.trim(param)
        if val == '' then val = altValue end
        return val
    end
end

local function errorStr(s)
    return '<strong class="error">Fehler im Modul <em>Location map</em>: ' .. s .. '</strong>'
end

-- Map functions

local function apiLocationMap(args)
    local map = argCheck(args['map'], 'missing')
    local success, mObject = pcall(getMapObject, map)
    if not success then
        return errorStr(string.format(errMsgs['unknownMap'], map))
    else
        -- Error handling
        local errorMsgs = {}
        success = true
        
        -- Parameters check
        local mData = mObject.data
        local description = ''
        if mData['description'] ~= nil then description = mData['description'] end

        local lat = tonumber(argCheck(tostring(args['lat']), 0))
        local long = tonumber(argCheck(tostring(args['long']), 0))
        local x = getX(mObject, long, lat)
        if (x<0) or (x>1) then
            success = false
            if x == -1 then
                table.insert(errorMsgs, errorStr(errMsgs['wrongXBorders']))
            else
                table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLong'], long)))
            end
        end
        local y = getY(mObject, long, lat)
        if (y<0) or (y>1) then
            success = false
            if y == -1 then
                table.insert(errorMsgs, errorStr(errMsgs['wrongYBorders']))
            else
                table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLat'], lat)))
            end
        end

        local maptype = argCheck(args['maptype'], 'default')
        local mapImage = argCheck(args['alternativeMap'], getMapImage(mData, maptype))
        if (mapImage == nil) or (mapImage == '') then
            success = false
            table.insert(errorMsgs, errorStr(errMsgs['noMapImage']))
        end
        if not success then
            return table.concat(errorMsgs, '<br />')
        else
            local name = argCheck(args['name'], '')
            local label = argCheck(args['label'], '')
            -- Checking width syntax
            local width = argCheck(tostring(args['width']), '')
            if (string.match(width, '^%d+$') == nil) and (string.match(width, '^%d*x%d+$') == nil) then
                width = '200x200'
            end
            if mData['mark'] ~= nil then mark = mData['mark'] else mark = 'Reddot.svg' end
            mark = argCheck(args['mark'], mark)
            if mData['marksize'] ~= nil then marksize = mData['marksize'] else marksize = 5 end
            local marksize = argCheck(args['marksize'], marksize)
            local mapStyle = argCheck(args['mapStyle'], 'mitte')
            local labelStyle = argCheck(args['labelStyle'], '')
            local labelBackground = argCheck(args['labelBackground'], '')
            if labelBackground ~='' then
                labelBackground = 'background: ' .. labelBackground
                if labelStyle ~='' then
                    labelStyle = labelStyle .. '; ' .. labelBackground
                else
                    labelStyle = labelBackground
                end
            end
            local labelWrap = argCheck(args['labelWrap'], 'auto')
            local labelPosition = argCheck(args['labelPosition'], 'auto')
            local caption = argCheck(args['caption'], '')
            local captionStyle = argCheck(args['captionStyle'], '')
            local captionInnerBorder = argCheck(args['captionInnerBorder'], '1px solid #cccccc')
            local captionOuterBorder = argCheck(args['captionOuterBorder'], '1px solid #cccccc')
            local places = argCheck(args['places'], '')

            return baseMap(mapImage, description, mapStyle, width, caption, captionStyle,
            	captionInnerBorder, captionOuterBorder, x, y, name, label, mark, marksize,
            	labelStyle, labelWrap, labelPosition, places)
        end
    end
end

local function apiAddLocation(args)
    local map = argCheck(args['map'], 'missing')
    local success, mObject = pcall(getMapObject, map)
    if not success then
        return errorStr(string.format(errMsgs['unknownMap'], map))
    else
        -- Error handling
        local errorMsgs = {}
        success = true

        -- Parameters check
        local mData = mObject.data
        local lat = tonumber(argCheck(tostring(args['lat']), 0))
        local long = tonumber(argCheck(tostring(args['long']), 0))
        local x = getX(mObject, long, lat)
        if (x<0) or (x>1) then
            success = false
            if x == -1 then
                table.insert(errorMsgs, errorStr(errMsgs['wrongXBorders']))
            else
                table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLong'], long)))
            end
        end
        local y = getY(mObject, long, lat)
        if (y<0) or (y>1) then
            success = false
            if y == -1 then
                table.insert(errorMsgs, errorStr(errMsgs['wrongYBorders']))
            else
                table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLat'], lat)))
            end
        end
        if not success then
            return table.concat(errorMsgs, '<br />')
        else
            local name = argCheck(args['name'], '')
            local label = argCheck(args['label'], '')

            if mData['mark'] ~= nil then mark = mData['mark'] else mark = 'Reddot.svg' end
            mark = argCheck(args['mark'], mark)
            if mData['marksize'] ~= nil then marksize = mData['marksize'] else marksize = 5 end
            local marksize = argCheck(args['marksize'], marksize)
            local mapStyle = argCheck(args['mapStyle'], 'mitte')
            local labelStyle = argCheck(args['labelStyle'], '')
            local labelBackground = argCheck(args['labelBackground'], '')
            if labelBackground ~='' then
                labelBackground = 'background: ' .. labelBackground
                if labelStyle ~='' then
                    labelStyle = labelStyle .. '; ' .. labelBackground
                else
                    labelStyle = labelBackground
                end
            end
            local labelWrap = argCheck(args['labelWrap'], 'auto')
            local labelPosition = argCheck(args['labelPosition'], 'auto')

            return setLocation(x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition)
        end
    end
end

local function apiAddObject(args)
    local anObject = argCheck(args['object'], '')
    if anOject == '' then
        return errorStr(errMsgs['noObject'])
    else
        local success = true
        local errorMsgs = {}
        
        local right = argCheck(args['right'], '')
        local left = argCheck(args['left'], '')
        if (right == '') and (left == '') then
            success = false
            table.insert(errorMsgs, errorStr(errMsgs['noXPos']))
        end
        local top = argCheck(args['top'], '')
        local bottom = argCheck(args['bottom'], '')
        if (top == '') and (bottom == '') then
            success = false
            table.insert(errorMsgs, errorStr(errMsgs['noYPos']))
        end
        if not success then
            return table.concat(errorMsgs, '<br />')
        else
            local objectStyle = argCheck(args['objectStyle'], '')
            local objectBackground = argCheck(args['objectBackground'], '')
            if objectBackground ~='' then
                objectBackground = 'background: ' .. objectBackground
                if objectStyle ~='' then
                    objectStyle = objectStyle .. '; ' .. objectBackground
                else
                    objectStyle = objectBackground
                end
            end
            sCode = '<table style="border-collapse: collapse; margin: 0; padding: 0; width: auto !important; overflow: visible; position: absolute; background: transparent; '
            if (left ~='') then
                sCode = sCode .. 'left: ' .. left .. '; '
            else
                sCode = sCode .. 'right: ' .. right .. '; '
            end
            if (top ~='') then
                sCode = sCode .. 'top: ' .. top .. ';"><tr><td style="border: none; padding: 0; '
            else
                sCode = sCode .. 'bottom: ' .. bottom .. ';"><tr><td style="border: none; padding: 0; '
            end
            sCode = sCode .. analyzeLabelStyle(objectStyle)
                .. '">' .. anObject .. '</td></tr></table>'

            return sCode
        end
    end
end

-- Documentation of map data

local function apiGetMapValue(args)
    local map = argCheck(args['map'], 'missing')
    local success, mData = pcall(getMapData, map)
    if not success then
        return errorStr(string.format(errMsgs['unknownMap'], map))
    else
        local param = argCheck(args['param'], '')
        if param == '' then
            return errorStr(errMsgs['noParam'])
        else
            if mData[param] == nil then
                return errMsgs['anError']
            else
                return mData[param]
            end
        end
    end
end

local function apiGetMapValueSet(args)
    local map = argCheck(args['map'], 'missing')
    local success, mData = pcall(getMapData, map)
    if not success then
        return errorStr(string.format(errMsgs['unknownMap'], map))
    else
        local paramList = {'name', 'description', 'projection', 'top', 'bottom',
           'left', 'right', 'default', 'relief', 'quickbar', 'mark', 'marksize'}
        local sCode = '<table class="' .. mapDocs['tableClass'] .. '">'
        for i = 1, #paramList, 1 do
            sCode = sCode .. '<tr><th style="text-align: left">' .. mapDocs[paramList[i]] .. '</th><td>'
            if mData[paramList[i]] == nil then
                sCode = sCode .. errMsgs['notDefined'] .. '</td></tr>'
            else
                local v = mData[paramList[i]]
                if mapDocs[v] ~= nil then v = mapDocs[v] end
            	sCode = sCode .. v .. '</td></tr>'
            end
        end
        return sCode .. '</table>'
    end
end

-- API function calls

locMap[api.apiLocationMap] = function (frame)
    return apiLocationMap(frame.args)
end

locMap[api.apiAddLocation] = function (frame)
    return apiAddLocation(frame.args)
end

locMap[api.apiAddObject] = function (frame)
    return apiAddObject(frame.args)
end

locMap[api.apiGetMapValue] = function (frame)
    return apiGetMapValue(frame.args)
end

locMap[api.apiGetMapValueSet] = function (frame)
    return apiGetMapValueSet(frame.args)
end

-- Example for usage in a Lua script

function locMap.exampleLuaCall()
    local frame = {}
    frame.args = {
        map   = 'de',
        lat   = 52.51789,
        long  = 13.38873,
        name  = 'Berlin',
        label = '[[Berlin]]',
    }
    return locMap.locationMap(frame)
end

return locMap