Va ô cuntinutu

Mòdulu:Bozza/GianAntonucci/PusizzioniNtâMappa

Dâ Wikipedia, la nciclupidìa lìbbira.

Po' criari la ducumintazzioni di stu mòdulu nta Mòdulu:Bozza/GianAntonucci/PusizzioniNtâMappa/doc

-- Mòdulu:Bozza/GianAntonucci/PusizzioniNtâMappa
-- Location map module for showing city positions on regional/country maps
-- Modular version - map data stored in separate /data/ submodules

local p = {}

-- Helper function to extract args from frame (reused from Wikidata module)
local function getArgs(frame, ...)
    local paramNames = {...}
    local args = {}
    
    -- Check if we have any direct parameters
    local hasDirectParams = false
    for k, v in pairs(frame.args) do
        if v and v ~= '' then
            hasDirectParams = true
            break
        end
    end
    
    if hasDirectParams then
        -- Use frame args, handling empty strings properly
        for k, v in pairs(frame.args) do
            if v ~= '' then
                args[k] = v
            end
        end
    else
        -- Use parent args
        for k, v in pairs(frame:getParent().args) do
            if v ~= '' then
                args[k] = v
            end
        end
    end
    
    return args
end

-- Helper function to get map data
local function getMapData(mapName)
    if not mapName then
        return nil
    end
    
    -- Try to load from dati submodule with exact name
    local success, mapData = pcall(function()
        return require('Mòdulu:Bozza/GianAntonucci/PusizzioniNtâMappa/dati/' .. mapName)
    end)
    
    if success and mapData then
        return mapData
    end
    
    -- Try with lowercase
    success, mapData = pcall(function()
        return require('Mòdulu:Bozza/GianAntonucci/PusizzioniNtâMappa/dati/' .. mw.ustring.lower(mapName))
    end)
    
    if success and mapData then
        return mapData
    end
    
    -- Try with uppercase first letter
    success, mapData = pcall(function()
        local name = mw.ustring.lower(mapName)
        name = mw.ustring.upper(mw.ustring.sub(name, 1, 1)) .. mw.ustring.sub(name, 2)
        return require('Mòdulu:Bozza/GianAntonucci/PusizzioniNtâMappa/dati/' .. name)
    end)
    
    if success and mapData then
        return mapData
    end
    
    return nil
end

-- Calculate pixel position from coordinates (FIXED)
local function getPixelPosition(lat, lon, mapData, displayWidth)
    local top = mapData.top
    local bottom = mapData.bottom
    local left = mapData.left
    local right = mapData.right
    
    -- Calculate relative position (0 to 1)
    local xRatio = (lon - left) / (right - left)
    local yRatio = (top - lat) / (top - bottom)
    
    -- Ensure values are within bounds
    xRatio = math.max(0, math.min(1, xRatio))
    yRatio = math.max(0, math.min(1, yRatio))
    
    -- Calculate display height maintaining aspect ratio
    local aspectRatio = mapData.height / mapData.width
    local displayHeight = displayWidth * aspectRatio
    
    -- Convert to pixels (FIXED: using correct dimensions)
    local x = math.floor(xRatio * displayWidth)
    local y = math.floor(yRatio * displayHeight)
    
    return x, y, displayHeight
end

