Módulo:Coordenadas

De MacphersonWiki
Ir a la navegación Ir a la búsqueda

La documentación para este módulo puede ser creada en Módulo:Coordenadas/doc

--[[
Este módulo está diseñado para reemplazar la funcionalidad de {{coord}} y afines
plantillas. Proporciona varios métodos, incluye:

{{#Invoke:Coordenadas | coord }} : Función general de formato y visualización
valores de coordenadas.

{{#Invoke:Coordenadas | dec2dms }} : Función sencilla para la conversión  el formato de grados con decimales (deg)
al formato DMS (grados minutos y segundos).

{{#Invoke:Coordenadas | dms2dec }} : Función sencilla para convertir el formato DMS (grados, minutos y segundos) al formato
 de grados con decimales (deg).

]]

math_mod = require( "Module:Math" );
globalFrame = nil

coordinates = {};

--[[ Función auxiliar, sustituye a: {{coord/display/title}} ]]
function displaytitle (s, notes)
	local htmlTitle = mw.html.create('span')
		:attr{ id = 'coordinates' }
		:css( 'font-size', 'small' )
		:node( "[[Coordenadas geográficas|Coordenadas]]: " .. s .. notes )
	local frame = mw.getCurrentFrame()
	frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' }	)
	return ''
end
--[[ Función auxiliar, sustituye a: {{coord/display/inline}} ]]
function displayinline (s, notes)
    return s .. notes    
end

--[[ Función auxiliar, que se utiliza en la detección de formato DMS]]
local dmsTest = function(first, second)
    local concatenated = first:upper() .. second:upper();
    
    if concatenated == "NE" or concatenated == "NW" or concatenated == "SE" or concatenated == "SW" or
        concatenated == "EN" or concatenated == "WN" or concatenated == "ES" or concatenated == "WS" or
        concatenated == "NO" or concatenated == "SO" or concatenated == "ON" or concatenated == "OS" then
        return true;
    end
    return false;
end

--[[
parseDec

Transforma al formato decimal la latitud y la longitud en la estructura que utiliza
el visualizador de coordenadas
]]
function parseDec( lat, long, format )
    local coordinateSpec = {}
    local errors = {}
    
    if long == "" or long == nil then
        return nil, {{"parseDec", "falta la longitud"}}
    end
    
    errors = validate( lat, nil, nil, long, nil, nil, 'parseDec', false );    
    coordinateSpec["dec-lat"]  = lat;
    coordinateSpec["dec-long"] = long;

    local mode = coordinates.determineMode( lat, long );
    coordinateSpec["dms-lat"]  = convert_dec2dms( lat, "N", "S", mode)  -- {{coord/dec2dms|{{{1}}}|N|S|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}
    coordinateSpec["dms-long"] = convert_dec2dms( long, "E", "O", mode)  -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}    
    
    if format ~= "" then
        coordinateSpec.default = format
    else
        coordinateSpec.default = "dec"  -- por defecto el formato dado
    end

    return coordinateSpec, errors
end

--[[ Función auxiliar, manejar argumentos opcionales. ]]
function optionalArg(arg, suplement)
    if arg ~= nil and arg ~= "" then
        return arg .. suplement
    end
    return ""
end

--[[
parseDMS

Transforma el formato de grados, minutos y segundos de la latitud y longitud 
en la estructura para ser utilizado en la visualización de coordenadas
]]
function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format )
    local coordinateSpec = {}
    local errors = {}
    
    lat_f = lat_f:upper();
    long_f = long_f:upper();
    
    -- Compruebe si se ha especificado hacia atrás la posición E, O 
    if lat_f == 'E' or lat_f == 'W' or lat_f == "O" then
        local t_d, t_m, t_s, t_f;
        t_d = lat_d;
        t_m = lat_m;
        t_s = lat_s;
        t_f = lat_f;
        lat_d = long_d;
        lat_m = long_m;
        lat_s = long_s;
        lat_f = long_f;
        long_d = t_d;
        long_m = t_m;
        long_s = t_s;
        long_f = t_f;
    end    
    
    errors = validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, 'parseDMS', true );
    if long_d == nil or long_d == "" then
        table.insert(errors, {"parseDMS", "falta la longitud" })
    end
    
    if lat_m == nil and lat_s == nil and long_m == nil and long_s == nil and #errors == 0 then 
        if math_mod._precision( lat_d ) > 0 or math_mod._precision( long_d ) > 0 then
            if lat_f:upper() == 'S' then 
                lat_d = '-' .. lat_d;
            end
            if long_f:upper() == 'W' or long_f:upper() == 'O' then 
                long_d = '-' .. long_d;
            end     
            
            return parseDec( lat_d, long_d, format );
        end        
    end   
    
    if long_f == "W" then
        long_f = "O"        -- dirección a mostrar, excepto con coordinateSpec["param"]
    end
    coordinateSpec["dms-lat"]  = lat_d.."°"..optionalArg(lat_m,"′") .. optionalArg(lat_s,"″") .. lat_f
    coordinateSpec["dms-long"] = long_d.."°"..optionalArg(long_m,"′") .. optionalArg(long_s,"″") .. long_f
    coordinateSpec["dec-lat"]  = convert_dms2dec(lat_f, lat_d, lat_m, lat_s) -- {{coord/dms2dec|{{{4}}}|{{{1}}}|0{{{2}}}|0{{{3}}}}}
    coordinateSpec["dec-long"] = convert_dms2dec(long_f, long_d, long_m, long_s) -- {{coord/dms2dec|{{{8}}}|{{{5}}}|0{{{6}}}|0{{{7}}}}}

    if format ~= "" then
        coordinateSpec.default = format
    else
        coordinateSpec.default = "dms"
    end   

    return coordinateSpec, errors
end

--[[
specPrinter

Salida de formateador. Toma la estructura generada por cualquiera parseDec
o parseDMS y da el formato para su inclusión en Wikipedia.
]]
function specPrinter(args, coordinateSpec)
    local uriComponents = coordinateSpec["param"]
    if uriComponents == "" then
        -- Devuelve error, nunca debe estar vacío o nulo
        return "ERROR: El parémetro está vacio"
    end
    if coordinateSpec["name"] ~= "" and coordinateSpec["name"] ~= nil then
        uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec["name"])
    end
    
    local geodmshtml = '<span class="geo-dms" title="Mapas, fotos y otros datos de ' .. coordinateSpec["dms-lat"] .. ' ' ..  coordinateSpec["dms-long"] .. '">'
             .. '<span class="latitude">'.. coordinateSpec["dms-lat"] .. ' </span>'
             .. '<span class="longitude">'..coordinateSpec["dms-long"] .. '</span>'
             .. '</span>'
    
    local geodechtml = '<span class="geo-dec" title="Mapas, fotos y otros datos de ' .. coordinateSpec["dec-lat"] .. ' ' ..  coordinateSpec["dec-long"] .. '">'
             .. '<span class="geo">'
             .. '<span class="latitude">' .. coordinateSpec["dec-lat"] .. ', </span>'
             .. '<span class="longitude">' .. coordinateSpec["dec-long"] .. '</span>'
             .. '</span></span>'

    local inner;
    inner = '<span class="' .. displayDefault(coordinateSpec["default"], "dms" ) .. '">' .. geodmshtml .. '</span>'
                .. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'
                .. '<span class="' .. displayDefault(coordinateSpec["default"], "dec" ) .. '">';

    if coordinateSpec["name"] == "" or coordinateSpec["name"] == nil then
        inner = inner .. geodechtml .. '</span>'
    else
        inner = inner .. '<span class="vcard">' .. geodechtml 
                .. '<span style="display:none">&#xfeff; (<span class="fn org">'
                .. coordinateSpec["name"] .. '</span>)</span></span></span>'
    end

    --return '<span class="plainlinksneverexpand">' 
    return '<span class="plainlinks nourlexpansion">'.. globalFrame:preprocess( 
        '[http://tools.wmflabs.org/geohack/geohack.php?language=es&pagename={{FULLPAGENAMEE}}&params=' ..
        uriComponents .. ' ' .. inner .. ']') .. '</span>'
end
--http://tools.wmflabs.org/geohack/geohack.php?language=fr&pagename=Communaut%C3%A9_forale_de_Navarre&params=42.816666666667_N_1.65_W_type:city_globe:earth&title=
--[[
Formatos de los mensajes de error autogenerados 
]]
function errorPrinter(errors)
    local result = ""
    for i,v in ipairs(errors) do
        local errorHTML = '<small><strong class="error">Coordenadas: ' .. v[2] .. '</strong></small>'
        result = result .. errorHTML .. "<br />"
    end
    return result
end

--[[
Determina la clase CSS requerida para mostrar las coordenadas

Por lo general, "geo-nodefault" está oculto por CSS, a menos que un usuario haya omitido esto
"default" es el modo que aparece por defecto, aunque el usuario puede especificarlo al llamar a la plantilla {{coord}}
modo es el modo de visualización (dec o DMS) que vamos a necesitar para determinar la clase css para

]]
function displayDefault(default, mode)
    if default == "" then
        default = "dec"
    end
    
    if default == mode then
        return "geo-default"
    else
        return "geo-nondefault"
    end
end

--[[ 
Comprueba los argumentos de entrada de coord para determinar el tipo de datos que se proporcionan
y luego hacer el proceso necesario.
]]
function formatTest(args)
    local result, errors;
    local primary = false;
    
    if args[1] == "" then
        -- Ninguna lat lógica
        return errorPrinter( {{"formatTest", "Falta la latitud"}} )
    elseif args[4] == "" and args[5] == "" and args[6] == "" then
        -- dec lógico
        result, errors = parseDec( formatPunt(args[1]), formatPunt(args[2]), args['format'] )
        if result == nil then
            return errorPrinter( errors );
        end              
        result.param    = table.concat( {formatPunt(args[1]), "_N_", formatPunt(args[2]), "_E_", args[3] } );
    elseif dmsTest(args[4], args[8]) then
        -- dms lógico
        result, errors = parseDMS( args[1], args[2], formatPunt(args[3]), args[4], 
            args[5], args[6], formatPunt(args[7]), args[8], args['format'] )
        result.param = table.concat( { args[1], args[2], formatPunt(args[3]), args[4], args[5],
            args[6], formatPunt(args[7]), formatLongW(args[8]), args[9] } , '_' );
        if args[10] ~= '' then
            table.insert( errors, { 'formatTest', 'Sobran parámetros' } );
        end        
    elseif dmsTest(args[3], args[6]) then
        -- dm lógico
        result, errors = parseDMS( args[1], formatPunt(args[2]), nil, args[3], 
            args[4], formatPunt(args[5]), nil, args[6], args['format'] )
        result.param = table.concat( { args[1], formatPunt(args[2]), args[3], args[4], formatPunt(args[5]),
            formatLongW(args[6]), args[7] } , '_' );
        if args[8] ~= '' then
            table.insert( errors, { 'formatTest', 'sobran paràmetros' } );
        end        
    elseif dmsTest(args[2], args[4]) then
        -- d lógico
        result, errors = parseDMS( formatPunt(args[1]), nil, nil, args[2], 
            formatPunt(args[3]), nil, nil, args[4], args['format'] )
        result.param = table.concat( { formatPunt(args[1]), args[2], formatPunt(args[3]), formatLongW(args[4]), args[5] } , '_' );
        if args[6] ~= '' then
            table.insert( errors, { 'formatTest', 'sobran parámetros' } );
        end        
    else
        -- Error
        return errorPrinter( {{"formatTest", "Formato no reconocido"}} )
    end
    result.name = args["name"] or args["nom"]
    
    local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type'}
    for _, v in ipairs( extra_param ) do
        if (args[v] or '') ~= '' then 
            table.insert( errors, {'formatTest', 'Parámetro: "' .. v .. '=" debería de ser"' .. v .. ':"' } );
        end
    end
    
    if #errors == 0 then
        return specPrinter( args, result )    
    else
        return specPrinter( args, result ) .. " " .. errorPrinter(errors) .. '[[Categoría:Wikipedia:Mantenimiento de la plantilla coord]]'; 
    end    
end
--[[ Función auxiliar para convertir la coma decimal en punto decimal ]]
function formatPunt(num)
    return mw.ustring.gsub(num, ",", ".")
end
--[[ Función auxilar para convertir longitud O a W ]]
function formatLongW(dir)
    if dir:upper() == "O" then
        return "W"
    end
    return dir
end

--[[ 
Función auxiliar, convierte la latitud o longitud al formato decimal
grados, minutos y segundos en formato basado en la precisión especificada.  
]]
function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
    local coord = tonumber(coordinate) or 0
    local postfix
    if coord >= 0 then
        postfix = firstPostfix
    else
        postfix = secondPostfix
    end

    precision = precision:lower();
    if precision == "dms" then
        return convert_dec2dms_dms( math.abs( coord ) ) .. postfix;
    elseif precision == "dm" then
        return convert_dec2dms_dm( math.abs( coord ) ) .. postfix;
    elseif precision == "d" then
        return convert_dec2dms_d( math.abs( coord ) ) .. postfix;
    end
end

--[[ Función auxiliar, convierte decimales a grados ]]
function convert_dec2dms_d(coordinate)
    local d = math_mod._round( coordinate, 0 ) .. "°"
    return d .. ""
end

--[[ Función auxiliar, convierte decimales a grados y minutos ]]
function convert_dec2dms_dm(coordinate)    
    coordinate = math_mod._round( coordinate * 60, 0 );
    local m = coordinate % 60;
    coordinate = math.floor( (coordinate - m) / 60 );
    local d = coordinate % 360 .."°"
    
    return d .. string.format( "%02d′", m )
end

--[[ Función auxiliar, convierte decimales a grados, minutos y segundos ]]
function convert_dec2dms_dms(coordinate)
    coordinate = math_mod._round( coordinate * 60 * 60, 0 );
    local s = coordinate % 60
    coordinate = math.floor( (coordinate - s) / 60 );
    local m = coordinate % 60
    coordinate = math.floor( (coordinate - m) / 60 );
    local d = coordinate % 360 .."°"

    return d .. string.format( "%02d′", m ) .. string.format( "%02d″", s )
end

--[[
Convierte el formato DMS con un N o E en coordenadas decimales
]]
function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str)
    local degrees = tonumber(degrees_str) or 0
    local minutes = tonumber(minutes_str) or 0
    local seconds = tonumber(seconds_str) or 0
    
    local factor
    direction = mw.ustring.gsub(direction, '^[ ]*(.-)[ ]*$', '%1');
    if direction == "N" or direction == "E" then
        factor = 1
    else
        factor = -1
    end
    
    local precision = 0
    if seconds_str ~= nil and seconds_str ~= '' then
        precision = 5 + math.max( math_mod._precision(seconds_str), 0 );
    elseif minutes_str ~= nil and minutes_str ~= '' then
        precision = 3 + math.max( math_mod._precision(minutes_str), 0 );
    else
        precision = math.max( math_mod._precision(degrees_str), 0 );
    end
    
    local decimal = factor * (degrees+(minutes+seconds/60)/60) 
    return string.format( "%." .. precision .. "f", decimal ) -- no en el número donde se basa la cadena.
end

--[[ 
Comprueba los valores de entrada y los errores fuera de rango.
]]
function validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, source, strong )
    local errors = {};
    lat_d = tonumber( lat_d ) or 0;
    lat_m = tonumber( lat_m ) or 0;
    lat_s = tonumber( lat_s ) or 0;
    long_d = tonumber( long_d ) or 0;
    long_m = tonumber( long_m ) or 0;
    long_s = tonumber( long_s ) or 0;

    if strong then
        if lat_d < 0 then
            table.insert(errors, {source, "Latitud negativa y con dirección N"})
        end
        if long_d < 0 then
            table.insert(errors, {source, "Longitud negativa y con dirección E"})
        end
        --[[ 
        #coordinates es contradictoria sobre si esto es un error. Si "globe" es
         especificado, no será error en esta condición, pero de lo contrario si lo será.
        
        solo por no deshabilitar esta comprobación.
        
        if long_d > 180 then
            table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"})
        end
        ]]
    end    
        
    if lat_d > 90 then
        table.insert(errors, {source, "grados de latitud > 90"})
    end
    if lat_d < -90 then
        table.insert(errors, {source, "grados de latitud < -90"})
    end
    if lat_m >= 60 then
        table.insert(errors, {source, "minutos de latitud >= 60"})
    end
    if lat_m < 0 then
        table.insert(errors, {source, "minutos de latitud < 0"})
    end
    if lat_s >= 60 then
        table.insert(errors, {source, "segundos de latitud >= 60"})
    end
    if lat_s < 0 then
        table.insert(errors, {source, "segundos de latitud < 0"})
    end
    if long_d >= 360 then
        table.insert(errors, {source, "grados de longitud >= 360"})
    end
    if long_d <= -360 then
        table.insert(errors, {source, "grados de longitud <= -360"})
    end
    if long_m >= 60 then
        table.insert(errors, {source, "minutos de longitud >= 60"})
    end
    if long_m < 0 then
        table.insert(errors, {source, "minutos de longitud < 0"})
    end
    if long_s >= 60 then
        table.insert(errors, {source, "segundos de longitud >= 60"})
    end
    if long_s < 0 then
        table.insert(errors, {source, "segundos de longitud < 0"})
    end
    
    return errors;
end

--[[
dec2dms

Función para permitir que las plantillas  llamen directamente a dec2dms.

Uso:
    {{ #Invoke:Coordenadas | dec2dms | <coordenadas_decimales> | <sufijo_positivo> | 
        <sufijo_negativo> | <precision> }}
    
Las <coordenas_decimales> se convierte al formato DMS. Si es positivo, el <sufijo_positivo>
se añade (N o E), si es negativo, el <sufijo_negativo> se adjunta si la
<precision> especificada puede ser 'D', 'DM' o 'DMS' para especificar el nivel de detalle
para utilizar.
]]
function coordinates.dec2dms(frame)
    globalFrame = frame
    local coordinate = frame.args[1]
    local firstPostfix = frame.args[2]
    local secondPostfix = frame.args[3]
    local precision = frame.args[4]

    return convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
end

--[[
Función auxiliar para determinar si se debe utilizar el formato D, DM, o DMS
en la función "precision" de la entrada decimal
.
]]
function coordinates.determineMode( value1, value2 )
    local precision = math.max( math_mod._precision( value1 ), math_mod._precision( value2 ) );
    if precision <= 0 then
        return 'd'
    elseif precision <= 2 then
        return 'dm';
    else
        return 'dms';
    end
end        

--[[
dms2dec

Wrapper para permitir que las plantillas llamen directamente a dms2dec.

Uso:
    {{ #Invoke:Coordenadas | dms2dec | <indicador de dirección> | grados | 
        minutos | segundos }}
    
Convierte los valores de formato DMS  especificados en grados, minutos y segundos en grados decimales.
El indicador de dirección puede ser N, S, E, O , y determina si la salida es
positiva (es decir, N o E) o negativa (es decir, S o O).
]]
function coordinates.dms2dec(frame)
    globalFrame = frame
    local direction = frame.args[1]
    local degrees = frame.args[2]
    local minutes = frame.args[3]
    local seconds = frame.args[4]

    return convert_dms2dec(direction, degrees, minutes, seconds)
end

--[[
coord

Principal punto de entrada para la función Lua para reemplazar {{coord}}

Uso:
    {{ #Invoke:Coordenadas | coord }}
    {{ #Invoke:Coordenadas | coord | lat | long }}
    {{ #Invoke:Coordenadas | coord | lat | <lat_dirección> | <long> | <long_dirección> }}
    ...
    
    Consulte la página de documentación {{coord}} para muchos parámetros adicionales y
    opciones de configuración.
    
Nota: Esta función proporciona los elementos de presentación visual de {{coord}}. 
Para poder cargar coordenadas en la base de datos, el parser function (analizador de función) {{#coordinates:}} también debe ser llamado, esto se realiza automáticamente en la versión de {{Coord}} en módulo Lua.
]]
function coordinates.coord(frame)
    globalFrame = frame
    local args = frame.args
    if args[1] == nil then
        local pFrame = frame:getParent();
        args = pFrame.args;

        for k,v in pairs( frame.args ) do
            args[k] = v;
        end
    end
    
    for i=1,10 do 
        if args[i] == nil then 
            args[i] = ""
        else
            args[i] = args[i]:match( '^%s*(.-)%s*$' );  --elimina espacios en blanco
        end        
    end
    args['format'] = args['formato'] or args['format'] or '';
    
    local contents = formatTest(args)
    local Notes = args.notes or ""
    local Display = string.lower(args.display or "inline")
    if Display == '' then
        Display = 'inline';
    end
    
    local text = ''
    if string.find( Display, 'inline' ) ~= nil or Display == 'i' or 
            Display == 'it' or Display == 'ti' then
        text = displayinline(contents, Notes)
    end
    if string.find( Display, 'title' ) ~= nil or Display == 't' or 
            Display == 'it' or Display == 'ti' then
        text = text .. displaytitle(contents, Notes)
    end
    return text
    end
function coordinates.coord2(frame)
        globalFrame = frame.args.gf 
        local origArgs = {}
 
             if frame == mw.getCurrentFrame() then
                 origArgs = frame:getParent().args
             else        
                 origArgs = frame.args
             end
 
         args = {}
            for k, v in pairs(origArgs) do                 
                     args[k] = v                    
            end
    for i=1,10 do 
        if args[i] == nil then 
            args[i] = ""
        else
            args[i] = args[i]:match( '^%s*(.-)%s*$' );  --elimina espacios en blanco
        end        
    end
    args['format'] = args['format'] or '';
    
    local contents = formatTest(args)
    local Notes = args.notes or ""
    local Display = string.lower(args.display or "inline")
    if Display == '' then
        Display = 'inline';
    end
    
    local text = ''
    if string.find( Display, 'inline' ) ~= nil or Display == 'i' or 
            Display == 'it' or Display == 'ti' then
        text = displayinline(contents, Notes)
    end
    if string.find( Display, 'title' ) ~= nil or Display == 't' or 
            Display == 'it' or Display == 'ti' then
        text = text .. displaytitle(contents, Notes)
    end
    return text
end

return coordinates