Modulo:Coord

Versione del 20 ott 2013 alle 17:12 di Rotpunkt (discussione | contributi) (Nuova pagina)
(diff) ← Versione meno recente | Versione attuale (diff) | Versione più recente → (diff)
Info Istruzioni per l'uso
Questo è un modulo scritto in Lua. Le istruzioni che seguono sono contenute nella sottopagina Modulo:Coord/man (modifica · cronologia)
Sandbox: Modulo:Coord/sandbox (modifica · cronologia) · Sottopagine: lista · Test: Modulo:Coord/test (modifica · cronologia · esegui)

Modulo Lua che implementa le funzionalità del Template:Coord.

Ha una sottopagina di configurazione Modulo:Coord/Configurazione e una sottopagina CSS Modulo:Coord/styles.css.

Utilizzo da un altro modulo

Il modulo può essere usato anche da un altro modulo tramite "require". È sufficiente inserire nel modulo:

local mCoord = require('Modulo:Coord')

La funzione esportata è _main, con gli stessi parametri del template.

Esempio
local mCoord = require('Modulo:Coord')
local p = {}

function p.main(frame)
    local sydney, wd

    sydney = mCoord._main( { '-33.86', '151.211111', format = 'dms' } )
    wd = mCoord._main( { display = 'inline,title', format = 'dec' } )
    
    return string.format('Le coordinate dms di Sydney sono: %s. ' .. 
						 'Le coordinate dec dell\'elemento Wikidata collegato: %s.',
						 sydney, wd)
end

return p

--[[
* Modulo per implementare le funzionalità di Coord
*
* Traduce in lua:
* Template:Coord
* Template:Coord/input/dec
* Template:Coord/input/d
* Template:Coord/input/dm
* Template:Coord/input/dms
* Template:Coord/input/ERROR
* Template:Coord/input/error2
* Template:Coord/link
* Template:Coord/prec dec
* Template:Coord/dms2dec
* Template:Coord/dec2dms
* Template:Coord/dec2dms/d
* Template:Coord/dec2dms/d1
* Template:Coord/dec2dms/dm
* Template:Coord/dec2dms/dm1
* Template:Coord/dec2dms/dms
* Template:Coord/dec2dms/dms1
* Template:Precision1
]]

-- Variabili globali
local p = {}           -- per l'esportazione delle funzioni del modulo
local args = {}        -- argomenti passati al template
local errorTable = {}  -- table per contenere gli errori da ritornare

-- Import
local htmlBuilder = require("Module:HtmlBuilder")

-- Configurazione
local cfg = mw.loadData("Module:Coord/Configurazione")

-------------------------------------------------------------------------------
--                      Funzioni di utilità
-------------------------------------------------------------------------------

-- Ritorna il numero arrotondato al numero di cifre decimali richiesto
local function round(num, idp)
    local mult = 10^(idp or 0)

    return math.floor(num * mult + 0.5) / mult
end

-- Ritorna il numero di cifre decimali di un numero
local function getDecimalPlaces(num)
    local dec = tostring(num):match("%d+%.(%d+)")

    return dec and dec:len() or 0
end

-- Ritorna la stringa "0 + numero" quando il numero è di una sola cifra, altrimenti lo stesso numero
local function padleft0(num)
    return num < 10 and ("0" .. num) or num
end

-------------------------------------------------------------------------------
--                      Validazione parametri
-------------------------------------------------------------------------------

-- Aggiunge un messagio di errore alla risposta come elenco puntato
local function dumpError(...)
    local arg = {...}

    table.insert(errorTable, "* ")
    for _, val in ipairs(arg) do
        table.insert(errorTable, val)
    end
    table.insert(errorTable, "\n")
end

-- Ritorna una stringa contenente la lista degli errori,
-- nel namespace principale aggiunge una categoria di warning
local function getErrors()
    local text = ""

    if mw.title.getCurrentTitle().namespace == 0 then
        text = "[[Categoria:" .. cfg.categorie["warning"] .. "]]\n"
    end

    text = text .. "<span style=\"color:red;\">Il template {{Coord}} ha riscontrato degli errori ([[Template:Coord|istruzioni]]):\n" ..
           table.concat(errorTable) .. "</span>"

    return text