-- Parse coordinates from various formats
local function parseCoordinates(coordStr)
    if not coordStr then
        return nil, nil
    end
    
    -- Remove any leading/trailing whitespace
    coordStr = mw.text.trim(coordStr)
    
    -- Try to parse decimal coordinates (e.g., "45.5, 9.2")
    local lat, lon = coordStr:match('([%-%.%d]+)%s*,%s*([%-%.%d]+)')
    if lat and lon then
        return tonumber(lat), tonumber(lon)
    end
    
    -- Try to parse DMS format (e.g., "45°30'0"N 9°12'0"E")
    local latDeg, latMin, latSec, latDir, lonDeg, lonMin, lonSec, lonDir = 
        coordStr:match('(%d+)°(%d+)[\'′](%d*)[\"″]?([NS])%s+(%d+)°(%d+)[\'′](%d*)[\"″]?([EW])')
    
    if latDeg then
        lat = tonumber(latDeg) + tonumber(latMin)/60 + (tonumber(latSec) or 0)/3600
        if latDir == 'S' then lat = -lat end
        
        lon = tonumber(lonDeg) + tonumber(lonMin)/60 + (tonumber(lonSec) or 0)/3600
        if lonDir == 'W' then lon = -lon end
        
        return lat, lon
    end
    
    return nil, nil
end

-- Get coordinates and region from Wikidata efficiently
local function getWikidataInfo(wikidataId)
    local info = {
        lat = nil,
        lon = nil,
        region = nil
    }
    
    -- Use the more efficient functions from the Wikidata module if available
    local wikidataModule = require('Mòdulu:Bozza/GianAntonucci/Wikidata')
    
    if wikidataModule then
        -- Get coordinates
        local coordsStr = wikidataModule._getProperty({
            property = 'P625',
            from = wikidataId,
            formatting = 'raw'
        })
        if coordsStr then
            info.lat, info.lon = parseCoordinates(coordsStr)
        end
        
        -- Get administrative divisions for map detection
        local divisions = wikidataModule._getProperty({
            property = 'P131',
            from = wikidataId,
            formatting = 'label',
            separator = '|'
        })
        
        if divisions then
            -- Check each division to find a matching map
            for division in divisions:gmatch('[^|]+') do
                division = mw.text.trim(division)
                if getMapData(division) then
                    info.region = division
                    break
                end
            end
        end
    else
        -- Fallback to direct Wikibase calls if module not available
        local entity = wikidataId and mw.wikibase.getEntity(wikidataId) or mw.wikibase.getEntity()
        
        if entity and entity.claims then
            -- Get coordinates
            if entity.claims.P625 then
                local coords = entity.claims.P625[1].mainsnak.datavalue.value
                info.lat = coords.latitude
                info.lon = coords.longitude
            end
            
            -- Get region
            if entity.claims.P131 then
                for _, claim in ipairs(entity.claims.P131) do
                    if claim.mainsnak.datavalue then
                        local divisionId = 'Q' .. claim.mainsnak.datavalue.value['numeric-id']
                        local divisionLabel = mw.wikibase.getLabel(divisionId)
                        
                        if divisionLabel and getMapData(divisionLabel) then
                            info.region = divisionLabel
                            break
                        end
                    end
                end
            end
        end
    end
    
    return info
end

-- Main function to generate the map
function p._main(args)
    local mapName = args.map or args[1]
    local lat = tonumber(args.lat or args.latitude)
    local lon = tonumber(args.lon or args.longitude or args.long)
    local fallbackMap = args.fallback or 'Sicilia'  -- Changed default fallback to Sicilia
    
    -- Try to parse coordinates if not already numbers
    if not lat or not lon then
        local coordStr = args.coordinates or args.coord
        if coordStr then
            lat, lon = parseCoordinates(coordStr)
        end
    end
    
    -- Get info from Wikidata if needed (single call)
    if not lat or not lon or not mapName then
        local wikidataId = args.wikidata or args.from
        local wikidataInfo = getWikidataInfo(wikidataId)
        
        if not lat then lat = wikidataInfo.lat end
        if not lon then lon = wikidataInfo.lon end
        if not mapName then mapName = wikidataInfo.region end
    end
    
    if not lat or not lon then
        return '<div class="error" style="color:red; word-wrap:break-word;">Cuurdinati mancanti</div>'
    end
    
    -- DEBUG: Show coordinates and map boundaries
    -- return string.format('DEBUG: lat=%s, lon=%s<br>Map: %s<br>Bounds: N=%s, S=%s, W=%s, E=%s', 
    --     lat, lon, mapName or 'none', 
    --     mapData.top, mapData.bottom, mapData.left, mapData.right)
    
    -- Get map data
    local mapData = getMapData(mapName)
    if not mapData then
        -- Try fallback if specified
        if fallbackMap and fallbackMap ~= 'none' then
            mapData = getMapData(fallbackMap)
            if mapData then
                mapName = fallbackMap
            else
                -- Fallback also failed - shorter error message
                return '<div class="error" style="color:red; word-wrap:break-word;">Mappa nun truvata:<br/>' .. 
                       (mapName or 'nenti') .. ', ' .. fallbackMap .. '</div>'
            end
        else
            -- Shorter error message
            return '<div class="error" style="color:red; word-wrap:break-word;">Mappa "' .. 
                   (mapName or '?') .. '" nun truvata</div>'
        end
    end
    
    -- Build the map HTML
    local width = tonumber(args.width) or mapData.width or 280
    
    -- Get label - try provided label, then Wikidata label, then page title
    local label = args.label
    if not label or label == '' then
        local wikidataId = args.wikidata or args.from
        if wikidataId then
            label = mw.wikibase.getLabel(wikidataId)
        end
    end
    if not label or label == '' then
        label = args.name or mw.title.getCurrentTitle().text
    end
    
    local labelPos = args.position or args.label_position or 'right'
    local markerColor = args.marker_color or args.marker or 'red'
    local markerSize = tonumber(args.marker_size) or 8
    local showLabel = args.label ~= 'none' and args.show_label ~= 'no'
    
    -- Calculate marker position (with fixed height calculation)
    local x, y, displayHeight = getPixelPosition(lat, lon, mapData, width)
    
    -- Build output - return just the image path for the template to handle
    -- Check if we're in "simple" mode (just return image name)
    if args.simple == 'yes' then
        return mapData.image
    end
    
    -- Otherwise build full HTML output (for direct module use)
    local output = {}
    
    -- Container div with inline-block to contain the image
    table.insert(output, '<div class="locmap" style="position:relative; display:inline-block; line-height:0;">')
    
    -- Add the image using HTML img tag (more reliable than wikitext in this context)
    table.insert(output, '[[File:' .. mapData.image .. '|' .. width .. 'px|link=]]')
    
    -- Overlay container for marker and label
    table.insert(output, '<div style="position:absolute; top:0; left:0; width:' .. width .. 'px; height:' .. displayHeight .. 'px;">')
    
    -- Add marker
    local markerOffset = math.floor(markerSize / 2)
    table.insert(output, '<div style="position:absolute; left:' .. (x - markerOffset) .. 'px; top:' .. (y - markerOffset) .. 
        'px; width:' .. markerSize .. 'px; height:' .. markerSize .. 'px; background-color:' .. markerColor .. 
        '; border:1px solid dark' .. markerColor .. '; border-radius:50%;" title="' .. label .. '"></div>')
    
    -- Add label if requested
    if showLabel then
        local labelStyle = 'position:absolute; font-size:' .. (args.label_size or '90%') .. '; line-height:110%;'
        
        -- Adjust positioning to be next to the marker, not on top of it
        if labelPos == 'left' then
            -- Position to the left of the marker
            local rightPos = width - x + markerSize + 1
            labelStyle = labelStyle .. ' right:' .. math.max(5, rightPos) .. 'px; top:' .. (y - markerOffset) .. 'px; text-align:right;'
        elseif labelPos == 'top' then
            -- Position above the marker
            labelStyle = labelStyle .. ' left:' .. (x - 30) .. 'px; bottom:' .. (displayHeight - y + markerSize + 1) .. 'px; text-align:center; width:60px;'
        elseif labelPos == 'bottom' then
            -- Position below the marker
            labelStyle = labelStyle .. ' left:' .. (x - 30) .. 'px; top:' .. (y + markerSize + 1) .. 'px; text-align:center; width:60px;'
        else -- right (default)
            -- Position to the right of the marker with minimal spacing
            local leftPos = x + markerSize + 1
            -- Check if label would go outside bounds
            if leftPos + 50 > width then
                -- If too close to edge, position to the left instead
                labelStyle = labelStyle .. ' right:' .. (width - x + markerSize + 1) .. 'px; top:' .. (y - markerOffset) .. 'px; text-align:right;'
            else
                labelStyle = labelStyle .. ' left:' .. leftPos .. 'px; top:' .. (y - markerOffset) .. 'px;'
            end
        end
        
        table.insert(output, '<div style="' .. labelStyle .. '">' .. label .. '</div>')
    end
    
    -- Close overlay container
    table.insert(output, '</div>')
    
    -- Close main container
    table.insert(output, '</div>')
    
    -- Add caption if provided
    if args.caption then
        table.insert(output, '<div style="text-align:center; font-size:90%; margin-top:5px;">' .. args.caption .. '</div>')
    end
    
    return table.concat(output)
end

-- Template entry point
function p.main(frame)
    local args = getArgs(frame)
    local output = p._main(args)
    -- Preprocess the output to ensure wikitext is parsed
    return frame:preprocess(output)
end

return p