Modul:Koordinaty

Z Wikipedije

Dokumentacija za toś ten modul dajo se na Modul:Koordinaty/Dokumentacija napóraś

local m = {}

local geoformatdata = {
    supportedFormats = {
        { prec = "10st", precision = 10.00000000000000000000, dms = false, secondsFormat = nil,      format = "%0.0f%s" },
        { prec = "st",   precision =  1.00000000000000000000, dms = false, secondsFormat = nil,      format = "%0.0f%s" },
        { prec = "1",    precision =  0.10000000000000000000, dms = false, secondsFormat = nil,      format = "%0.1f%s" },
        { prec = "min",  precision =  0.01666666666666670000, dms = true,  secondsFormat = "%02.0f", format = "%0.0f%s%02.0f%s" },
        { prec = "2",    precision =  0.01000000000000000000, dms = false, secondsFormat = nil,      format = "%0.2f%s" },
        { prec = "3",    precision =  0.00100000000000000000, dms = false, secondsFormat = nil,      format = "%0.3f%s" },
        { prec = "sek",  precision =  0.00027777777777777800, dms = true,  secondsFormat = "%02.0f", format = "%0.0f%s%02.0f%s%02.0f%s" },
        { prec = "4",    precision =  0.00010000000000000000, dms = false, secondsFormat = nil,      format = "%0.4f%s" },
        { prec = "sek+", precision =  0.00002777777777777780, dms = true,  secondsFormat = "%04.1f", format = "%0.0f%s%02.0f%s%04.1f%s" },
        { prec = "5",    precision =  0.00001000000000000000, dms = false, secondsFormat = nil,      format = "%0.5f%s" },
        { prec = "sek2", precision =  0.00000277777777777778, dms = true,  secondsFormat = "%05.2f", format = "%0.0f%s%02.0f%s%05.2f%s" },
        { prec = "6",    precision =  0.00000100000000000000, dms = false, secondsFormat = nil,      format = "%0.6f%s" },
        { prec = "sek3", precision =  0.00000027777777777778, dms = true,  secondsFormat = "%06.3f", format = "%0.0f%s%02.0f%s%06.3f%s" },
        { prec = "7",    precision =  0.00000010000000000000, dms = false, secondsFormat = nil,      format = "%0.7f%s" },
        { prec = "sek4", precision =  0.00000002777777777778, dms = true,  secondsFormat = "%07.4f", format = "%0.0f%s%02.0f%s%07.4f%s" },
    },

    displayGlobes = {
        earth     = "EW",
        moon      = "EW",
        mercury   = "W",
        mars      = "W",
        phobos    = "W",
        deimos    = "W",
        ganymede  = "W",
        callisto  = "W",
        io        = "W",
        europa    = "W",
        mimas     = "W",
        enceladus = "W",
        tethys    = "W",
        dione     = "W",
        rhea      = "W",
        titan     = "W",
        lapetus   = "W",
        phoebe    = "W",
        venus     = "E",
        ceres     = "E",
        vesta     = "E",
        miranda   = "E",
        ariel     = "E",
        umbriel   = "E",
        titania   = "E",
        oberon    = "E",
        triton    = "E",
        pluto     = "E",
    },

    latitudeLinkMarkers   = { degree="_", minute="_", second="_", positivePrefix="", positiveSuffix="N", negativePrefix="", negativeSuffix="S", },
    longitudeLinkMarkers  = { degree="_", minute="_", second="_", positivePrefix="", positiveSuffix="E", negativePrefix="", negativeSuffix="W", },
    latitudeGlobeMarkers  = { degree="°", minute="′", second="″", positivePrefix="", positiveSuffix="N", negativePrefix="", negativeSuffix="S", },
    longitudeGlobeMarkers = { degree="°", minute="′", second="″", positivePrefix="", positiveSuffix="E", negativePrefix="", negativeSuffix="W", },

    displayDecimalSeparator = ",",
    coordinatesSeparator = "\194\160",
    topPrefix = "Na kórtach: ",
    documentationSubpage = "wopis",

    geohack_link = "http://toolserver.org/~geohack/geohack.php?language=dsb&pagename=%s&params=%s",
    geohack_hint = "Kórty, satelitowe wobraze a druge informacije wó geografiskich koordinatach %s %s",

    -- template API data
    apiTemplateName = "pśedłoga",
    apiMicroName = "mikro",
    apiAutoName = "awto",
    apiLatitude = "šyrina",
    apiLongitude = "dlinina",
    
    argLocation = "póstaj",
    valLocationTop = "górjejce",
    valLocationInline = "w tekśće",
    valLocationTopAndInline = "w tekśće a górjejce",
    
    argPrecision = "dokładność",
    valPrecisionDecimal = { "1", "2", "3", "4", "5", "6", "7", },
    valPrecisionDMS = { "st", "min", "sek", "sek+", },
    valPrecisionAutoDecimal = "decimalnje",
    valPrecisionAutoDMS = "kutowo",
    
    argLink = "zwězuj",
    valLinkYes = "jo",
    valLinkNo = "ně",
    
    argName = "mě",

    -- categories
    errorCategory = "[[Kategorija:Boki ze zmólkami w parameterach koordinatow]]",

    -- error messages
    errorTooManyPositionalArguments = "Za dużo parametrów", 
    errorExpectedIntegerDegree = "Oczekiwana liczba stopni bez kropki dziesiętnej jeśli podawane są minuty (%s°%s')",
    errorInvalidMinutes = "Wartość minut jest nieprawidłowa (%s°%s')",
    errorExpectedIntegerMinutes = "Oczekiwana liczba minut bez kropki dziesiętnej jeśli podawane są sekundy (%s°%s'%s″)",
    errorInvalidSeconds = "Wartość sekund jest nieprawidłowa (%s°%s'%s″)",
    errorInvalidPositionalArguments = "Nieprawidłowe parametry",
    errorExpectedNonNegativeLatitude = "Oczekiwana nieujemna wartość szerokości geograficznej: %f",
    errorLatitudeOutOfRange = "Przekroczony zakres szerokości geograficznej (%f)",
    errorExpectedNonNegativeLongitude = "Oczekiwana nieujemna wartość długości geograficznej: %f",
    errorLongitudeOutOfRange = "Przekroczony zakres długości geograficznej (%f)",
    errorUnrecognizedLinkOption = "Niedozwolona wartość parametru ''link'': %s",
}