end

-- Riconosce il tipo di richiesta ("dec", "d", "dm" o "dms") e valida i parametri posizionali
-- Ritorna il tipo di richiesta o nil in caso di errore.
local function paramsParse()
    local paramType, paramName, paramMin, paramMax, reqFormat, prefix, num, str
    local posArgs = 0

    -- riconoscimento tipo di richiesta
    for k, v in ipairs(args) do
        posArgs = posArgs + 1
    end
    if posArgs == 2 or posArgs == 3 then
        reqFormat = "dec"
    elseif posArgs == 4 or posArgs == 5 then
        reqFormat = "d"
    elseif posArgs == 6 or posArgs == 7 then
        reqFormat = "dm"
    elseif posArgs == 8 or posArgs == 9 then
        reqFormat = "dms"
    else
        dumpError("Errato numero di parametri")
        return nil
    end

    -- validazione parametri posizionali
    currFormat = cfg.params[reqFormat]    
    for k, v in ipairs(args) do
        if currFormat[k] then
            paramType = currFormat[k][1]
            paramName = currFormat[k][2]
            paramMin = currFormat[k][3]
            paramMax = currFormat[k][4]
            prefix = reqFormat .. " format: " .. paramName
            -- valida un parametro di tipo numero
            if paramType == "number" then
                num = tonumber(v)
                if num then
                    if num < paramMin then
                        dumpError(prefix, " < ", paramMin)
                    elseif num > paramMax then
                        dumpError(prefix, " > ", paramMax)        
                    end
                else
                    dumpError(prefix, " non è un numero")
                end
            -- valida un parametro di tipo stringa            
            elseif paramType == "string" then
                if v ~= paramMin and v ~= paramMax then
                    dumpError(prefix, " != ", paramMin, " e != ", paramMax)
                end
            end
        end
    end
   
    return #errorTable == 0 and reqFormat or nil
end

-------------------------------------------------------------------------------
--                      classi DecCoord e DmsCoord
-------------------------------------------------------------------------------

-- Rappresenta una coordinata (lat o long) in gradi decimali 
local DecCoord = {
    deg = nil, card = nil, display = nil
}

-- Rappresenta una coordinata (lat o long) in gradi/minuti/secondi
local DmsCoord = {
    deg = nil, min = nil, sec = nil, card = nil, display = nil
}

-- Costruttore di DecCoord
function DecCoord:new(deg, card)
    local self = {}
    setmetatable(self, { __index = DecCoord })

    self.deg = tonumber(deg)
    self.card = card
    self.display = (self.deg > 0 and self.deg or -self.deg) .. "°" .. self.card

    return self
end

-- Ritorna un nuovo oggetto DmsCoord, convertendo in gradi/minuti/secondi.
-- {{coord}} usa d/dm/dms (arrotondati all'intero), {{dec2dms}} anche d1/dm1/dms1 (arrotondati a 0.1)
function DecCoord:toDms(dmsFormat)
    local deg, min, sec, card

    card = self.card
    if self.deg < 0 then
        card = card == "N" and "S" or (card == "E" and "W" or card)
    end
    self.deg = self.deg > 0 and self.deg or -self.deg

    if dmsFormat == "d" then
        deg = round(self.deg, 0) 
    elseif dmsFormat == "d1" then
        deg = round(self.deg, 1)
    elseif dmsFormat == "dm" then
        deg = math.floor(self.deg) 
        min = round((round(self.deg * 600, 0) % 600) / 10, 0)
    elseif dmsFormat == "dm1" then
        deg = math.floor(self.deg)
        min = (round(self.deg * 600, 0) % 600) / 10
    elseif dmsFormat == "dms" then
        deg = math.floor(self.deg)
        min = math.floor((self.deg * 60) % 60)
        sec = round((round(self.deg * 36000, 0) % 600) / 10, 0)
    elseif dmsFormat == "dms1" then
        deg = math.floor(self.deg)
        min = math.floor((self.deg * 60) % 60)
        sec = (round(self.deg * 36000, 0) % 600) / 10
    end

    return DmsCoord:new(deg, min, sec, card)
end

-- Costruttore di DmsCoord
function DmsCoord:new(deg, min, sec, card)
    local self = {}
    setmetatable (self, { __index = DmsCoord })

    self.deg = tonumber(deg)
    self.min = min and tonumber(min)
    self.sec = sec and tonumber(sec)    
    self.card = card
    self.display = self.deg .. "°" ..
                   (self.min and (padleft0(self.min) .. "′") or "") ..
                   (self.sec and (padleft0(self.sec) .. "″") or "") ..
                   (self.card and self.card or "")

    return self
end

-- Ritorna un nuovo oggetto DecCoord, convertendo in gradi decimali
function DmsCoord:toDec()
    local sign, roundval, deg
    
    sign = (self.card == "N" or self.card == "E") and 1 or -1
    roundval = (self.sec and 5 or (self.min and 3 or 0)) + getDecimalPlaces(self.sec or self.min or self.deg)
    deg = sign * round((self.deg + ((self.min or 0) + (self.sec or 0) / 60) / 60), roundval)
    
    return DecCoord:new(deg, self.card)
end

-------------------------------------------------------------------------------
--                                Coord
-------------------------------------------------------------------------------

-- Usato nelle conversioni da gradi decimali a gradi/minuti/secondi,
-- dato il numero di decimali maggiore tra latdec e longdec ritorna il formato adatto:
-- "d"   per 0   decimali
-- "dm"  per 1-2 decimali
-- "dms" per > 3 decimali
local function getDmsFormat(latdec, longdec)
    local d1, d2, max
  
    d1 = getDecimalPlaces(latdec)
    d2 = getDecimalPlaces(longdec)
    max = d1 > d2 and d1 or d2

    return max == 0 and "d" or
           ((max == 1 or max == 2) and "dm" or "dms")
end

-- Crea l'HTML ritornato da Coord
local function buildHTML(decLat, decLong, dmsLat, dmsLong, param, defaultFormat)
    local root, text, url, displayInline, displayTitle, htmlTitle

    -- geohack url e parametri
    url = "http://tools.wmflabs.org/geohack/geohack.php?pagename=" ..
          mw.uri.encode(mw.title.getCurrentTitle().prefixedText, "WIKI") ..
          "&language=it&params=" .. param ..
          (args["name"] and ("&title=" .. mw.uri.encode(args["name"])) or "")

    root = htmlBuilder.create()
    root
        .tag("span")
            .addClass("plainlinks nourlexpansion")
            .wikitext("[" .. url)
            .tag("span")
                .addClass(defaultFormat == "dec" and "geo-nondefault" or "geo-default") 
                .tag("span")
                    .addClass("geo-dms")
                    .attr("title", "Mappe, foto aeree e altri dati per questa posizione")
                    .tag("span")
                        .addClass("latitude")
                        .wikitext(dmsLat.display)
                        .done()
                    .wikitext("&#32;")
                    .tag("span")
                        .addClass("longitude")
                        .wikitext(dmsLong.display)
                        .done()
                    .done()
                 .done()
            .tag("span")
                .addClass("geo-multi-punct")
                .wikitext("&#32;/&#32;")
                .done()
            .tag("span")
                .addClass(defaultFormat == "dec" and "geo-default" or "geo-nondefault")
                .wikitext(args["name"] and "<span class=\"vcard\">" or "")
                .tag("span")
                    .addClass("geo-dms")
                    .attr("title", "Mappe, foto aeree e altri dati per questa posizione")
                    .wikitext(decLat.display .. "&#32;" .. decLong.display)
                    .done()
                .tag("span")
                    .attr("style", "display:none")
                    .tag("span")
                        .addClass("geo")
                        .wikitext(decLat.deg .. "&#32;" .. decLong.deg)
                         .done()
                    .done()
                .wikitext(args["name"] and ("<span style=\"display:none\"> (<span class=\"fn org\">" ..
                          args["name"] .. "</span>)</span></span>") or "")
                .done()
            .wikitext("]")
            .done()

    -- formatta il risultato a seconda di args["display"] (nil, "inline", "title", "inline,title")
    text = tostring(root) .. (args["notes"] or "")
    displayInline = not args["display"] or args["display"] == "inline" or args["display"] == "inline,title"
    displayTitle = args["display"] == "title" or args["display"] == "inline,title"
    htmlTitle = "<span style=\"font-size: small;\"><span id=\"coordinates\">[[Coordinate geografiche|Coordinate]]: "

    return (displayInline and text or "") ..
           (displayTitle and (htmlTitle .. text .. "</span></span>") or "")
end

-- Dato un input fino a 9 parametri posizionali e 3 con nome, ritorna un html
-- contenente le coordinate in formato dec e dms come collegamento esterno a geohack.php.
local function coord()
    local param, defaultFormat, dmsFormat, reqFormat

    reqFormat = paramsParse()
    if not reqFormat then
        return getErrors()
    end
   
    if reqFormat == "dec" then    
        -- {{coord|43.6500|-79.3800}}
        decLat = DecCoord:new(args[1], tonumber(args[1]) > 0 and "N" or "S")
        decLong = DecCoord:new(args[2], tonumber(args[2]) > 0 and "E" or "W")
    elseif reqFormat == "d" then
        -- {{coord|43.651234|N|79.383333|W}}
        decLat = DecCoord:new(args[1], args[2])
        decLong = DecCoord:new(args[3], args[4])
    elseif reqFormat == "dm" then
        -- {{coord|43|29|N|79|23|W}}
        dmsLat = DmsCoord:new(args[1], args[2], nil, args[3])
        dmsLong = DmsCoord:new(args[4], args[5], nil, args[6])
    elseif reqFormat == "dms" then
        -- {{coord|43|29|4|N|79|23|0|W}}
        dmsLat = DmsCoord:new(args[1], args[2], args[3], args[4])
        dmsLong = DmsCoord:new(args[5], args[6], args[7], args[8])
    end
    
    -- conversioni dec <=> dms
    if reqFormat == "dec" or reqFormat == "d" then
        dmsFormat = getDmsFormat(decLat.deg, decLong.deg)
        dmsLat = decLat:toDms(dmsFormat)
        dmsLong = decLong:toDms(dmsFormat)    
    elseif reqFormat == "dm" or reqFormat == "dms" then
        decLat = dmsLat:toDec()
        decLong = dmsLong:toDec()     
    end

    if args["format"] then
        defaultFormat = args["format"]
    elseif reqFormat == "dec" then
        defaultFormat = "dec"
    elseif reqFormat == "d" then
        defaultFormat = (dmsFormat == "d" and "dms" or "dec")
    else
        defaultFormat = "dms"
    end

    -- crea la stringa param per geohack.php
    if reqFormat == "dec" then
        param = args[1] .. "_N_" .. args[2] .. "_E" .. (args[3] and ("_" .. args[3]) or "")
    else
        -- concatena solo i posizionali
        param = table.concat(args, "_")
    end

    return buildHTML(decLat, decLong, dmsLat, dmsLong, param, defaultFormat)
end

-- Entry-point per eventuale {{dms2dec}}
function p.dms2dec(frame)
    local args = frame.args

    -- {{dms2dec|N|10|20|35}}
    return DmsCoord:new(tonumber(args[2]), tonumber(args[3]), tonumber(args[4]), args[1]):toDec().deg
end

-- Entry-point per eventuale {{dec2dms}}
function p.dec2dms(frame)
    local args = frame.args
    
    -- {{dec2dms|10.391944|N|S|d}}
    return DecCoord:new(tonumber(args[1]), tonumber(args[1]) > 0 and args["2"] or args["3"]):toDms(args["4"]).display
end

-- Entry-point per {{coord}}
function p.coord(frame)
    for k, v in pairs(frame:getParent().args) do
        if v ~= "" then
            args[k] = v
        end
    end
 
    return coord()
end
 
return p