local function create()
    
    -- initialize default data
    local result = {
        latitude  = 0,
        longitude = 0,
        precision = 1,
        params    = nil,
        inline    = false,
        top       = false,
        link      = true,
    }

    function result:parseCoordinates(args)

        local function isInt(s)
            -- up to 3 digits is enough for coordinates
            return s:match"^-?%d%d?%d?$"
        end

        local lang = mw.getContentLanguage()

        local function parseTypes()
            local types = {}
            for i = 1, 9 do
                local arg = mw.text.trim(args[i] or "")
                if #arg==0 then
                    table.insert(types, "_")
                elseif arg == "N" or arg=="E" or arg=="S" or arg=="W" then
                    table.insert(types, arg)
                elseif lang:parseFormattedNumber(arg) then
                    local scientific = arg:match"[eE]"
                    table.insert(types, scientific and "X" or "#")
                else
                    table.insert(types, "X")
                end
            end
 
            return table.concat(types, "")
        end
 
        local function calculateDecimalPrecision(s)
            local s1 = string.gsub(s,"%d","0")
            local s2 = string.gsub(s1,"^-","0")
            local s3 = string.gsub(s2,"0$","1")
            local result = lang:parseFormattedNumber(s3)
            return result > 0 and result or 1.0
        end

        local function selectAutoPrecision(p1, p2)
            local dms = nil
            if (args[geoformatdata.argPrecision] == geoformatdata.valPrecisionAutoDecimal)  then
                dms = false
            elseif not args[geoformatdata.argPrecision] or (args[geoformatdata.argPrecision] == geoformatdata.valPrecisionAutoDMS) then
                dms = true
            else
            	-- precision is selected explicit in the parameter
                return
            end

            -- select automatic precision
            local precision = p1 < p2 and p1 or p2
 
            -- find best DMS or decimal precision
            if precision < 1 then
                local eps = precision / 1024
                for i,v in ipairs(geoformatdata.supportedFormats) do
                    if (v.dms == dms) and ((v.precision - precision) < eps) then
                        precision = v.precision
                        break
                    end
                end
            end

            self.precision = precision
        end
        
        local function parseAngle(index, extra)
 
            local degree = mw.text.trim(args[index])
            local result = lang:parseFormattedNumber(degree)
            if extra == 0 then
                return true, result, calculateDecimalPrecision(degree)
            end
 
            local minutes = mw.text.trim(args[index+1])
            if not isInt(degree) then
                return false, string.format(geoformatdata.errorExpectedIntegerDegree, degree, minutes)
            end
 
            local precision = isInt(minutes) and 0.01666666666666670000 or 0.00027777777777777800
 
            local value = lang:parseFormattedNumber(minutes)
            if value < 0 or value >= 60 then
                return false, string.format(geoformatdata.errorInvalidMinutes, degree, minutes)
            end
 
            if result < 0 then
                result = result * 60 - value
            else
                result = result * 60 + value
            end
 
            if extra == 1 then
                return true, result / 60, precision
            end
 
            local seconds = mw.text.trim(args[index+2])
            if not isInt(minutes) then
                return false, string.format(geoformatdata.errorExpectedIntegerMinutes, degree, minutes, seconds)
            end
 
            precision = 0.00027777777777777800 * calculateDecimalPrecision(seconds)
            local value = lang:parseFormattedNumber(seconds)
            if value < 0 or value >= 60 then
                return false, string.format(geoformatdata.errorInvalidSeconds, degree, minutes, seconds)
            end
 
            if result < 0 then
                result = result * 60 - value
            else
                result = result * 60 + value
            end
 
            return true, result / 3600, precision
        end
        
        local function analyzeAngle(degree, minutes, seconds)
            local result = lang:parseFormattedNumber(degree)
            if not result then
                return false, geoformatdata.errorInvalidPositionalArguments
            end
 
            if not string.match(degree, "^%d+$") then
                if (#minutes > 0) or (#seconds > 0) then
                    -- expected empty minutes and empty seconds if float degree is given
                    return false, geoformatdata.errorInvalidPositionalArguments
                end
 
                return true, result, calculateDecimalPrecision(degree)
            end
 
            if #minutes == 0 then
                if #seconds > 0 then
                    -- expected empty seconds if minute is not given
                    return false, geoformatdata.errorInvalidPositionalArguments
                end
            
                return true, result, calculateDecimalPrecision(degree)
            end
 
            local minute = lang:parseFormattedNumber(minutes)
            if not minute or (minute >= 60) then
                return false, string.format(geoformatdata.errorInvalidMinutes, degree, minutes)
            end
 
            result = result * 60 + minute
            if not string.match(minutes, "^%d+$") then
                if #seconds > 0 then
                    return false, string.format(geoformatdata.errorExpectedIntegerMinutes, degree, minutes, seconds)
                end

                return true, result/60, 0.00027777777777777800
            end
 
            if #seconds == 0 then
                return true, result/60, 0.01666666666666670000
            end
 
            local second = lang:parseFormattedNumber(seconds)
            if not second or (second >= 60) then
                return false, string.format(geoformatdata.errorInvalidSeconds, degree, minutes, seconds)
            end

            result = result*60 + second
            return true, result/3600, calculateDecimalPrecision(seconds)*0.00027777777777777800
        end
 
        assert(args, "Missing template arguments")
        if not args[1] then
            -- display nothing if no positional arguments are provided
            return false, nil
        end
 
        if args[10] then
            return false, geoformatdata.errorTooManyPositionalArguments
        end

        local types = parseTypes()

        local function parseSimpleText()
            local arg = mw.text.trim(args[1])
            if types == "XX_______" then
                self.params = mw.text.trim(args[2])
            end

            local d1, m1, s1, h1, d2, m2, s2, h2 = mw.ustring.match(arg, "^([0-9,.]+)[°_]?%s*([0-9,.]*)['′_]?%s*([0-9,.]*)[\"″_]?%s*([NSEW])[,;]?%s+([0-9,.]+)[°_]?%s*([0-9,.]*)['′_]?%s*([0-9,.]*)[\"″_]?%s*([EWNS])$")
            if d1 then
                if (((h1 == "N") or (h1 == "S")) and ((h2 == "N") or (h2 == "S"))) or (((h1 == "E") or (h1 == "W")) and ((h2 == "E") or (h2 == "W"))) then
                    return geoformatdata.errorInvalidPositionalArguments
                end

                local status1, v1, p1 = analyzeAngle(d1, m1, s1)
                if not status1 then
                    return v1
                end

                local status2, v2, p2 = analyzeAngle(d2, m2, s2)
                if not status2 then
                    return v2
                end

                if (h1 == "S") or (h1 == "W") then
                    v1 = -v1;
                end
                if (h2 == "S") or (h2 == "W") then
                    v2 = -v2;
                end

                self.latitude  = ((h1 == "N") or (h1 == "S")) and v1 or v2
                self.longitude = ((h1 == "E") or (h1 == "W")) and v1 or v2
                selectAutoPrecision(p1, p2)
                return nil
            end

            local lat, lon = string.match(arg, "^(-?[0-9.,]+)%s+(-?[0-9.,]+)$")
            if lat then
                local latitude = lang:parseFormattedNumber(lat)
                local longitude = lang:parseFormattedNumber(lon)
                if latitude and longitude then
                    self.latitude = latitude
                    self.longitude = longitude
                    selectAutoPrecision(calculateDecimalPrecision(lat), calculateDecimalPrecision(lon))
                    return nil
                end
            end

            return geoformatdata.errorInvalidPositionalArguments
        end

        if (types == "X________") or (types == "XX_______") then
            local errorMessage = parseSimpleText()
            if errorMessage then
                return false, errorMessage
            end
        else
            local mapping = mw.loadData("Module:Koordinaty/parserData")[types]
            if not mapping then
                return false, geoformatdata.errorInvalidPositionalArguments
            end
     
            if mapping[7] ~= 0 then
                self.params = mw.text.trim(args[mapping[7]])
            end
            
            local status1, latitude, latPrecision = parseAngle(mapping[1], mapping[2])
            if not status1 then
                return false, latitude
            end
     
            if mapping[3] ~= 0 then
                assert(mapping[3] == 1 or mapping[3] == -1, "Invalid adjust mode: " .. mapping[3]);
                if latitude < 0 then
                    return false, string.format(geoformatdata.errorExpectedNonNegativeLatitude, latitude)
                end
                latitude = mapping[3] * latitude
            end
     
            local status2, longitude, lonPrecision = parseAngle(mapping[4], mapping[5])
            if not status2 then
                return false, longitude
            end
     
            if mapping[6] ~= 0 then
                assert(mapping[6] == 1 or mapping[6] == -1, "Invalid adjust mode: " .. mapping[6]);
                if longitude < 0 then
                    return false, string.format(geoformatdata.errorExpectedNonNegativeLongitude, longitude)
                end
                longitude = mapping[6] * longitude
            end
            
            self.latitude  = latitude
            self.longitude = longitude
            selectAutoPrecision(latPrecision, lonPrecision)
        end
    
        if self.latitude < -90 or self.latitude > 90 then
            return false, string.format(geoformatdata.errorLatitudeOutOfRange, self.latitude)
        end
 
        if self.longitude < -360 or self.longitude > 360 then
            return false, string.format(geoformatdata.errorLongitudeOutOfRange, self.longitude)
        end
        
        return true, nil
    end
    
    function result:normalize()
        assert(self,"Did you use '.' instead of ':' while calling the function?")
        local mode = false
        if self.params then
            for i, v in ipairs(mw.text.split( self.params, '_', true )) do
                if mode then
                    -- more than one globe, display as given
                    return
                end
                
                local globe = string.match(v, "^globe:(%a+)$")
                if globe then
                    mode = geoformatdata.displayGlobes[string.lower(globe)]
                    if not mode then
                        -- unrecognized display as given
                        return
                    end
                end
            end
        end
    
        if mode == "?" then
            -- unrecognized left as given
        elseif mode == "W" then
            if self.longitude > 0 then
                self.longitude = self.longitude - 360
            end
        elseif mode == "E" then
            if self.longitude < 0 then
                self.longitude = self.longitude + 360
            end
        elseif self.longitude < -180 then
            self.longitude = self.longitude + 360
        elseif self.longitude > 180 then
            self.longitude = self.longitude - 360
        end
    end
    
    function result:parseOptions(args)
        -- TODO process notation in conjuction with precision
        local precision = args[geoformatdata.argPrecision]
        if precision and (precision ~= geoformatdata.valPrecisionAutoDecimal) and (precision ~= geoformatdata.valPrecisionAutoDMS) then
            self.precision = precision
        end
        
        self.name = args[geoformatdata.argName]
       
        local link = args[geoformatdata.argLink]
        if link == geoformatdata.valLinkYes then
            self.link = true
        elseif link == geoformatdata.valLinkNo then
            self.link = false
        elseif link then
            return false, string.format(geoformatdata.errorUnrecognizedLinkOption, link)
        else -- default is yes
            self.link = true
        end

        local location = args[geoformatdata.argLocation]
        if location == geoformatdata.valLocationTop then
            self.top = true
            self.inline = false
        elseif location == geoformatdata.valLocationInline then
            self.top = false
            self.inline = true
        elseif location == geoformatdata.valLocationTopAndInline then
            self.top = true
            self.inline = true
        elseif location then
            return false, string.format(geoformatdata.errorUnrecognizedLocationOption, location)
        elseif mw.title.getCurrentTitle().isTalkPage then
            -- an exception for talk pages
            self.top = false
            self.inline = true
        else -- default if not given
            self.top = true
            self.inline = false
        end

        return true, nil
    end
 
    function result:display(inlinePrefix)
        
        local function selectFormat(precision)
            local supportedFormats = geoformatdata.supportedFormats
            local precisionType = type(precision)
            if precisionType == "string" then
                -- find wikipedia template precision
                for i, v in ipairs(supportedFormats) do
                    if (precision == v.prec) then
                        return true, v
                    end
                end
            elseif precisionType == "number" then
                -- find wikidata precision
                for i, v in ipairs(supportedFormats) do
                    local prec = v.precision
                    local eps = prec / 64
                    local minPrec = prec - eps
                    local maxPrec = prec + eps
                    if (minPrec < precision) and (precision < maxPrec) then
                        return true, v
                    end
                end
            end

            -- use the last one with highest precision
            return false, supportedFormats[#supportedFormats]
        end
    
        local function formatAngle(value, format, markers, decimalSeparator)
            assert(type(value) == "number")
            local prefix = value < 0 and markers.negativePrefix or markers.positivePrefix
            local suffix = value < 0 and markers.negativeSuffix or markers.positiveSuffix
        
            value = math.abs(value)
        
            local result = nil
        
            if not format.dms then
                -- format decimal value
                if format.precision > 1 then
                    -- round the value
                    value = math.floor(value / format.precision) * format.precision
                end
        
                result = string.format(format.format, value, markers.degree)
            else
                -- format dms value
                local angle   = math.floor(value)
                local minutes = math.floor((value - angle) * 60)
                local seconds = tonumber(string.format(format.secondsFormat, (value - angle) * 3600 - minutes * 60))
                
                -- fix rounded seconds
                if seconds == 60 then
                    minutes = minutes + 1
                    seconds = 0
                    if minutes == 60 then
                        angle = angle + 1
                        minutes = 0
                    end
                end
        
                if format.precision > 0.01 then
                    -- round the value
                    if seconds >= 30 then
                        minutes = minutes + 1
                    end
                    seconds = 0
                    if minutes == 60 then
                        angle = angle + 1
                        minutes = 0
                    end
                end
        
                result = string.format(format.format, angle, markers.degree, minutes, markers.minute, seconds, markers.second)
            end
        
            if decimalSeparator then
                result = string.gsub(result, "%.", decimalSeparator)
            end
        
            return prefix .. result .. suffix
        end
        
        local function formatDegree(value, decimalSeparator)
            local result = string.format("%f", value)
        
            if decimalSeparator then
                result = string.gsub(result, "%.", decimalSeparator)
            end
        
            return result
        end
    
        local function fullpagenamee()
            local title = mw.title.getCurrentTitle()
            return title.namespace == 0
                and title:partialUrl()
                or  title.nsText .. ":" .. title:partialUrl()
        end
    
        local status, format = selectFormat(self.precision)
        assert(format)
    
        local prettyLatitude  = formatAngle(self.latitude,  format, geoformatdata.latitudeGlobeMarkers,  geoformatdata.displayDecimalSeparator)
        local prettyLongitude = formatAngle(self.longitude, format, geoformatdata.longitudeGlobeMarkers, geoformatdata.displayDecimalSeparator)
        
        local categoryStatus, categoryFormat = selectFormat("st")
        local latitudeCategoryMarkers = { degree="°", minute="′", second="″", positivePrefix="", positiveSuffix=" N", negativePrefix="", negativeSuffix=" S", }
        local longitudeLinkMarkers = { degree="°", minute="′", second="″", positivePrefix="", positiveSuffix=" E", negativePrefix="", negativeSuffix=" W", }
        local categoryLatitude  = formatAngle(self.latitude,  categoryFormat, latitudeCategoryMarkers,  geoformatdata.displayDecimalSeparator)
        local categoryLongitude = formatAngle(self.longitude, categoryFormat, longitudeLinkMarkers, geoformatdata.displayDecimalSeparator)
    
        if not self.link then
            return mw.text.nowiki(prettyLatitude .. geoformatdata.coordinatesSeparator .. prettyLongitude)
        end
    
        local params = {
            formatAngle(self.latitude,  format, geoformatdata.latitudeLinkMarkers),
            formatAngle(self.longitude, format, geoformatdata.longitudeLinkMarkers),
        }
    
        if self.params then
            table.insert(params, self.params)
        end
    
        local degreeLatitude  = formatDegree(self.latitude,  geoformatdata.displayDecimalSeparator)
        local degreeLongitude = formatDegree(self.longitude, geoformatdata.displayDecimalSeparator)
    
        local geohack_link = string.format(geoformatdata.geohack_link, fullpagenamee(), table.concat(params,"_"))
        if self.name then
            geohack_link = geohack_link .. "&title=" .. mw.uri.encode(self.name)
        end
    
        local pretty_hint = string.format(geoformatdata.geohack_hint, prettyLatitude, prettyLongitude)
        local degree_hint = string.format(geoformatdata.geohack_hint, degreeLatitude, degreeLongitude)
        local separator = mw.text.nowiki(geoformatdata.coordinatesSeparator)
    
        local result = {
            "[", geohack_link, " ",
            "<span class=\"geo-default\">",
                "<span class=\"geo-dms\" title=\"", mw.text.nowiki(pretty_hint), "\">",
                    "<span class=\"latitude\">", mw.text.nowiki(prettyLatitude), "</span>",
                    separator,
                    "<span class=\"longitude\">", mw.text.nowiki(prettyLongitude), "</span>",
                "</span>",
            "</span>",
            "<span class=\"geo-multi-punct\">/</span>",
            "<span class=\"geo-nondefault\">",
                "<span class=\"geo-dms\" title=\"", mw.text.nowiki(degree_hint), "\">",
                    "<span class=\"latitude\">", mw.text.nowiki(degreeLatitude), "</span>",
                    separator,
                    "<span class=\"longitude\">", mw.text.nowiki(degreeLongitude), "</span>",
                "</span>",
            "</span>",
            "]"
        }
    
        local text = table.concat(result, "")
    
        if not self.inline and not self.top then
            return text
        end
        
        result = {}
    
        if self.inline then
            if inlinePrefix then
                table.insert(result, inlinePrefix)
            end
            if self.top then
                table.insert(result, "<span class=\"coordinates inline inline-and-top plainlinks\">")
            else
                table.insert(result, "<span class=\"coordinates inline plainlinks\">")
            end
            table.insert(result, text)
            table.insert(result, "</span>")
        end
    
        if self.top then
            table.insert(result, "<span id=\"coordinates\" class=\"coordinates put-in-header plainlinks\">")
            table.insert(result, geoformatdata.topPrefix)
            table.insert(result, text)
            table.insert(result, "</span>")
        end
        
        -- add category
        table.insert(result, "[[Category:"..categoryLongitude.."]]")
        table.insert(result, "[[Category:"..categoryLatitude.."]]")
    
        return table.concat(result, "")
    end

    function result:extensionGeoData(frame)
        local params = {}
 
        local title = mw.title.getCurrentTitle()
        if self.top and not title.isTalkPage and (title.subpageText ~= geoformatdata.documentationSubpage) then
            table.insert(params, "primary")
        end

        if self.latitude >= 0 then
            table.insert(params, string.format("%f", self.latitude))
            table.insert(params, "N")
        else
            table.insert(params, string.format("%f", -self.latitude))
            table.insert(params, "S")
        end

        if mode == "W" then
            if self.longitude > 0 then
                table.insert(params, string.format("%f", 360-self.longitude))
            else
                table.insert(params, string.format("%f", -self.longitude))
            end
            table.insert(params, "W")
        elseif mode == "E" then
            if self.longitude >= 0 then
                table.insert(params, string.format("%f", self.longitude))
            else
                table.insert(params, string.format("%f", 360+self.longitude))
            end
            table.insert(params, "E")
        elseif self.longitude >= 0 then
            table.insert(params, string.format("%f", self.longitude))
            table.insert(params, "E")
        else
            table.insert(params, string.format("%f", -self.longitude))
            table.insert(params, "W")
        end

        if self.params then
            table.insert(params, self.params)
        end

        if self.name then
            params.name = self.name
        end

        --  https://bugzilla.wikimedia.org/show_bug.cgi?id=50863 RESOLVED
        return frame:callParserFunction("#coordinates", params) or ""
    end

    return result;
end

local function showError(message, args)
    if not message then
        return geoformatdata.errorCategory
    end
    
    local result = {}
    table.insert(result, "<span style=\"color:red\">")
    assert(type(message) == "string", "Expected string message")
    table.insert(result, message)
    local i = 1
    while args[i] do
        if i == 1 then
            table.insert(result, ": {")
        else
            table.insert(result, "&#x7C;")
        end
        
        table.insert(result, args[i])
        i = i + 1
    end
    if i > 1 then
        table.insert(result, "}")
    end
    
    table.insert(result, "</span>")
    
    if mw.title.getCurrentTitle().namespace == 0 then
        table.insert(result, geoformatdata.errorCategory)
    end
    
    return table.concat(result, "")
end

local function parse(frame, link)
    local coordinates = create()
    local args = frame.args
    local status, errorMessage = coordinates:parseCoordinates(args)
    if not status then
        return showError(errorMessage, args)
    end

    local status, errorMessage = coordinates:parseOptions(args)
    if not status then
        return showError(errorMessage, args)
    end

    coordinates.link = link
    coordinates:normalize()
    return coordinates:display() .. coordinates:extensionGeoData(frame)
end

function m.format(frame)
    return parse(frame, false)
end

function m.link(frame)
    return parse(frame, true)
end

m[geoformatdata.apiTemplateName] = function (frame)
    local coordinates = create()
    local args = frame:getParent().args
    local status, errorMessage = coordinates:parseCoordinates(args)
    if not status then
        return showError(errorMessage, args)
    end

    local status, errorMessage = coordinates:parseOptions(args)
    if not status then
        return showError(errorMessage, args)
    end
    
    coordinates:normalize()
    return coordinates:display() .. coordinates:extensionGeoData(frame)
end

m[geoformatdata.apiMicroName] = function (frame)
    local coordinates = create()
    local args = frame:getParent().args
    local status, errorMessage = coordinates:parseCoordinates(args)
    if not status then
        return showError(errorMessage, args)
    end

    -- the only available option
    coordinates.name = args[geoformatdata.argName]

    -- options are implied in micro variant
    if coordinates.precision > 0.00027777777777777800 then
        coordinates.precision = 0.00027777777777777800 -- seconds
    elseif coordinates.precision < 0.00002777777777777780 then
        coordinates.precision = 0.00002777777777777780 -- seconds with one decimal digit
    end
    
    if not coordinates.params then
        coordinates.params    = "scale:5000" -- bonus
    end
    
    coordinates.inline    = true
    coordinates.top       = false
    coordinates.link      = true

    -- simple link without geodata extension
    coordinates:normalize()
    return coordinates:display()
end

m[geoformatdata.apiAutoName] = function(frame)
    
    local entity = mw.wikibase.getEntity() if not entity then return nil end -- missing entity
    local claims = entity.claims if not claims then return nil end -- missing claims
    
    local function selectProperty(pid)
        local prop = claims[pid] if not prop then return false end -- missing property
 
        -- load preferred statements
        local result = {}
        for i = 0, #prop do
            if prop[i].rank == "preferred" then
                table.insert(result, prop[i])
            end
        end
 
        if #result ~= 0 then return true, prop end
 
        for i = 0, #prop do
            if prop[i].rank == "normal" then
                table.insert(result, prop[i])
            end
        end
 
        if #result ~= 0 then return true, prop end
 
        return false -- empty property table
    end
 
    local function selectValue(prop, index, expectedType)
        local p = prop[index]
        if not p then return false end
        if p.type ~= "statement" then return false end
        local snak = p.mainsnak
        if not snak or snak.snaktype ~= "value" then return false end
        local datavalue = snak.datavalue
        if not datavalue or datavalue.type ~= expectedType then return false end
        local value = datavalue.value
        if not value then return false end
        return true, value
    end
 
    function selectGlobe(globe)
        local globes = {
            unknownGlobe = { symbol="", link=false },
            ["http://www.wikidata.org/entity/Q2"]   = { symbol="[[Plik:Geographylogo.svg|20px|alt=Ziemia|link=Ziemia]] ", link="" },
            ["http://www.wikidata.org/entity/Q405"] = { symbol="[[Plik:Nuvola apps kmoon left.png|15px|alt=Księżyc|link=Księżyc]] ", link="globe:Moon" },
            ["http://www.wikidata.org/entity/Q111"] = { symbol="[[Plik:Blue Mars symbol.svg|15px|alt=Mars|link=Mars]] ", link="globe:Mars" },
            ["http://www.wikidata.org/entity/Q308"] = { symbol="[[Plik:Blue Mercury symbol.svg|12px|alt=Merkury|link=Merkury]] ", link="globe:Mercury" },
            ["http://www.wikidata.org/entity/Q313"] = { symbol="[[Plik:Symbol venus blue.svg|12px|alt=Wenus|link=Wenus]] ", link="globe:Venus" },
        }
 
        return globes[globe or "http://www.wikidata.org/entity/Q2"] or globes.unknownGlobe
    end
 
    function selectType()
        local types = {
            unknownType = "type:city",
            [515]  = "type:city",
            [6256] = "type:country",
            [5107] = "type:satellite",
            [165]  = "type:satellite",
        }
 
        local status, classes = selectProperty("P31") if not status then return types.unknownType end
        for i = 0, #classes do
            local status2, v = selectValue(classes, i, "wikibase-entityid")
            if status2 and v["entity-type"] == "item" then
                local result = types[v["numeric-id"]]
                if result then return result end
            end
        end
 
        return types.unknownType
    end

    local status1, coordinates = selectProperty("P" .. (frame.args[1] or "625")) if not status1 then return nil end
    local status2, autocoords = selectValue(coordinates, 0, "globecoordinate") if not status2 then return nil end
    local globe = selectGlobe(autocoords.globe)
    if not globe.link then return nil end -- not supported globe
 
    local params = {
        selectType(),
    }
    if #globe.link > 0 then
        table.insert(params, globe.link)
    end
    
    local coords = create()
    coords:parseOptions(frame.args)
    coords.latitude = autocoords.latitude
    coords.longitude = autocoords.longitude
    coords.precision = autocoords.precision or 1
    coords.params = table.concat(params,"_")
 
    coordinates:normalize()
    return coords:display(globe.symbol)
end

m[geoformatdata.apiLatitude] = function (frame)
    local coordinates = create()
    local status = coordinates:parseCoordinates(frame.args)
    return status and coordinates.latitude or ""
end

m[geoformatdata.apiLongitude] = function (frame)
    local coordinates = create()
    local status = coordinates:parseCoordinates(frame.args)
    return status and coordinates.longitude or ""
end

return m