Aller au contenu

« Module:Coordinates » : différence entre les versions

De Wreck
Modèle:Infobox>Xfigpower
m intégration de la méthode Modèle:Coordonnées à la sauce 08/32/53/E
m 153 versions importées
 
(106 versions intermédiaires par 13 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
--[[
local math_mod = require( "Module:Math" )
This module is intended to replace the functionality of {{Coord}} and related
templates.  It provides several methods, including


{{#Invoke:Coordinates | coord }} : General function formatting and displaying
local p = {}
coordinate values.


{{#Invoke:Coordinates | dec2dms }} : Simple function for converting decimal
local NBSP = '\194\160' -- espace insécable (code UTF-8 sur deux octets)
degree values to DMS format.


{{#Invoke:Coordinates | dms2dec }} : Simple function for converting DMS format
local i18n = {
to decimal degree format.
N = 'N',
Nlong = 'nord',
W = 'O',
Wlong = 'ouest',
E = 'E',
Elong = 'est',
S = 'S',
Slong = 'sud',
degrees = '°' .. NBSP,
minutes = '′' .. NBSP,
seconds = '″' .. NBSP,
geohackurl = 'http://tools.wmflabs.org/geohack/geohack.php?language=fr',
tooltip = 'Cartes, vues aériennes, etc.',
errorcat = 'Page avec des balises de coordonnées mal formées',
sameaswikidata = 'Page avec coordonnées similaires sur Wikidata',
notaswikidata = 'Page avec coordonnées différentes sur Wikidata',
nowikidata = 'Page sans coordonnées Wikidata',
throughwikidata = 'Page géolocalisée par Wikidata',
invalidFormat = 'format invalide',                                          -- 'invalid coordinate format',
invalidNSEW = 'orientation invalide, devrait être "N", "S", "E" or "W"',    -- 'invalid direction should be "N", "S", "E" or "W"',
invalidNS = 'orientation de latitude invalide, devrait être "N" ou "S"',    -- 'could not find latitude direction (should be N or S)',
invalidEW = 'orientation de longitude invalide, devrait être "E" ou "W"',  -- 'could not find longitude direction (should be W or E) ',
noCardinalDirection = 'orientation cardinale non trouvée',                  -- 'no cardinal direction found in coordinates',
invalidDirection = 'direction invalide',                                    -- 'invalid direction',
latitude90 = 'latitude > 90',
longitude360 = 'longitude > 360',
minSec60 = 'minutes ou secondes > 60',
negativeCoode = 'en format dms les degrés doivent être positifs',          -- 'dms coordinates should be positive',
dmIntergers = 'degrés et minutes doivent être des nombres entiers',        -- 'degrees and minutes should be integers',
tooManyParam = 'trop de paramètres pour la latitude ou la longitude',      -- 'too many parameters for coordinates',
coordMissing = 'latitude ou longitude absente',                            -- 'latitude or longitude missing',
invalidGlobe = 'globe invalide' .. NBSP .. ': ',                            -- 'invalid globe:',
}
local coordParse = {
NORTH = 'N',
NORD = 'N',
EAST = 'E',
EST = 'E',
WEST = 'W',
O = 'W',
OUEST = 'W',
SOUTH = 'S',
SUD = 'S',
}


]]
local globedata = {
--[[ notes:
radius in kilometers (especially imprecise for non spheric bodies)
defaultdisplay is currently disabled, activate it ?
]]--
ariel =  {radius = 580, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
callisto =  {radius = 2410, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
ceres =  {radius = 470, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
charon =  {radius = 1214, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
deimos =  {radius = 7, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
dione =  {radius = 560, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
enceladus =  {radius = 255, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
ganymede =  {radius = 2634, defaultdisplay = 'dec west', trackingcat = 'sur Ganymède'},
earth =  {radius = 6371, defaultdisplay = 'dms', trackingcat = 'sur Terre'},
europa =  {radius = 1561, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
hyperion =  {radius = 140, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
iapetus =  {radius = 725, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
['io'] =  {radius = 1322, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
jupiter =  {radius = 68911, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
mars =  {radius = 3389.5, defaultdisplay = 'dec east', trackingcat = 'sur Mars'},
mercury =  {radius = 2439.7, defaultdisplay = 'dec west', trackingcat = 'sur Mercure'},
mimas =  {radius = 197, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
miranda =  {radius = 335, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
moon =  {radius = 1736, defaultdisplay = 'dec', trackingcat = 'sur la Lune'},
neptune =  {radius = 24553, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
oberon =  {radius = 761, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
phoebe =  {radius = 110, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
phobos =  {radius = 11, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
pluto =  {radius = 1185, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
rhea =  {radius = 765, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
saturn =  {radius = 58232, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
titan =  {radius = 2575.5, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
tethys =  {radius = 530, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
titania =  {radius = 394, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
triton =  {radius = 1353, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
umbriel =  {radius = 584, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
uranus =  {radius = 25266, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
venus =  {radius = 6051.8, defaultdisplay = 'dec east', trackingcat = 'sur Vénus'},
vesta =  {radius = 260, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'}
}
globedata[''] = globedata.earth


math_mod = require( "Module:Math" );
local wikidatathreshold = 10 -- si la distance entre coordonnées Wikipédia et Wikidata dépasse ce seuil (en kilomètres), une catégorie de maintenance est ajoutée
globalFrame = nil
local lang = mw.language.getContentLanguage()
local default_zoom = 13


coordinates = {};
local function makecat(cat, sortkey)
if type( sortkey ) == 'string' then
return '[[Category:' .. cat .. '|' .. sortkey .. ']]'
else
return '[[Category:' .. cat .. ']]'
end
end
 
----------------------------------------
--Error handling
--[[ Notes:
when errors occure a new error message is concatenated to errorstring
an error message contains an error category with a sortkey
For major errors, it can also display an error message (the error message will the usually be returned and the function terminated)
More minor errors do only add a category, so that readers are not bothered with error texts
sortkeys:
* A: invalid latitude, longitude or direction
* B: invalid globe
* C: something wrong with other parameters
* D: more than one primary coord
]]--
 
local errorstring = ''
 
local function makeerror(args)
local errormessage = ''
if args.message then
errormessage = '<strong class="error"> Coordonnées' .. NBSP .. ': ' .. args.message .. '</strong>'
end
local errorcat = ''
if mw.title.getCurrentTitle().namespace == 0 then
errorcat = makecat(i18n.errorcat, args.sortkey)
end
errorstring = errormessage .. errorcat -- reinitializes the string to avoid absurdly long messages
return nil
end
 
local function showerrors()
return errorstring
end
 
 
 
-- Distance computation
function p._distance(a, b, globe) -- calcule la [[distance orthodromique]] en kilomètres entre deux points du globe
 
globe = string.lower(globe or 'earth')
 
-- check arguments and converts degreees to radians
local latA, latB, longA, longB = a.latitude, b.latitude, a.longitude, b.longitude
if (not latA) or (not latB) or (not longA) or (not longB) then return
error('coordinates missing, can\'t compute distance')
end
if type(latA) ~= 'number' or type(latB) ~= 'number' or type(longA) ~= 'number' or type(longB) ~= 'number' then
error('coordinates are not numeric, can\'t compute distance')
end
if not globe or not globedata[globe] then
return error('globe: ' .. globe .. 'is not supported')
end
 
-- calcul de la distance angulaire en radians
local convratio = math.pi / 180 -- convertit en radians
latA, latB, longA, longB = convratio * latA, convratio * latB, convratio * longA, convratio * longB
local cosangle = math.sin(latA) * math.sin(latB) + math.cos(latA) * math.cos(latB) * math.cos(longB - longA)
if cosangle >= 1 then -- may be above one because of rounding errors
return 0
end
local angle = math.acos(cosangle)
-- calcul de la distance en km
local radius = globedata[globe].radius
return radius * angle
end
 
function p.distance(frame)
local args = frame.args
return p._distance(
{latitude = tonumber(args.latitude1), longitude = tonumber(args.longitude1)},
{latitude = tonumber(args.latitude2), longitude = tonumber(args.longitude2)},
args.globe)
end


--[[ Helper function, replacement for {{coord/display/title}} ]]
local function geoHackUrl(decLat, decLong, globe, displayformat, objectname, extraparams)
function displaytitle (s, notes)
extraparams = extraparams or ''
    --local l = "[[Geographic coordinate system|Coordinates]]: " .. s
local geohacklatitude, geohacklongitude
    local l = "" .. s
-- format latitude and longitude for the URL
    local extracat = ""
if tonumber(decLat) < 0 then
   
geohacklatitude = tostring(-tonumber(decLat)) .. '_S'
    if mw.title.getCurrentTitle().nsText == "" then
else
        extracat = "[[Catégorie:Article géolocalisé]]";
geohacklatitude = decLat .. '_N'
    end
end
   
if tonumber(decLong) < 0  then
    local co = '<span id="coordinates">' .. l .. notes .. '</span>';
geohacklongitude = tostring(-tonumber(decLong)) .. '_W'
    return '<span style="font-size: small;">' .. co .. '</span>' .. extracat;
elseif globedata[globe].defaultdisplay == 'dec west' then
geohacklongitude = decLong .. '_W'
else
geohacklongitude = decLong .. '_E'
end
-- prepares the 'paramss=' parameter
local geohackparams = geohacklatitude .. '_' .. geohacklongitude .. '_' ..extraparams
-- concatenate parameteres for geohack
return i18n.geohackurl ..
"&pagename=" .. mw.uri.encode(mw.title.getCurrentTitle().prefixedText, "WIKI") ..
"&params=" .. geohackparams ..
(objectname and ("&title=" .. mw.uri.encode(objectname)) or "")
end
end


--[[ Helper function, Replacement for {{coord/display/inline}} ]]
--HTML builder for a geohack link
function displayinline (s, notes)
local function buildHTML(decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
    return s .. notes   
-- geohack url
local url = geoHackUrl(decLat, decLong, globe, displayformat, objectname, extraparams)
 
-- displayed coordinates
local displaycoords
if string.sub(displayformat, 1, 3) == 'dec' then
displaycoords = p.displaydec(decLat, decLong, displayformat)
else
displaycoords = {
p.displaydmsdimension(dmsLat, displayformat),
p.displaydmsdimension(dmsLong, displayformat),
}
end
 
-- build coordinate in h-geo / h-card microformat
local globeNode
if globe and globe ~= 'earth' then
globeNode = mw.html.create('data')
:addClass('p-globe')
:attr{ value = globe }
:done()
end
 
local coordNode = mw.html.create('')
if objectname then
coordNode = mw.html.create('span')
:addClass('h-card')
:tag('data')
:addClass('p-name')
:attr{ value = objectname }
:done()
end
coordNode
:tag('span')
:addClass('h-geo')
:addClass('geo-' .. string.sub(displayformat, 1, 3))
:tag('data')
:addClass('p-latitude')
:attr{ value = decLat }
:wikitext( displaycoords[1] )
:done()
:wikitext(", ")
:tag('data')
:addClass('p-longitude')
:attr{ value = decLong }
:wikitext( displaycoords[2] )
:done()
:node( globeNode )
:done()
 
-- buid GeoHack link
local root = mw.html.create('span')
:addClass('plainlinks nourlexpansion')
:attr('title', i18n.tooltip)
:wikitext('[' .. url )
:node(coordNode)
:wikitext("]")
:done()
 
-- format result depending on args["display"] (nil, "inline", "title", "inline,title")
local inlineText = displayinline and tostring(root) or ''
local titleText = ''
if displaytitle then
local htmlTitle = mw.html.create('span')
:attr{ id = 'coordinates' }
:addClass( displayinline and 'noprint' or nil )
:node( root )
local frame = mw.getCurrentFrame()
titleText = frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' } )
end
 
return inlineText .. titleText
end
end


--[[ Helper function, used in detecting DMS formatting ]]
local function zoom( extraparams )
local dmsTest = function(first, second)
local zoomParam = extraparams:match( '%f[%w]zoom: ?(%d+)' )
    local concatenated = first:upper() .. second:upper();
if zoomParam then
   
return zoomParam
    return concatenated == "NE" or concatenated == "NO" or concatenated == "NW" or concatenated == "SE" or concatenated == "SO"  or concatenated == "SW" or
end
        concatenated == "EN" or concatenated == "ON" or concatenated == "WN" or concatenated == "ES" or concatenated == "OS"  or concatenated == "WS"
 
local scale = extraparams:match( '%f[%w]scale: ?(%d+)' )
if scale then
return math.floor(math.log10( 1 / tonumber( scale ) ) * 3 + 25)
end
 
local extraType = extraparams:match( '%f[%w]type: ?(%w+)' )
if extraType then
local zoomType = {
country = 5,
state = 6,
adm1st = 7,
adm2nd = 8,
city = 9,
isle = 10,
mountain = 10,
waterbody = 10,
airport = 12,
landmark = 13,
}
return zoomType[ extraType ]
end
end
end


--[[
--HTML builder for a geohack link
parseDec
local function buildMaplinkHTML( decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams )
-- displayed coordinates
local displaycoords
if string.sub(displayformat, 1, 3) == 'dec' then
displaycoords = p.displaydec(decLat, decLong, displayformat)
else
displaycoords = {
p.displaydmsdimension(dmsLat, displayformat),
p.displaydmsdimension(dmsLong, displayformat),
}
end
 
-- JSON for maplink
local jsonParams = {
type = 'Feature',
geometry = {
type ='Point',
coordinates = {
math_mod._round( decLong, 6 ), -- max precision in GeoJSON format
math_mod._round( decLat, 6 )
}
},
properties = {
['marker-color'] = "228b22",
}
}
if objectname then
jsonParams.properties.title = objectname
end
-- ajout de geoshape via externaldata
local geoshape = extraparams:match( '%f[%w]geoshape: ?(Q%d+)' )
if not geoshape and displaytitle and mw.wikibase.getEntity() then
geoshape = mw.wikibase.getEntity().id
end
if geoshape then
jsonParams = {
jsonParams,
{
type = 'ExternalData',
service = 'geoshape',
ids = geoshape,
properties = {
['fill-opacity'] = 0.2
}
}
}
end
 
local maplink = mw.getCurrentFrame():extensionTag{
name = 'maplink',
content = mw.text.jsonEncode( jsonParams ),
args = {
text = displaycoords[1] .. ", " .. displaycoords[2],
zoom = zoom( extraparams ) or default_zoom,
latitude = decLat,
longitude = decLong,
}
}
 
-- format result depending on args["display"] (nil, "inline", "title", "inline,title")
local inlineText = displayinline and maplink or ''
local titleText = ''
if displaytitle then
local htmlTitle = mw.html.create('span')
:attr{ id = 'coordinates' }
:addClass( displayinline and 'noprint' or nil )
:wikitext( maplink )
local frame = mw.getCurrentFrame()
titleText = frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' } )
end


Transforms decimal format latitude and longitude into the a
return inlineText .. titleText
structure to be used in displaying coordinates
end
]]
function parseDec( lat, long, format )
    local coordinateSpec = {}
    local errors = {}
   
    if long == "" or long == nil then
        return nil, {{"parseDec", "Missing longitude"}}
    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 );
-- dms specific funcions
    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}}}}}}} 
    coordinateSpec["dmslong-lat"]  = convert_dec2dms( lat, "Nord", "Sud", mode)  -- {{coord/dec2dms|{{{1}}}|N|S|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}
    coordinateSpec["dmslong-long"] = convert_dec2dms( long, "Est", "Ouest", mode)  -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}     
   
    if format ~= "" then
        coordinateSpec.default = format
    else
        coordinateSpec.default = "dec"
    end


    return coordinateSpec, errors
local function twoDigit( value )
if ( value < 10 ) then
value = '0' .. lang:formatNum( value )
else
value = lang:formatNum( value )
end
return value
end
end


--[[ Helper function, handle optional args. ]]
function p.displaydmsdimension(valuetable, format) -- formate en latitude ou une longitude dms
function optionalArg(arg, suplement)
local str = ''
    if arg ~= nil and arg ~= "" then
local direction = valuetable.direction
        return arg .. suplement
local degrees, minutes, seconds = '', '', ''
    end
local dimension
    return ""
 
if format == 'dms long' then
direction = i18n[direction .. 'long']
else
direction = i18n[direction]
end
degrees = lang:formatNum( valuetable.degrees ) .. i18n.degrees
 
if valuetable.minutes then
minutes = twoDigit( valuetable.minutes ) .. i18n.minutes
end
if valuetable.seconds then
seconds = twoDigit( valuetable.seconds ) .. i18n.seconds
end
return degrees .. minutes .. seconds .. direction
end
end


--[[
local function validdms(coordtable)
parseDMS
local direction = coordtable.direction
local degrees = coordtable.degrees or 0
local minutes = coordtable.minutes or 0
local seconds = coordtable.seconds or 0
local dimension = coordtable.dimension
if not dimension then
if direction == 'N' or direction == 'S' then
dimension = 'latitude'
elseif direction == 'E' or direction == 'W' then
dimension = 'longitude'
else
makeerror({message = i18n.invalidNSEW, sortkey = 'A'})
return false
end
end
 
if type(degrees) ~= 'number' or type(minutes) ~= 'number' or type(seconds) ~= 'number' then
makeerror({message = i18n.invalidFormat, sortkey = 'A'})
return false
end
 
if dimension == 'latitude' and direction ~= 'N' and direction ~= 'S' then
makeerror({message = i18n.invalidNS, sortkey = 'A'})
return false
end
if dimension == 'longitude' and direction ~= 'W' and direction ~= 'E' then
makeerror({message = i18n.invalidEW, sortkey = 'A'})
return false
end
 
if dimension == 'latitude' and degrees > 90 then
makeerror({message = i18n.latitude90, sortkey = 'A'})
return false
end


Transforms degrees, minutes, seconds format latitude and longitude
if dimension == 'longitude' and degrees > 360 then
into the a structure to be used in displaying coordinates
makeerror({message = i18n.longitude360, sortkey = 'A'})
]]
return false
function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format )
end
    local coordinateSpec = {}
    local errors = {}
   
    lat_f = lat_f:upper();
    long_f = string.gsub(long_f:upper(),'W','O');


    local lat_flong = 'Nord';
if degrees < 0 or minutes < 0 or seconds < 0 then
    if lat_f == 'S' then
makeerror({message = i18n.negativeCoode, sortkey = 'A'})
        lat_flong = 'Sud';
return false
    end
end
    local long_flong = 'Ouest';
    if long_f == 'E' then
        long_flong = 'Est';
    end
   
   
    -- Check if specified backward
    if lat_f == 'E' or lat_f == 'O' or lat_f == 'W' 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", "Missing longitude" })
    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() == 'O' or long_f:upper() == 'W' then
                long_d = '-' .. long_d;
            end   
           
            return parseDec( lat_d, long_d, format );
        end       
    end 
   
    coordinateSpec["dms-lat"]  = lat_d.."°&nbsp;"..optionalArg(lat_m,"′&nbsp;") .. optionalArg(lat_s,"″&nbsp;") .. lat_f
    coordinateSpec["dmslong-lat"]  = lat_d.."°&nbsp;"..optionalArg(lat_m,"′&nbsp;") .. optionalArg(lat_s,"″&nbsp;") .. lat_flong
    coordinateSpec["dms-long"] = long_d.."°&nbsp;"..optionalArg(long_m,"′&nbsp;") .. optionalArg(long_s,"″&nbsp;") .. long_f
    coordinateSpec["dmslong-long"] = long_d.."°&nbsp;"..optionalArg(long_m,"′&nbsp;") .. optionalArg(long_s,"″&nbsp;") .. long_flong
    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
if minutes > 60 or seconds > 60 then
makeerror({message = i18n.minSec60, sortkey = 'A'})
return false
end
if (math.floor(degrees) ~= degrees and minutes ~= 0) or (math.floor(minutes) ~= minutes and seconds ~= 0) then
makeerror({message = i18n.dmIntergers, sortkey = 'A'})
return false
end
return true
end
end


--[[
local function builddmsdimension(degrees, minutes, seconds, direction, dimension)
specPrinter
-- no error checking, done in function validdms
local dimensionobject = {}


Output formatter.  Takes the structure generated by either parseDec
-- direction and dimension (= latitude or longitude)
or parseDMS and formats it for inclusion on Wikipedia.
dimensionobject.direction = direction
]]
if dimension then
function specPrinter(args, coordinateSpec)
dimensionobject.dimension = dimension
    local uriComponents = coordinateSpec["param"]
elseif direction == 'N' or direction == 'S' then
    if uriComponents == "" then
dimensionobject.dimension = 'latitude'
        -- RETURN error, should never be empty or nil
elseif direction == 'E' or direction == 'W' then
        return "ERROR param was empty"
dimensionobject.dimension = 'longitude'
    end
end
       
    if args["name"] ~= "" and args["name"] ~= nil then
        uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec["name"])
    end
   
    local lang=mw.language.getContentLanguage():getCode();
   
    local geodmshtml = '<span class="geo-dms" title="Cartes, vues aériennes et autres données pour cet endroit">'
            .. '<span class="latitude">' .. coordinateSpec["dms-lat"] .. '</span> '
            .. '<span class="longitude">' ..coordinateSpec["dms-long"] .. '</span>'
            .. '</span>'
    local geodmslonghtml = '<span class="geo-dms" title="Cartes, vues aériennes et autres données pour cet endroit">'
            .. '<span class="latitude">' .. coordinateSpec["dmslong-lat"] .. '</span> '
            .. '<span class="longitude">' ..coordinateSpec["dmslong-long"] .. '</span>'
            .. '</span>'
    local lat = tonumber( coordinateSpec["dec-lat"] ) or 0
    if lat < 0 then
        -- FIXME this breaks the pre-existing precision
        --geodeclat = coordinateSpec["dec-lat"]:sub(2) .. "°S"
        geodeclat = "-" .. coordinateSpec["dec-lat"]:sub(2)
    else
        --geodeclat = (coordinateSpec["dec-lat"] or 0) .. "°N"
        geodeclat = (coordinateSpec["dec-lat"] or 0)
    end


    local long = tonumber( coordinateSpec["dec-long"] ) or 0
-- degrees, minutes, seconds
    if long < 0 then
dimensionobject.degrees = tonumber(degrees)
        -- FIXME does not handle unicode minus
dimensionobject.minutes = tonumber(minutes)
        --geodeclong = coordinateSpec["dec-long"]:sub(2) .. "°O"
dimensionobject.seconds = tonumber(seconds)
        geodeclong = '-' .. coordinateSpec["dec-long"]:sub(2)
if degrees and not dimensionobject.degrees then dimensionobject.degrees = 'error' end
    else
if minutes and not dimensionobject.minutes then dimensionobject.minutes = 'error' end
        --geodeclong = (coordinateSpec["dec-long"] or 0) .. "°E"
if seconds and not dimensionobject.seconds then dimensionobject.seconds = 'error' end
        geodeclong = (coordinateSpec["dec-long"] or 0)
return dimensionobject
    end
end
   
    local geodechtml = '<span class="geo-dec" title=Cartes, vues aériennes et autres données pour cet endroit">'
            .. geodeclat .. ', '
            .. geodeclong
            .. '</span>'


    local geonumhtml = '<span class="geo">'
function p._parsedmsstring( str, dimension ) -- prend une séquence et donne des noms aux paramètres
            .. coordinateSpec["dec-lat"] .. '; '
-- output table: { latitude=, longitude = , direction =  }
            .. coordinateSpec["dec-long"]
if type( str ) ~= 'string' then
            .. '</span>'
return nil
end
str = mw.ustring.gsub( mw.ustring.upper( str ), '%a+', coordParse )
if not tonumber( str ) and not str:find( '/' ) and str:find( '°' ) then
local str2 = mw.ustring.gsub( str, '[°″′\"\'\194\160 ]+', '/' )
-- avoid cases were there is degree ans seconds but no minutes
if not mw.ustring.find( str, '[″"]' ) or mw.ustring.find( str, '%d[′\'][ \194\160%d]' ) then
str = str2
end
end
if not tonumber(str) and not string.find(str, '/') then
makeerror({message = i18n.invalidFormat, sortkey = 'A'})
return nil
end
local args = mw.text.split(str, '/', true)
if #args > 4 then
makeerror({message = i18n.tooManyParam, sortkey = 'A'})
end
local direction = mw.text.trim(args[#args])
table.remove(args)
local degrees, minutes, seconds = args[1], args[2], args[3]
local dimensionobject = builddmsdimension(degrees, minutes, seconds, direction, dimension)
if validdms(dimensionobject) then
return dimensionobject
else
return nil
end
end


    local inner;
--- decimal specific functions
    if coordinateSpec["default"] == "dms" then
function p.displaydec(latitude, longitude, format)
        inner = '<span class="' .. displayDefault(coordinateSpec["default"], "dms" ) .. '">' .. geodmshtml .. '</span>'
local lat = lang:formatNum( latitude )
                    .. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'
local long = lang:formatNum( longitude )
                    .. '<span class="' .. displayDefault(coordinateSpec["default"], "dec" ) .. '">';
    else
        inner = '<span class="' .. displayDefault(coordinateSpec["default"], "dms long" ) .. '">' .. geodmslonghtml .. '</span>'
                    .. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'
                    .. '<span class="' .. displayDefault(coordinateSpec["default"], "dec" ) .. '">';       
    end


if format == 'dec west' or  format == 'dec east' then
local symbolNS, symbolEW = i18n.N, i18n.E
if latitude < 0 then
symbolNS = i18n.S
lat = lang:formatNum( -latitude )
end
if format == 'dec west' then
symbolEW = i18n.W
end
if longitude < 0 then
long = lang:formatNum( 360 + longitude )
end


    if args["name"] == "" or args["name"] == nil then
return { lat .. i18n.degrees .. symbolNS,  long .. i18n.degrees .. symbolEW }
        inner = inner .. geodechtml
                .. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span></span>'
    else
        inner = inner .. '<span class="vcard">' .. geodechtml
                .. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span>'
                .. '<span style="display:none">&#xfeff; (<span class="fn org">'
                .. args["name"] .. '</span>)</span></span></span>'
    end


    return '<span class="plainlinksneverexpand">' .. globalFrame:preprocess(
else
        '[http://toolserver.org/~geohack/geohack.php?pagename={{FULLPAGENAMEE}}&language=' .. lang .. '&params=' ..
return { lat, long }
        uriComponents .. ' ' .. inner .. ']') .. '</span>'
end
end
end


--[[
 
Formats any error messages generated for display
local function parsedec(dec, coordtype, globe) -- coordtype = latitude or longitude
]]
dec = mw.text.trim(dec)
function errorPrinter(errors)
if not dec then
    local result = ""
return nil
    for i,v in ipairs(errors) do
end
        local errorHTML = '<strong class="error">Coordinates: ' .. v[2] .. '</strong>'
if coordtype ~= 'latitude' and coordtype ~= 'longitude' then
        result = result .. errorHTML .. "<br />"
makeerror({'invalid coord type', sortkey = "A"})
    end
return nil
    return result
end
local numdec = tonumber(dec) -- numeric value, kept separated as it looses significant zeros
if not numdec then -- tries the decimal + direction format
dec = mw.ustring.gsub( mw.ustring.upper( dec ), '%a+', coordParse )
local direction = mw.ustring.sub(dec, mw.ustring.len(dec), mw.ustring.len(dec))
dec = mw.ustring.sub(dec, 1, mw.ustring.len(dec)-2) -- removes the /N at the end
if not dec or not tonumber(dec) then
return nil
end
if direction == 'N' or direction == 'E' or direction == 'W' and globedata[globe].defaultdisplay == 'dec west' then
numdec = tonumber( dec )
elseif direction == 'W' or direction == 'S' then
dec = '-' .. dec
numdec = tonumber( dec )
else
if coordtype == 'latitude' then
makeerror({message = i18n.invalidNS, sortkey = 'A'})
else
makeerror({message = i18n.invalidEW, sortkey = 'A'})
end
return nil
end
end
 
if coordtype == 'latitude' and math.abs(numdec) > 90 then
makeerror({message = i18n.latitude90 , sortkey = 'A'})
return nil
end
if coordtype == 'longitude' and math.abs(numdec) > 360 then
makeerror({message = i18n.longitude360 , sortkey = 'A'})
return nil
end
return dec
end
end


--[[
-- dms/dec conversion functions
Determine the required CSS class to display coordinates
local function convertprecision(precision) -- converts a decimal precision like "2" into "dm"
if precision >= 3 then
return 'dms'
elseif precision >=1 then
return 'dm'
else
return 'd'
end
end


Usually geo-nondefault is hidden by CSS, unless a user has overridden this for himself
local function determinedmsprec(decs) -- returns the most precision for a dec2dms conversion, depending on the most precise value in the decs table
default is the mode as specificied by the user when calling the {{coord}} template
local precision = 0
mode is the display mode (dec or dms) that we will need to determine the css class for  
for d, val in ipairs(decs) do
]]
precision = math.max(precision, math_mod._precision(val))
function displayDefault(default, mode)
end
    if default == "" then
return convertprecision(precision)
        default = "dec"
    end
   
    if default:sub(1,4) == mode:sub(1,4) then
        return "geo-default"
    else
        return "geo-nondefault"
    end
end
end


--[[
local function dec2dms_d(dec)
Check the input arguments for coord to determine the kind of data being provided
local degrees = math_mod._round( dec, 0 )
and then make the necessary processing.
return degrees
]]
function formatTest(args)
    local result, errors;
    local primary = false;
   
    if args[1] == "" then
        -- no lat logic
        return errorPrinter( {{"formatTest", "Missing latitude"}} )
    elseif args[4] == "" and args[5] == "" and args[6] == "" then
        -- dec logic
        result, errors = parseDec( args[1], args[2], args['format'] )
        if result == nil then
            return errorPrinter( errors );
        end             
        result.param    = table.concat( {args[1], "_N_", args[2], "_E_", args[3] } );
    elseif dmsTest(args[4], args[8]) then
        -- dms logic
        result, errors = parseDMS( args[1], args[2], args[3], args[4],
            args[5], args[6], args[7], args[8], args['format'] )
        result.param = table.concat( { args[1], args[2], args[3], args[4], args[5],
            args[6], args[7], args[8], args[9] } , '_' );
        if args[10] ~= '' then
            table.insert( errors, { 'formatTest', 'Extra unexpected parameters' } );
        end       
    elseif dmsTest(args[3], args[6]) then
        -- dm logic
        result, errors = parseDMS( args[1], args[2], nil, args[3],
            args[4], args[5], nil, args[6], args['format'] )
        result.param = table.concat( { args[1], args[2], args[3], args[4], args[5],
            args[6], args[7] } , '_' );
        if args[8] ~= '' then
            table.insert( errors, { 'formatTest', 'Extra unexpected parameters' } );
        end       
    elseif dmsTest(args[2], args[4]) then
        -- d logic
        result, errors = parseDMS( args[1], nil, nil, args[2],
            args[3], nil, nil, args[4], args['format'] )
        result.param = table.concat( { args[1], args[2], args[3], args[4], args[5] } , '_' );
        if args[6] ~= '' then
            table.insert( errors, { 'formatTest', 'Extra unexpected parameters' } );
        end       
    else
        -- Error
        return errorPrinter( {{"formatTest", "Unknown argument format"}} )
    end
    result.name    = args["name"]
   
    local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type', 'scale'}
    for _, v in ipairs( extra_param ) do
        if (args[v] or '') ~= '' then
            table.insert( errors, {'formatTest', 'Parameter: "' .. v .. '=" should be "' .. v .. ':"' } );
        end
    end
   
    if #errors == 0 then
        return specPrinter( args, result )  
    else
        return specPrinter( args, result ) .. " " .. errorPrinter(errors) .. '[[Catégorie:Article posant un problème de coordonnées]]';
    end   
end
end


--[[
local function dec2dms_dm(dec)
Helper function, convert decimal latitude or longitude to
dec = math_mod._round( dec * 60, 0 )
degrees, minutes, and seconds format based on the specified precision.
local minutes = dec % 60
]]
dec = math.floor( (dec - minutes) / 60 )
function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
local degrees = dec % 360
    local coord = tonumber(coordinate) or 0
return degrees, minutes
    local postfix
end
    if coord >= 0 then
        postfix = firstPostfix
    else
        postfix = secondPostfix
    end


    precision = precision:lower();
local function dec2dms_dms(dec)
    if precision == "dms" then
dec = math_mod._round( dec * 60 * 60, 0 )
        return convert_dec2dms_dms( math.abs( coord ) ) .. postfix;
local seconds = dec % 60
    elseif precision == "dm" then
dec = math.floor( (dec - seconds) / 60 )
        return convert_dec2dms_dm( math.abs( coord ) ) .. postfix;
local minutes = dec % 60
    elseif precision == "d" then
dec = math.floor( (dec - minutes) / 60 )
        return convert_dec2dms_d( math.abs( coord ) ) .. postfix;
local degrees = dec % 360
    end
return degrees, minutes, seconds
end
end


--[[ Helper function, convert decimal to degrees ]]
function p._dec2dms(dec, coordtype, precision, globe) -- coordtype: latitude or longitude
function convert_dec2dms_d(coordinate)
local degrees, minutes, seconds
    local d = math_mod._round( coordinate, 0 ) .. "°&nbsp;"
 
    return d .. ""
-- vérification du globe
if not ( globe and globedata[ globe ] ) then
globe = 'earth'
end
 
-- precision
if not precision or precision == '' then
precision = determinedmsprec({dec})
end
if precision ~= 'd' and precision ~= 'dm' and precision ~= 'dms' then
return makeerror({sortkey = 'C'})
end
local dec = tonumber(dec)
 
-- direction
local direction
if coordtype == 'latitude' then
if dec < 0 then
direction = 'S'
else
direction = 'N'
end
elseif coordtype == 'longitude' then
if dec < 0 or globedata[globe].defaultdisplay == 'dec west' then
direction = 'W'
else
direction = 'E'
end
end
 
-- conversion
dec = math.abs(dec) -- les coordonnées en dms sont toujours positives
if precision == 'dms' then
degrees, minutes, seconds = dec2dms_dms(dec)
elseif precision == 'dm' then
degrees, minutes = dec2dms_dm(dec)
else
degrees = dec2dms_d(dec)
end
return builddmsdimension(degrees, minutes, seconds, direction)
end
end


--[[ Helper function, convert decimal to degrees and minutes ]]
function p.dec2dms(frame) -- legacy function somewhat cumbersome syntax
function convert_dec2dms_dm(coordinate)  
local args = frame.args
    coordinate = math_mod._round( coordinate * 60, 0 );
local dec = args[1]
    local m = coordinate % 60;
if not tonumber(dec) then
    coordinate = math.floor( (coordinate - m) / 60 );
makeerror({message = i18n.invalidFormat, sortkey = 'A'})
    local d = coordinate % 360 .."°&nbsp;"
return showerrors()
   
end
    return d .. string.format( "%02d′&nbsp;", m )
local dirpositive = string.lower(args[2] or '')
local dirnegative = string.lower(args[3] or '')
local precision = string.lower(args[4] or '')
local displayformat, coordtype
 
if dirpositive == 'n' or dirpositive == 'nord' then
coordtype = 'latitude'
else
coordtype = 'longitude'
end
if dirpositive == 'nord' or dirpositive == 'est' or dirnegative == 'ouest' or dirnegative == 'sud' then
displayformat = 'dms long'
end
local coordobject = p._dec2dms(dec, coordtype, precision)
if coordobject then
return p.displaydmsdimension(coordobject, displayformat) .. showerrors()
else
return showerrors()
end
end
end


--[[ Helper function, convert decimal to degrees, minutes, and seconds ]]
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 .."°&nbsp;"


    return d .. string.format( "%02d′&nbsp;", m ) .. string.format( "%02d″&nbsp;", s )
function p._dms2dec(dmsobject) -- transforme une table degré minute secondes en nombre décimal
local direction, degrees, minutes, seconds = dmsobject.direction, dmsobject.degrees, dmsobject.minutes, dmsobject.seconds
local factor = 0
local precision = 0
if not minutes then minutes = 0 end
if not seconds then seconds = 0 end
 
if direction == "N" or direction == "E" then
factor = 1
elseif direction == "W" or direction == "S" then
factor = -1
elseif not direction then
makeerror({message = i18n.noCardinalDirection, sortkey = 'A'})
return nil
else
makeerror({message = i18n.invalidDirection, sortkey = 'A'})
return nil
end
 
if dmsobject.seconds then -- vérifie la précision des données initiales
precision = 5 + math.max( math_mod._precision(tostring(seconds), 0 ) ) -- passage par des strings assez tarabiscoté ?
elseif dmsobject.minutes then
precision = 3 + math.max( math_mod._precision(tostring(minutes), 0 ) )
else
precision = math.max( math_mod._precision(tostring(degrees), 0 ) )
end
 
local decimal = factor * (degrees+(minutes+seconds/60)/60)
return math_mod._round(decimal, precision)
end
end


--[[
function p.dms2dec(frame) -- legacy function, somewhat bizarre syntax
Convert DMS format into a N or E decimal coordinate
local args = frame.args
]]
if tonumber(args[1]) then
function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str)
return args[1] -- coordonnées déjà en décimal
    local degrees = tonumber(degrees_str) or 0
elseif not args[2] then
    local minutes = tonumber(minutes_str) or 0
local dmsobject = p._parsedmsstring(args[1])
    local seconds = tonumber(seconds_str) or 0
if dmsobject then
   
return p._dms2dec(dmsobject) -- coordonnées sous la fore 23/22/N
    local factor
else
    if direction == "N" or direction == "E" then
local coordType
        factor = 1
if args[1]:match( '[NS]' ) then
    else
coordType = 'latitude'
        factor = -1
elseif args[1]:match( '[EWO]') then
    end
coordType = 'longitude'
   
end
    local precision = 0
if coordType then
    if seconds_str ~= nil and seconds_str ~= '' then
local result = parsedec( args[1],  coordType, args.globe or 'earth' )
        precision = 5 + math.max( math_mod._precision(seconds_str), 0 );
if result then
    elseif minutes_str ~= nil and minutes_str ~= '' then
return result
        precision = 3 + math.max( math_mod._precision(minutes_str), 0 );
end
    else
end
        precision = math.max( math_mod._precision(degrees_str), 0 );
return showerrors()
    end
end
   
else
    local decimal = factor * (degrees+(minutes+seconds/60)/60)  
return p._dms2dec({direction = args[1], degrees = tonumber(args[2]), minutes = tonumber(args[3]), seconds = tonumber(args[4])})
    return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based.
end
end
end


--[[
-- Wikidata
Checks input values to for out of range errors.
local function convertwikidataprecision(precision) -- converts a decima like "0.1" into "dm"
]]
if precision < 0.016 then
function validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, source, strong )
return 'dms'
    local errors = {};
elseif precision < 1 then
    lat_d = tonumber( lat_d ) or 0;
return 'dm'
    lat_m = tonumber( lat_m ) or 0;
else
    lat_s = tonumber( lat_s ) or 0;
return 'd'
    long_d = tonumber( long_d ) or 0;
end
    long_m = tonumber( long_m ) or 0;
end
    long_s = tonumber( long_s ) or 0;


    if strong then
local function wikidatacoords(query)
        if lat_d < 0 then
query = query or {property = 'p625'}
            table.insert(errors, {source, "latitude degrees < 0 with hemisphere flag"})
query.formatting = 'raw'
        end
local wd = require('Module:Wikidata')
        if long_d < 0 then
local claim = wd.getClaims(query)
            table.insert(errors, {source, "longitude degrees < 0 with hemisphere flag"})
if claim and claim[1] then -- redundant but more robust in case of a change in the code of Module:Wikidata
        end
local coords = wd.formatSnak(claim[1].mainsnak) -- todo: check for special values
        --[[
-- Wikidata does not handle correctly +West longitudes
        #coordinates is inconsistent about whether this is an error. If globe: is
if globedata[ coords.globe ] and globedata[ coords.globe ].defaultdisplay == 'dec west' then
        specified, it won't error on this condition, but otherwise it will.
coords.longitude = math.abs( coords.longitude )
       
end
        For not simply disable this check.
return coords.latitude, coords.longitude, coords.globe or 'earth', convertwikidataprecision(coords.precision or .001)
       
end
        if long_d > 180 then
return nil
            table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"})
        end
        ]]
    end   
       
    if lat_d > 90 then
        table.insert(errors, {source, "latitude degrees > 90"})
    end
    if lat_d < -90 then
        table.insert(errors, {source, "latitude degrees < -90"})
    end
    if lat_m >= 60 then
        table.insert(errors, {source, "latitude minutes >= 60"})
    end
    if lat_m < 0 then
        table.insert(errors, {source, "latitude minutes < 0"})
    end
    if lat_s >= 60 then
        table.insert(errors, {source, "latitude seconds >= 60"})
    end
    if lat_s < 0 then
        table.insert(errors, {source, "latitude seconds < 0"})
    end
    if long_d >= 360 then
        table.insert(errors, {source, "longitude degrees >= 360"})
    end
    if long_d <= -360 then
        table.insert(errors, {source, "longitude degrees <= -360"})
    end
    if long_m >= 60 then
        table.insert(errors, {source, "longitude minutes >= 60"})
    end
    if long_m < 0 then
        table.insert(errors, {source, "longitude minutes < 0"})
    end
    if long_s >= 60 then
        table.insert(errors, {source, "longitude seconds >= 60"})
    end
    if long_s < 0 then
        table.insert(errors, {source, "longitude seconds < 0"})
    end
   
    return errors;
end
end


--[[
dec2dms


Wrapper to allow templates to call dec2dms directly.
local function wikidatacat(globe)
--catbase= Article géolocalisé sur Terre
local entitycat = mw.wikibase.getEntity()
 
local basecat = 'Article géolocalisé'
local finalcat = {}
--BADGES
if entitycat then
--BADGES
  for i, badgeId in ipairs( entitycat.sitelinks['frwiki'].badges ) do
if badgeId == 'Q17437796'  then
basecat = string.gsub(basecat, "Article géolocalisé", "Article de qualité géolocalisé")
end
if badgeId == 'Q17437798'  then
basecat = string.gsub(basecat, "Article géolocalisé", "Bon article géolocalisé")
end
end
end


Usage:
if globe == 'earth' then
    {{ Invoke:Coordinates | dec2dms | decimal_coordinate | positive_suffix |
if entitycat and entitycat.claims then
        negative_suffix | precision }}
local country = entitycat.claims['P17']
   
if not country then
decimal_coordinate is converted to DMS format. If positive, the positive_suffix
--pas pays à récupérer
is appended (typical N or E), if negative, the negative suffix is appendedThe
basecat = basecat .. ' sur Terre'
specified precision is one of 'D', 'DM', or 'DMS' to specify the level of detail
table.insert(finalcat, basecat)
to use.
else
]]
--parfois plusieurs pays
function coordinates.dec2dms(frame)
for i, paysId in ipairs( country ) do
    globalFrame = frame
--on fait confiance au label wikidata
    local coordinate = frame.args[1]
local gdataone, qid
    local firstPostfix = frame.args[2]
    local secondPostfix = frame.args[3]
    local precision = frame.args[4]


    return convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
if paysId.mainsnak.snaktype == 'value' then
qid = paysId.mainsnak.datavalue.value['numeric-id']
gdataone = mw.loadData("Module:Drapeau/Data").data[qid]
else
--Bir Tawil n'a pas de pays connu
qid = '?'
end
if gdataone ~= nil then
local genre = mw.loadData("Module:Drapeau/Domaine").genre
local prep = genre[gdataone['genre']]['en'] or 'en '
local thecat = basecat .. ' '..prep ..mw.wikibase.label('Q'.. qid)
if mw.title.new('category:'..thecat).exists then
table.insert(finalcat, thecat)
else
--Dommage!
mw.log(thecat .. ' à créer')
end
else
--pas d'id?
mw.log(qid .. ' à paramétrer')
end
end
if #finalcat == 0 then
--pas pays à récupérer
basecat = basecat .. ' sur Terre'
table.insert(finalcat, basecat)
end
end
else
--pas wikidata
basecat = basecat .. ' sur Terre'
table.insert(finalcat, basecat)
end
elseif globedata[globe] then
basecat = basecat .. ' ' .. globedata[globe].trackingcat
table.insert(finalcat, basecat)
else
basecat = basecat .. ' extraterrestre'
table.insert(finalcat, basecat)
end
return finalcat
end
end


--[[
-- main function for displaying coordinates
Helper function to determine whether to use D, DM, or DMS
function p._coord(args)
format depending on the precision of the decimal input.
 
]]
-- I declare variable
function coordinates.determineMode( value1, value2 )
local displayformat = args.format -- string: one of: 'dms', 'dms long', 'dec', 'dec east' and 'dec west'
    local precision = math.max( math_mod._precision( value1 ), math_mod._precision( value2 ) );
local displayplace = string.lower(args.display or 'inline') --string: one of 'inline', 'title' or 'inline,title'
    if precision <= 0 then
local displayinline = string.find(displayplace, 'inline') and true or false
        return 'd'
local displaytitle = string.find(displayplace, 'title') and true or false
    elseif precision <= 2 then
local objectname = (args.name ~= '') and args.name -- string: name of the title displayed in geohack
        return 'dm';
local notes = (' ' and args.notes) or '' -- string: notes to de displayed after coordinates
    else
local wikidata = args.wikidata -- string: set to "true" if needed
        return 'dms';
local wikidataquery = args.wikidataquery -- table: see [[Module:Wikidata]] see function wikidatacoords
    end
local dmslatitude, dmslongitude -- table (when created)
end      
local extraparams = args.extraparams or '' -- string (legacy, corresponds to geohackparams)
local trackingstring = '' -- tracking cats except error cats (already in errorstring)
local rawlat, rawlong = args.latitude, args.longitude
if rawlat == '' then rawlat = nil end
if rawlong == '' then rawlong = nil end
local globe = string.lower( args.globe or extraparams:match('globe:(%a+)') or '' ) -- string: see the globedata table for accepted values
local latitude, longitude, precision, dmslatitude, dmslongitude -- latitude and longitude in decimal / dmslatitude and dmslongitude: tables withdms coords
local maplink = true -- use maplink whenever it is possible
local savegeodata = nil
if args.geodata ~= nil and args.geodata ~= '' then
savegeodata = require('Module:Yesno')(args.geodata)
end
if savegeodata == nil then -- args.geodata non renseigné ou valeur non reconnue
savegeodata = (displaytitle and mw.title.getCurrentTitle():inNamespaces(0, 14, 100))
end
 
-- II extract coordinates from Wikitext
if (rawlat or rawlong) then
if (not rawlat) or (not rawlong) then -- if latitude is provided so should be longitude
makeerror({message = i18n.coordMissing, sortkey = 'A'})
return showerrors()
end
latitude = parsedec(rawlat, 'latitude', globe)
 
if latitude then -- if latitude is decimal
longitude = parsedec(rawlong, 'longitude', globe) -- so should be longitude
precision = determinedmsprec({latitude, longitude}) -- before conversion from string to number for trailing zeros
if not latitude or not longitude then
if errorstring == '' then
makeerror({message = i18n.invalidFormat, sortkey = 'A'})
end
return showerrors()
end
dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision, globe)
latitude, longitude = tonumber(latitude), tonumber(longitude)
else -- if latitude is not decimal try to parse it as a dms string
dmslatitude, dmslongitude = p._parsedmsstring(args.latitude, 'latitude'), p._parsedmsstring(args.longitude, 'longitude')
if not dmslatitude or not dmslongitude then
return showerrors()
end
latitude, longitude = p._dms2dec(dmslatitude), p._dms2dec(dmslongitude)
end
end
 
-- III extract coordinate data from Wikidata and compare them to local data
local wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision
if wikidata == 'true' then
wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision = wikidatacoords(wikidataquery)
 
if wikidatalatitude and latitude and longitude then
local maxdistance = tonumber(args.maxdistance) or wikidatathreshold
if p._distance({latitude = latitude, longitude = longitude}, {latitude = wikidatalatitude, longitude = wikidatalongitude}, wikidataglobe) < maxdistance then
trackingstring = trackingstring .. makecat(i18n.sameaswikidata)
else
trackingstring = trackingstring .. makecat(i18n.notaswikidata)
end
end
if wikidatalatitude and not latitude then
latitude, longitude, globe, precision = wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision
dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision, globe)
trackingstring = trackingstring .. makecat(i18n.throughwikidata)
end
 
if latitude and not wikidatalatitude then
if mw.title.getCurrentTitle().namespace == 0 then
trackingstring = trackingstring .. makecat(i18n.nowikidata)
end
end
end
 
 
-- exit if stil no latitude or no longitude
if not latitude and not longitude then
return nil -- ne rien ajouter ici pour que l'appel à cette fonction retourne bien nil en l'absence de données
end
 
-- IV best guesses for missing parameters
 
--- globe
if globe == '' then
globe = 'earth'
end
if not globedata[globe] then
makeerror({message = i18n.invalidGlobe .. globe})
globe = 'earth'
end
if globe ~= 'earth' then
extraparams = extraparams .. '_globe:' .. globe -- pas de problème si le globe est en double
maplink = false
end
 
--- diplayformat
if not displayformat or displayformat == '' then
displayformat = globedata[globe].defaultdisplay
end
 
-- displayinline/displaytitle
if not displayinline and not displaytitle then
displayinline = true
if displayplace ~= '' then
makeerror({sortkey = 'C'}) --error if display not empty, but not a major error, continue
end
end
if displaytitle and mw.title.getCurrentTitle().namespace == 0 then
--local cattoappend = globedata[globe].trackingcat
--Récupération des badges
local cats = wikidatacat(globe)
for i, cat in ipairs( cats ) do
trackingstring = trackingstring .. makecat(cat)
end


--[[
end
dms2dec


Wrapper to allow templates to call dms2dec directly.
-- V geodata
local geodata = ''
if savegeodata and latitude and longitude then
local latstring, longstring = tostring(latitude), tostring(longitude)
local primary = ''


Usage:
local frame = mw.getCurrentFrame()
    {{ Invoke:Coordinates | dms2dec | direction_flag | degrees |
local geodataparams = {[1] = latstring, [2] = longstring, [3] = extraparams}
        minutes | seconds }}
if displaytitle then
   
geodataparams[4] = 'primary'
Converts DMS values specified as degrees, minutes, seconds too decimal format.
end
direction_flag is one of N, S, E, W, and determines whether the output is  
if objectname then
positive (i.e. N and E) or negative (i.e. S and W).
geodataparams.name = objectname
]]
end
function coordinates.dms2dec(frame)
geodata = frame:callParserFunction('#coordinates', geodataparams)
    globalFrame = frame
if string.find(geodata, 'error') then -- the only error that has not been caught yet is primary key
    local direction = frame.args[1]
geodata = ''
    local degrees = frame.args[2]
makeerror({sortkey='D'})
    local minutes = frame.args[3]
end
    local seconds = frame.args[4]
end
-- VI final output
local mainstring = ''
if maplink then
mainstring = buildMaplinkHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
else
mainstring = buildHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
end


    return convert_dms2dec(direction, degrees, minutes, seconds)
return mainstring .. notes .. trackingstring .. geodata .. showerrors()
end
end


--[[
function p.coord(frame) -- parses the strange parameters of Template:Coord before sending them to p.coord
coord
local args = frame.args
local numericargs = {}
for i, j in ipairs(args) do
args[i] = mw.text.trim(j)
if args[i] ~= '' then
table.insert(numericargs, args[i])
end
end
 
if #numericargs %2 == 1 then -- if the number of args is odd, the last one provides formatting parameters
args.extraparams = numericargs[#numericargs]
if #numericargs == 1 and tonumber(numericargs[1]) then
makeerror({message = i18n.coordMissing, sortkey = 'A'})
return showerrors()
end
table.remove(numericargs)
end
for i, j in ipairs(numericargs) do
if i <= (#numericargs / 2) then
if not args.latitude then
args.latitude = j
else
args.latitude = args.latitude .. '/' .. j
end
else
if not args.longitude then
args.longitude = j
else
args.longitude = args.longitude .. '/' .. j
end
end
end


Main entry point for Lua function to replace {{coord}}
if string.find(args.latitude or '', 'E') or string.find(args.latitude or '', 'W') then
args.latitude, args.longitude = args.longitude, args.latitude
end
return p._coord(args)
end


Usage:
function p.Coord(frame)
    {{ Invoke:Coordinates | coord }}
return p.coord(frame)
    {{ Invoke:Coordinates | coord | lat | long }}
    {{ Invoke:Coordinates | coord | lat | lat_flag | long | long_flag }}
    ...
   
    Refer to {{coord}} documentation page for many additional parameters and
    configuration options.
   
Note: This function provides the visual display elements of {{coord}}.  In
order to load coordinates into the database, the {{#coordinates:}} parser
function must also be called, this is done automatically in the Lua
version of {{coord}}.
]]
function coordinates.coord(frame)
    globalFrame = frame
   
    local text = ''
   
    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*$' );  --remove whitespace
        end       
    end
    args['format'] = args['format'] or '';
   
    local latdms=mw.text.split( args[1], '/' )
    local lngdms=mw.text.split( args[2], '/' )
    if #latdms > 1 or #lngdms >1 then
        -- je copie
        local argsN=mw.clone(args) 
        local i = 1 -- point de départ
        local ia = 1 -- point de départ
        while (latdms[i] ~= nil) do
          args[ia]=latdms[i]
          i = i + 1
          ia = ia +1
        end
        local i = 1 -- point de départ
        while (lngdms[i] ~= nil) do
          args[ia]=lngdms[i]
          i = i + 1
          ia = ia +1
        end
        local i = 3 -- point de départ
        while (argsN[i] ~= nil) do
          args[ia]=argsN[i]
          i = i + 1
          ia = ia +1
        end
    end     
    local contents = formatTest(args)
    local Notes = args.notes or ""
    local Display = string.lower(args.display or "inline")
    if Display == '' then
        Display = 'inline';
    end
   
    if string.find( Display, 'inline' ) ~= nil or Display == 'i' or
            Display == 'it' or Display == 'ti' then
        text = 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
end


function coordinates.Coord(frame)
function p.latitude(frame) -- helper function pour infobox, à déprécier
    return coordinates.coord(frame)
local args = frame.args
local latitude  = frame.args[1]
if latitude and mw.text.trim(latitude) ~= '' then
return latitude
elseif frame.args['wikidata'] == 'true' then
local lat, long = wikidatacoords()
return lat
end
end
function p.longitude(frame) -- helper function pour infobox, à déprécier
local args = frame.args
local longitude = frame.args[1]
if longitude and mw.text.trim(longitude) ~= '' then
return longitude
elseif frame.args['wikidata'] == 'true' then
local lat, long = wikidatacoords()
return long
end
end
end




return coordinates
return p

Dernière version du 22 février 2026 à 00:17

La documentation pour ce module peut être créée à Module:Coordinates/doc

local math_mod = require( "Module:Math" )

local p = {}

local NBSP = '\194\160' -- espace insécable (code UTF-8 sur deux octets)

local i18n = {
	N = 'N',
	Nlong = 'nord',
	W = 'O',
	Wlong = 'ouest',
	E = 'E',
	Elong = 'est',
	S = 'S',
	Slong = 'sud',
	degrees = '°' .. NBSP,
	minutes = '′' .. NBSP,
	seconds = '″' .. NBSP,
	geohackurl = 'http://tools.wmflabs.org/geohack/geohack.php?language=fr',
	tooltip = 'Cartes, vues aériennes, etc.',
	errorcat = 'Page avec des balises de coordonnées mal formées',
	sameaswikidata = 'Page avec coordonnées similaires sur Wikidata',
	notaswikidata = 'Page avec coordonnées différentes sur Wikidata',
	nowikidata = 'Page sans coordonnées Wikidata',
	throughwikidata = 'Page géolocalisée par Wikidata',
	invalidFormat = 'format invalide',                                          -- 'invalid coordinate format',
	invalidNSEW = 'orientation invalide, devrait être "N", "S", "E" or "W"',    -- 'invalid direction should be "N", "S", "E" or "W"',
	invalidNS = 'orientation de latitude invalide, devrait être "N" ou "S"',    -- 'could not find latitude direction (should be N or S)',
	invalidEW = 'orientation de longitude invalide, devrait être "E" ou "W"',   -- 'could not find longitude direction (should be W or E) ',
	noCardinalDirection = 'orientation cardinale non trouvée',                  -- 'no cardinal direction found in coordinates',
	invalidDirection = 'direction invalide',                                    -- 'invalid direction',
	latitude90 = 'latitude > 90',
	longitude360 = 'longitude > 360',
	minSec60 = 'minutes ou secondes > 60',
	negativeCoode = 'en format dms les degrés doivent être positifs',           -- 'dms coordinates should be positive',
	dmIntergers = 'degrés et minutes doivent être des nombres entiers',         -- 'degrees and minutes should be integers',
	tooManyParam = 'trop de paramètres pour la latitude ou la longitude',       -- 'too many parameters for coordinates',
	coordMissing = 'latitude ou longitude absente',                             -- 'latitude or longitude missing',
	invalidGlobe = 'globe invalide' .. NBSP .. ': ',                            -- 'invalid globe:',
}
local coordParse = {
	NORTH = 'N',
	NORD = 'N',
	EAST = 'E',
	EST = 'E',
	WEST = 'W',
	O = 'W',
	OUEST = 'W',
	SOUTH = 'S',
	SUD = 'S',
}

local globedata = 	{
	--[[ notes:
		radius in kilometers (especially imprecise for non spheric bodies)
		defaultdisplay is currently disabled, activate it ?
	]]--
	ariel =  {radius = 580, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	callisto =  {radius = 2410, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	ceres =  {radius = 470, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	charon =  {radius = 1214, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	deimos =  {radius = 7, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	dione =  {radius = 560, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	enceladus =  {radius = 255, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	ganymede =  {radius = 2634, defaultdisplay = 'dec west', trackingcat = 'sur Ganymède'},
	earth =  {radius = 6371, defaultdisplay = 'dms', trackingcat = 'sur Terre'},
	europa =  {radius = 1561, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	hyperion =  {radius = 140, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	iapetus =  {radius = 725, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	['io'] =  {radius = 1322, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	jupiter =  {radius = 68911, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	mars =  {radius = 3389.5, defaultdisplay = 'dec east', trackingcat = 'sur Mars'},
	mercury =  {radius = 2439.7, defaultdisplay = 'dec west', trackingcat = 'sur Mercure'},
	mimas =  {radius = 197, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	miranda =  {radius = 335, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	moon =  {radius = 1736, defaultdisplay = 'dec', trackingcat = 'sur la Lune'},
	neptune =  {radius = 24553, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	oberon =  {radius = 761, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	phoebe =  {radius = 110, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	phobos =  {radius = 11, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	pluto =  {radius = 1185, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	rhea =  {radius = 765, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	saturn =  {radius = 58232, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	titan =  {radius = 2575.5, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	tethys =  {radius = 530, defaultdisplay = 'dec west', trackingcat = 'extraterrestre'},
	titania =  {radius = 394, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	triton =  {radius = 1353, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	umbriel =  {radius = 584, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	uranus =  {radius = 25266, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'},
	venus =  {radius = 6051.8, defaultdisplay = 'dec east', trackingcat = 'sur Vénus'},
	vesta =  {radius = 260, defaultdisplay = 'dec east', trackingcat = 'extraterrestre'}
}
globedata[''] = globedata.earth

local wikidatathreshold = 10 -- si la distance entre coordonnées Wikipédia et Wikidata dépasse ce seuil (en kilomètres), une catégorie de maintenance est ajoutée
local lang = mw.language.getContentLanguage()
local default_zoom = 13

local function makecat(cat, sortkey)
	if type( sortkey ) == 'string' then
		return '[[Category:' .. cat .. '|' .. sortkey .. ']]'
	else
		return '[[Category:' .. cat .. ']]'
	end
end

----------------------------------------
--Error handling
	--[[ Notes:
	when errors occure a new error message is concatenated to errorstring
	an error message contains an error category with a sortkey
	For major errors, it can also display an error message (the error message will the usually be returned and the function terminated)
	More minor errors do only add a category, so that readers are not bothered with error texts
	sortkeys:
		* A: invalid latitude, longitude or direction
		* B: invalid globe
		* C: something wrong with other parameters
		* D: more than one primary coord
	]]--

local errorstring = ''

local function makeerror(args)
	local errormessage = ''
	if args.message then
		errormessage = '<strong class="error"> Coordonnées' .. NBSP .. ': ' .. args.message .. '</strong>'
	end
	local errorcat = ''
	if mw.title.getCurrentTitle().namespace == 0 then
		errorcat = makecat(i18n.errorcat, args.sortkey)
	end
	errorstring = errormessage .. errorcat -- reinitializes the string to avoid absurdly long messages
	return nil
end

local function showerrors()
	return errorstring
end



-- Distance computation
function p._distance(a, b, globe) -- calcule la [[distance orthodromique]] en kilomètres entre deux points du globe

	globe = string.lower(globe or 'earth')

	-- check arguments and converts degreees to radians
	local latA, latB, longA, longB = a.latitude, b.latitude, a.longitude, b.longitude
	if (not latA) or (not latB) or (not longA) or (not longB) then return
		error('coordinates missing, can\'t compute distance')
	end
	if type(latA) ~= 'number' or type(latB) ~= 'number' or type(longA) ~= 'number' or type(longB) ~= 'number' then
		error('coordinates are not numeric, can\'t compute distance')
	end
		if not globe or not globedata[globe] then
		return error('globe: ' .. globe .. 'is not supported')
	end

	-- calcul de la distance angulaire en radians
	local convratio = math.pi / 180 -- convertit en radians
	latA, latB, longA, longB = convratio * latA, convratio * latB, convratio * longA, convratio * longB
	local cosangle = math.sin(latA) * math.sin(latB) + math.cos(latA) * math.cos(latB) * math.cos(longB - longA)
	if cosangle >= 1 then -- may be above one because of rounding errors
		return 0
	end
	local angle = math.acos(cosangle)
	-- calcul de la distance en km
	local radius = globedata[globe].radius
	return radius * angle
end

function p.distance(frame)
	local args = frame.args
	return p._distance(
		{latitude = tonumber(args.latitude1), longitude = tonumber(args.longitude1)},
		{latitude = tonumber(args.latitude2), longitude = tonumber(args.longitude2)},
		args.globe)
end

local function geoHackUrl(decLat, decLong, globe, displayformat, objectname, extraparams)
	extraparams = extraparams or ''
	local geohacklatitude, geohacklongitude
	-- format latitude and longitude for the URL
	if tonumber(decLat) < 0 then
		geohacklatitude = tostring(-tonumber(decLat)) .. '_S'
	else
		geohacklatitude = decLat .. '_N'
	end
	if tonumber(decLong) < 0  then
		geohacklongitude = tostring(-tonumber(decLong)) .. '_W'
	elseif globedata[globe].defaultdisplay == 'dec west' then
		geohacklongitude = decLong .. '_W'
	else
		geohacklongitude = decLong .. '_E'
	end
	-- prepares the 'paramss=' parameter
	local geohackparams = geohacklatitude .. '_' .. geohacklongitude .. '_' ..extraparams
	-- concatenate parameteres for geohack
	return i18n.geohackurl ..
		"&pagename=" .. mw.uri.encode(mw.title.getCurrentTitle().prefixedText, "WIKI") ..
		"&params=" .. geohackparams ..
		(objectname and ("&title=" .. mw.uri.encode(objectname)) or "")
end

--HTML builder for a geohack link
local function buildHTML(decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
	-- geohack url
	local url = geoHackUrl(decLat, decLong, globe, displayformat, objectname, extraparams)

	-- displayed coordinates
	local displaycoords
	if string.sub(displayformat, 1, 3) == 'dec' then
		displaycoords = p.displaydec(decLat, decLong, displayformat)
	else
		displaycoords = {
			p.displaydmsdimension(dmsLat, displayformat),
			p.displaydmsdimension(dmsLong, displayformat),
		}
	end

	-- build coordinate in h-geo / h-card microformat
	local globeNode
	if globe and globe ~= 'earth' then
		globeNode = mw.html.create('data')
			:addClass('p-globe')
			:attr{ value = globe }
			:done()
	end

	local coordNode = mw.html.create('')
	if objectname then
		coordNode = mw.html.create('span')
			:addClass('h-card')
			:tag('data')
				:addClass('p-name')
				:attr{ value = objectname }
				:done()
	end
	coordNode
		:tag('span')
			:addClass('h-geo')
			:addClass('geo-' .. string.sub(displayformat, 1, 3))
			:tag('data')
				:addClass('p-latitude')
				:attr{ value = decLat }
				:wikitext( displaycoords[1] )
				:done()
			:wikitext(", ")
			:tag('data')
				:addClass('p-longitude')
				:attr{ value = decLong }
				:wikitext( displaycoords[2] )
				:done()
			:node( globeNode )
			:done()

	-- buid GeoHack link
	local root = mw.html.create('span')
		:addClass('plainlinks nourlexpansion')
		:attr('title', i18n.tooltip)
		:wikitext('[' .. url )
		:node(coordNode)
		:wikitext("]")
		:done()

	-- format result depending on args["display"] (nil, "inline", "title", "inline,title")
	local inlineText = displayinline and tostring(root) or ''
	local titleText = ''
	if displaytitle then
		local htmlTitle = mw.html.create('span')
			:attr{ id = 'coordinates' }
			:addClass( displayinline and 'noprint' or nil )
			:node( root )
		local frame = mw.getCurrentFrame()
		titleText = frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' } )
	end

	return inlineText .. titleText
end

local function zoom( extraparams )
	local zoomParam = extraparams:match( '%f[%w]zoom: ?(%d+)' )
	if zoomParam then
		return zoomParam
	end

	local scale = extraparams:match( '%f[%w]scale: ?(%d+)' )
	if scale then
		return math.floor(math.log10( 1 / tonumber( scale ) ) * 3 + 25)
	end

	local extraType = extraparams:match( '%f[%w]type: ?(%w+)' )
	if extraType then
		local zoomType = {
			country = 5,
			state = 6,
			adm1st = 7,
			adm2nd = 8,
			city = 9,
			isle = 10,
			mountain = 10,
			waterbody = 10,
			airport = 12,
			landmark = 13,
		}
		return zoomType[ extraType ]
	end
end

--HTML builder for a geohack link
local function buildMaplinkHTML( decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams )
	-- displayed coordinates
	local displaycoords
	if string.sub(displayformat, 1, 3) == 'dec' then
		displaycoords = p.displaydec(decLat, decLong, displayformat)
	else
		displaycoords = {
			p.displaydmsdimension(dmsLat, displayformat),
			p.displaydmsdimension(dmsLong, displayformat),
		}
	end

	-- JSON for maplink
	local jsonParams = {
		type = 'Feature',
		geometry = {
			type ='Point',
			coordinates = {
				math_mod._round( decLong, 6 ), -- max precision in GeoJSON format
				math_mod._round( decLat, 6 )
			}
		},
		properties = {
			['marker-color'] = "228b22",
		}
	}
	if objectname then
		jsonParams.properties.title = objectname
	end
	-- ajout de geoshape via externaldata
	local geoshape = extraparams:match( '%f[%w]geoshape: ?(Q%d+)' )
	if not geoshape and displaytitle and mw.wikibase.getEntity() then
		geoshape = mw.wikibase.getEntity().id
	end
	if geoshape then
		jsonParams = {
			jsonParams,
			{
				type = 'ExternalData',
				service = 'geoshape',
				ids = geoshape,
				properties = {
					['fill-opacity'] = 0.2
				}
			}
		}
	end

	local maplink = mw.getCurrentFrame():extensionTag{
		name = 'maplink',
		content = mw.text.jsonEncode( jsonParams ),
		args = {
			text = displaycoords[1] .. ", " .. displaycoords[2],
			zoom = zoom( extraparams ) or default_zoom,
			latitude = decLat,
			longitude = decLong,
		}
	}

	-- format result depending on args["display"] (nil, "inline", "title", "inline,title")
	local inlineText = displayinline and maplink or ''
	local titleText = ''
	if displaytitle then
		local htmlTitle = mw.html.create('span')
			:attr{ id = 'coordinates' }
			:addClass( displayinline and 'noprint' or nil )
			:wikitext( maplink )
		local frame = mw.getCurrentFrame()
		titleText = frame:extensionTag( 'indicator', tostring(htmlTitle), { name = 'coordinates' } )
	end

	return inlineText .. titleText
end

-- dms specific funcions

local function twoDigit( value )
	if ( value < 10 ) then
		value = '0' .. lang:formatNum( value )
	else
		value = lang:formatNum( value )
	end
	return value
end

function p.displaydmsdimension(valuetable, format) -- formate en latitude ou une longitude dms
	local str = ''
	local direction = valuetable.direction
	local degrees, minutes, seconds = '', '', ''
	local dimension

	if format == 'dms long' then
		direction = i18n[direction .. 'long']
	else
		direction = i18n[direction]
	end
	degrees = lang:formatNum( valuetable.degrees ) .. i18n.degrees

	if valuetable.minutes then
		minutes = twoDigit( valuetable.minutes ) .. i18n.minutes
	end
	if valuetable.seconds then
		seconds = twoDigit( valuetable.seconds ) .. i18n.seconds
	end
	return degrees .. minutes .. seconds .. direction
end

local function validdms(coordtable)
	local direction = coordtable.direction
	local degrees = coordtable.degrees or 0
	local minutes = coordtable.minutes or 0
	local seconds = coordtable.seconds or 0
	local dimension = coordtable.dimension
	if not dimension then
		if direction == 'N' or direction == 'S' then
			dimension = 'latitude'
		elseif direction == 'E' or direction == 'W' then
			dimension = 'longitude'
		else
			makeerror({message = i18n.invalidNSEW, sortkey = 'A'})
			return false
		end
	end

	if type(degrees) ~= 'number' or type(minutes) ~= 'number' or type(seconds) ~= 'number' then
		makeerror({message = i18n.invalidFormat, sortkey = 'A'})
		return false
	end

	if dimension == 'latitude' and direction ~= 'N' and direction ~= 'S' then
		makeerror({message = i18n.invalidNS, sortkey = 'A'})
		return false
	end
	if dimension == 'longitude' and direction ~= 'W' and direction ~= 'E' then
		makeerror({message = i18n.invalidEW, sortkey = 'A'})
		return false
	end

	if dimension == 'latitude' and degrees > 90 then
		makeerror({message = i18n.latitude90, sortkey = 'A'})
		return false
	end

	if dimension == 'longitude' and degrees > 360 then
		makeerror({message = i18n.longitude360, sortkey = 'A'})
		return false
	end

	if degrees < 0 or minutes < 0 or seconds < 0 then
		makeerror({message = i18n.negativeCoode, sortkey = 'A'})
		return false
	end

	if minutes > 60 or seconds > 60 then
		makeerror({message = i18n.minSec60, sortkey = 'A'})
		return false
	end
	if (math.floor(degrees) ~= degrees and minutes ~= 0) or (math.floor(minutes) ~= minutes and seconds ~= 0) then
		makeerror({message = i18n.dmIntergers, sortkey = 'A'})
		return false
	end
	return true
end

local function builddmsdimension(degrees, minutes, seconds, direction, dimension)
	-- no error checking, done in function validdms
	local dimensionobject = {}

	-- direction and dimension (= latitude or longitude)
	dimensionobject.direction = direction
	if dimension then
		dimensionobject.dimension = dimension
	elseif direction == 'N' or direction == 'S' then
		dimensionobject.dimension = 'latitude'
	elseif direction == 'E' or direction == 'W' then
		dimensionobject.dimension = 'longitude'
	end

	-- degrees, minutes, seconds
	dimensionobject.degrees = tonumber(degrees)
	dimensionobject.minutes = tonumber(minutes)
	dimensionobject.seconds = tonumber(seconds)
	if degrees and not dimensionobject.degrees then dimensionobject.degrees = 'error' end
	if minutes and not dimensionobject.minutes then dimensionobject.minutes = 'error' end
	if seconds and not dimensionobject.seconds then dimensionobject.seconds = 'error' end
	return dimensionobject
end

function p._parsedmsstring( str, dimension ) -- prend une séquence et donne des noms aux paramètres
	-- output table: { latitude=, longitude = , direction =  }
	if type( str ) ~= 'string' then
		return nil
	end
	str = mw.ustring.gsub( mw.ustring.upper( str ), '%a+', coordParse )
	if not tonumber( str ) and not str:find( '/' ) and str:find( '°' ) then
		local str2 = mw.ustring.gsub( str, '[°″′\"\'\194\160 ]+', '/' )
		-- avoid cases were there is degree ans seconds but no minutes
		if not mw.ustring.find( str, '[″"]' ) or mw.ustring.find( str, '%d[′\'][ \194\160%d]' ) then
			str = str2
		end
	end
	if not tonumber(str) and not string.find(str, '/') then
		makeerror({message = i18n.invalidFormat, sortkey = 'A'})
		return nil
	end
	local args = mw.text.split(str, '/', true)
	if #args > 4 then
		makeerror({message = i18n.tooManyParam, sortkey = 'A'})
	end
	local direction = mw.text.trim(args[#args])
	table.remove(args)
	local degrees, minutes, seconds = args[1], args[2], args[3]
	local dimensionobject = builddmsdimension(degrees, minutes, seconds, direction, dimension)
	if validdms(dimensionobject) then
		return dimensionobject
	else
		return nil
	end
end

--- decimal specific functions
function p.displaydec(latitude, longitude, format)
	local lat = lang:formatNum( latitude )
	local long = lang:formatNum( longitude )

	if format == 'dec west' or  format == 'dec east' then
		local symbolNS, symbolEW = i18n.N, i18n.E
		if latitude < 0 then
			symbolNS = i18n.S
			lat = lang:formatNum( -latitude )
		end
		if format == 'dec west' then
			symbolEW = i18n.W
		end
		if longitude < 0 then
			long = lang:formatNum( 360 + longitude )
		end

		return { lat .. i18n.degrees .. symbolNS,  long ..  i18n.degrees .. symbolEW }

	else
		return { lat, long }
	end
end


local function parsedec(dec, coordtype, globe) -- coordtype = latitude or longitude
	dec = mw.text.trim(dec)
	if not dec then
		return nil
	end
	if coordtype ~= 'latitude' and coordtype ~= 'longitude' then
		makeerror({'invalid coord type', sortkey = "A"})
		return nil
	end
	local numdec = tonumber(dec) -- numeric value, kept separated as it looses significant zeros
	if not numdec then -- tries the decimal + direction format
		dec = mw.ustring.gsub( mw.ustring.upper( dec ), '%a+', coordParse )
		local direction = mw.ustring.sub(dec, mw.ustring.len(dec), mw.ustring.len(dec))
		dec = mw.ustring.sub(dec, 1, mw.ustring.len(dec)-2) -- removes the /N at the end
		if not dec or not tonumber(dec) then
			return nil
		end
		if direction == 'N' or direction == 'E' or direction == 'W' and globedata[globe].defaultdisplay == 'dec west' then
			numdec = tonumber( dec )
		elseif direction == 'W' or direction == 'S' then
			dec = '-' .. dec
			numdec = tonumber( dec )
		else
			if coordtype == 'latitude' then
				makeerror({message = i18n.invalidNS, sortkey = 'A'})
			else
				makeerror({message = i18n.invalidEW, sortkey = 'A'})
			end
			return nil
		end
	end

	if coordtype == 'latitude' and math.abs(numdec) > 90 then
		makeerror({message = i18n.latitude90 , sortkey = 'A'})
		return nil
	end
	if coordtype == 'longitude' and math.abs(numdec) > 360 then
		makeerror({message = i18n.longitude360 , sortkey = 'A'})
		return nil
	end
	return dec
end

-- dms/dec conversion functions
local function convertprecision(precision) -- converts a decimal precision like "2" into "dm"
	if precision >= 3 then
		return 'dms'
	elseif precision >=1 then
		return 'dm'
	else
		return 'd'
	end
end

local function determinedmsprec(decs) -- returns the most precision for a dec2dms conversion, depending on the most precise value in the decs table
	local precision = 0
	for d, val in ipairs(decs) do
		precision = math.max(precision, math_mod._precision(val))
	end
	return convertprecision(precision)
end

local function dec2dms_d(dec)
	local degrees = math_mod._round( dec, 0 )
	return degrees
end

local function dec2dms_dm(dec)
	dec = math_mod._round( dec * 60, 0 )
	local minutes = dec % 60
	dec = math.floor( (dec - minutes) / 60 )
	local degrees = dec % 360
	return degrees, minutes
end

local function dec2dms_dms(dec)
	dec = math_mod._round( dec * 60 * 60, 0 )
	local seconds = dec % 60
	dec = math.floor( (dec - seconds) / 60 )
	local minutes = dec % 60
	dec = math.floor( (dec - minutes) / 60 )
	local degrees = dec % 360
	return degrees, minutes, seconds
end

function p._dec2dms(dec, coordtype, precision, globe) -- coordtype: latitude or longitude
	local degrees, minutes, seconds

	-- vérification du globe
	if not ( globe and globedata[ globe ] ) then
		globe = 'earth'
	end

	-- precision
	if not precision or precision == '' then
		precision = determinedmsprec({dec})
	end
	if precision ~= 'd' and precision ~= 'dm' and precision ~= 'dms' then
		return makeerror({sortkey = 'C'})
	end
	local dec = tonumber(dec)

	-- direction
	local direction
	if coordtype == 'latitude' then
		if dec < 0 then
			direction = 'S'
		else
			direction = 'N'
		end
	elseif coordtype == 'longitude' then
		if dec < 0 or globedata[globe].defaultdisplay == 'dec west' then
			direction = 'W'
		else
			direction = 'E'
		end
	end

	-- conversion
	dec = math.abs(dec) -- les coordonnées en dms sont toujours positives
	if precision == 'dms' then
		degrees, minutes, seconds = dec2dms_dms(dec)
	elseif precision == 'dm' then
		degrees, minutes = dec2dms_dm(dec)
	else
		degrees = dec2dms_d(dec)
	end
	return builddmsdimension(degrees, minutes, seconds, direction)
end

function p.dec2dms(frame) -- legacy function somewhat cumbersome syntax
	local args = frame.args
	local dec = args[1]
	if not tonumber(dec) then
		makeerror({message = i18n.invalidFormat, sortkey = 'A'})
		return showerrors()
	end
	local dirpositive = string.lower(args[2] or '')
	local dirnegative = string.lower(args[3] or '')
	local precision = string.lower(args[4] or '')
	local displayformat, coordtype

	if dirpositive == 'n' or dirpositive == 'nord' then
		coordtype = 'latitude'
	else
		coordtype = 'longitude'
	end
	if dirpositive == 'nord' or dirpositive == 'est' or dirnegative == 'ouest' or dirnegative == 'sud' then
		displayformat = 'dms long'
	end
	local coordobject = p._dec2dms(dec, coordtype, precision)
	if coordobject then
		return p.displaydmsdimension(coordobject, displayformat) .. showerrors()
	else
		return showerrors()
	end
end


function p._dms2dec(dmsobject) -- transforme une table degré minute secondes en nombre décimal
	local direction, degrees, minutes, seconds = dmsobject.direction, dmsobject.degrees, dmsobject.minutes, dmsobject.seconds
	local factor = 0
	local precision = 0
	if not minutes then minutes = 0 end
	if not seconds then seconds = 0 end

	if direction == "N" or direction == "E" then
		factor = 1
	elseif direction == "W" or direction == "S" then
		factor = -1
	elseif not direction then
		makeerror({message = i18n.noCardinalDirection, sortkey = 'A'})
		return nil
	else
		makeerror({message = i18n.invalidDirection, sortkey = 'A'})
		return nil
	end

	if dmsobject.seconds then -- vérifie la précision des données initiales
		precision = 5 + math.max( math_mod._precision(tostring(seconds), 0 ) ) -- passage par des strings assez tarabiscoté ?
	elseif dmsobject.minutes then
		precision = 3 + math.max( math_mod._precision(tostring(minutes), 0 ) )
	else
		precision = math.max( math_mod._precision(tostring(degrees), 0 ) )
	end

	local decimal = factor * (degrees+(minutes+seconds/60)/60)
	return math_mod._round(decimal, precision)
end

function p.dms2dec(frame) -- legacy function, somewhat bizarre syntax
	local args = frame.args
	if tonumber(args[1]) then
		return args[1] -- coordonnées déjà en décimal
	elseif not args[2] then
		local dmsobject = p._parsedmsstring(args[1])
		if dmsobject then
			return p._dms2dec(dmsobject) -- coordonnées sous la fore 23/22/N
		else
			local coordType
			if args[1]:match( '[NS]' ) then
				coordType = 'latitude'
			elseif args[1]:match( '[EWO]') then
				coordType = 'longitude'
			end
			if coordType then
				local result = parsedec( args[1],  coordType, args.globe or 'earth' )
				if result then
					return result
				end
			end
			return showerrors()
		end
	else
		return p._dms2dec({direction = args[1], degrees = tonumber(args[2]), minutes = tonumber(args[3]), seconds = tonumber(args[4])})
	end
end

-- Wikidata
local function convertwikidataprecision(precision) -- converts a decima like "0.1" into "dm"
	if precision < 0.016 then
		return 'dms'
	elseif precision < 1 then
		return 'dm'
	else
		return 'd'
	end
end

local function wikidatacoords(query)
	query = query or {property = 'p625'}
	query.formatting = 'raw'
	local wd = require('Module:Wikidata')
	local claim = wd.getClaims(query)
	if claim and claim[1] then -- redundant but more robust in case of a change in the code of Module:Wikidata
		local coords = wd.formatSnak(claim[1].mainsnak) -- todo: check for special values
		-- Wikidata does not handle correctly +West longitudes
		if globedata[ coords.globe ] and globedata[ coords.globe ].defaultdisplay == 'dec west' then
			coords.longitude = math.abs( coords.longitude )
		end
		return coords.latitude, coords.longitude, coords.globe or 'earth', convertwikidataprecision(coords.precision or .001)
	end
	return nil
end


local function wikidatacat(globe)
	--catbase= Article géolocalisé sur Terre
	local entitycat = mw.wikibase.getEntity()

	local basecat = 'Article géolocalisé'
	local finalcat = {}
	--BADGES
	if entitycat then
		--BADGES
	   	for i, badgeId in ipairs( entitycat.sitelinks['frwiki'].badges ) do
			if badgeId == 'Q17437796'  then
				basecat = string.gsub(basecat, "Article géolocalisé", "Article de qualité géolocalisé")
			end
			if badgeId == 'Q17437798'  then
				basecat = string.gsub(basecat, "Article géolocalisé", "Bon article géolocalisé")
			end
		end
	end

	if globe == 'earth'  then
		if entitycat and entitycat.claims  then
			local country = entitycat.claims['P17']
			if not country then
				--pas pays à récupérer
				basecat = basecat .. ' sur Terre'
				table.insert(finalcat, basecat)
			else
				--parfois plusieurs pays
				for i, paysId in ipairs( country ) do
					--on fait confiance au label wikidata
					local gdataone, qid

					if paysId.mainsnak.snaktype == 'value' then
						qid = paysId.mainsnak.datavalue.value['numeric-id']
						gdataone = mw.loadData("Module:Drapeau/Data").data[qid]
					else
						--Bir Tawil n'a pas de pays connu
						qid = '?'
					end
					if gdataone ~= nil then
						local genre = mw.loadData("Module:Drapeau/Domaine").genre
						local prep = genre[gdataone['genre']]['en'] or 'en '
						local thecat = basecat .. ' '..prep ..mw.wikibase.label('Q'.. qid)
						if mw.title.new('category:'..thecat).exists then
							table.insert(finalcat, thecat)
						else
							--Dommage!
							mw.log(thecat .. ' à créer')
						end
					else
						--pas d'id?
						mw.log(qid .. ' à paramétrer')
					end
				end
				if #finalcat == 0 then
					--pas pays à récupérer
					basecat = basecat .. ' sur Terre'
					table.insert(finalcat, basecat)
				end
			end
		else
			--pas wikidata
			basecat = basecat .. ' sur Terre'
			table.insert(finalcat, basecat)
		end
	elseif globedata[globe] then
		basecat = basecat .. ' ' .. globedata[globe].trackingcat
		table.insert(finalcat, basecat)
	else
		basecat = basecat .. ' extraterrestre'
		table.insert(finalcat, basecat)
	end
	return finalcat
end

 -- main function for displaying coordinates
function p._coord(args)

	-- I declare variable
	local displayformat = args.format -- string: one of: 'dms', 'dms long', 'dec', 'dec east' and 'dec west'
	local displayplace = string.lower(args.display or 'inline') --string: one of 'inline', 'title' or 'inline,title'
	local displayinline = string.find(displayplace, 'inline') and true or false
	local displaytitle = string.find(displayplace, 'title') and true or false
	local objectname = (args.name ~= '') and args.name -- string: name of the title displayed in geohack
	local notes = (' ' and args.notes) or '' -- string: notes to de displayed after coordinates
	local wikidata = args.wikidata -- string: set to "true" if needed
	local wikidataquery = args.wikidataquery -- table: see [[Module:Wikidata]] see function wikidatacoords
	local dmslatitude, dmslongitude -- table (when created)
	local extraparams = args.extraparams or '' -- string (legacy, corresponds to geohackparams)
 	local trackingstring = '' -- tracking cats except error cats (already in errorstring)
 	local rawlat, rawlong = args.latitude, args.longitude
 	if rawlat == '' then rawlat = nil end
 	if rawlong == '' then rawlong = nil end
 	local globe = string.lower( args.globe or extraparams:match('globe:(%a+)') or '' ) -- string: see the globedata table for accepted values
	local latitude, longitude, precision, dmslatitude, dmslongitude -- latitude and longitude in decimal / dmslatitude and dmslongitude: tables withdms coords
	local maplink = true -- use maplink whenever it is possible
	local savegeodata = nil
	if args.geodata ~= nil and args.geodata ~= '' then
		savegeodata = require('Module:Yesno')(args.geodata)
	end
	if savegeodata == nil then -- args.geodata non renseigné ou valeur non reconnue
		savegeodata = (displaytitle and mw.title.getCurrentTitle():inNamespaces(0, 14, 100))
	end

	-- II extract coordinates from Wikitext
	if (rawlat or rawlong) then
		if (not rawlat) or (not rawlong) then -- if latitude is provided so should be longitude
			makeerror({message = i18n.coordMissing, sortkey = 'A'})
			return showerrors()
		end
		latitude = parsedec(rawlat, 'latitude', globe)

		if latitude then -- if latitude is decimal
			longitude = parsedec(rawlong, 'longitude', globe) -- so should be longitude
			precision = determinedmsprec({latitude, longitude}) -- before conversion from string to number for trailing zeros
			if not latitude or not longitude then
				if errorstring == '' then
					makeerror({message = i18n.invalidFormat, sortkey = 'A'})
				end
				return showerrors()
			end
			dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision, globe)
			latitude, longitude = tonumber(latitude), tonumber(longitude)
		else -- if latitude is not decimal try to parse it as a dms string
			dmslatitude, dmslongitude = p._parsedmsstring(args.latitude, 'latitude'), p._parsedmsstring(args.longitude, 'longitude')
			if not dmslatitude or not dmslongitude then
				return showerrors()
			end
			latitude, longitude = p._dms2dec(dmslatitude), p._dms2dec(dmslongitude)
		end
	end

	-- III extract coordinate data from Wikidata and compare them to local data
	local wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision
	if wikidata == 'true' then
		wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision = wikidatacoords(wikidataquery)

		if wikidatalatitude and latitude and longitude then
			local maxdistance = tonumber(args.maxdistance) or wikidatathreshold
			if p._distance({latitude = latitude, longitude = longitude}, {latitude = wikidatalatitude, longitude = wikidatalongitude}, wikidataglobe) < maxdistance then
				trackingstring = trackingstring .. makecat(i18n.sameaswikidata)
					else
				trackingstring = trackingstring .. makecat(i18n.notaswikidata)
			end
		end
		if wikidatalatitude and not latitude then
			latitude, longitude, globe, precision = wikidatalatitude, wikidatalongitude, wikidataglobe, wikidataprecision
			dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision, globe)
			trackingstring = trackingstring .. makecat(i18n.throughwikidata)
		end

		if latitude and not wikidatalatitude then
			if mw.title.getCurrentTitle().namespace == 0 then
				trackingstring = trackingstring .. makecat(i18n.nowikidata)
			end
		end
	end


	-- exit if stil no latitude or no longitude
	if not latitude and not longitude then
		return nil -- ne rien ajouter ici pour que l'appel à cette fonction retourne bien nil en l'absence de données
	end

	-- IV best guesses for missing parameters

	--- globe
	if globe == '' then
		globe = 'earth'
	end
	if not globedata[globe] then
		makeerror({message = i18n.invalidGlobe .. globe})
		globe = 'earth'
	end
	if globe ~= 'earth' then
		extraparams = extraparams .. '_globe:' .. globe -- pas de problème si le globe est en double
		maplink = false
	end

	--- diplayformat
	if not displayformat or displayformat == '' then
		displayformat = globedata[globe].defaultdisplay
	end

	-- displayinline/displaytitle
	if not displayinline and not displaytitle then
		displayinline = true
		if displayplace ~= '' then
			makeerror({sortkey = 'C'}) --error if display not empty, but not a major error, continue
		end
	end
	if displaytitle and mw.title.getCurrentTitle().namespace == 0 then
		--local cattoappend = globedata[globe].trackingcat
		--Récupération des badges
		local cats = wikidatacat(globe)
		for i, cat in ipairs( cats ) do
			trackingstring = trackingstring .. makecat(cat)
		end

	end

-- V geodata
	local geodata = ''
	if savegeodata and latitude and longitude then
		local latstring, longstring = tostring(latitude), tostring(longitude)
		local primary = ''

		local frame = mw.getCurrentFrame()
		local geodataparams = {[1] = latstring, [2] = longstring, [3] = extraparams}
		if displaytitle then
			geodataparams[4] = 'primary'
		end
		if objectname then
			geodataparams.name = objectname
		end
		geodata = frame:callParserFunction('#coordinates', geodataparams)
		if string.find(geodata, 'error') then -- the only error that has not been caught yet is primary key
			geodata = ''
			makeerror({sortkey='D'})
		end
	end
-- VI final output
	local mainstring = ''
	if maplink then
		mainstring = buildMaplinkHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
	else
		mainstring = buildHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
	end

	return mainstring .. notes .. trackingstring .. geodata .. showerrors()
end

function p.coord(frame) -- parses the strange parameters of Template:Coord before sending them to p.coord
	local args = frame.args
	local numericargs = {}
	for i, j in ipairs(args) do
		args[i] = mw.text.trim(j)
		if args[i] ~= '' then
			table.insert(numericargs, args[i])
		end
	end

	if #numericargs %2 == 1 then -- if the number of args is odd, the last one provides formatting parameters
		args.extraparams = numericargs[#numericargs]
		if #numericargs == 1 and tonumber(numericargs[1]) then
			makeerror({message = i18n.coordMissing, sortkey = 'A'})
			return showerrors()
		end
		table.remove(numericargs)
	end
	for i, j in ipairs(numericargs) do
		if i <= (#numericargs / 2) then
			if not args.latitude then
				args.latitude = j
			else
				args.latitude =	args.latitude .. '/' .. j
			end
		else
			if not args.longitude then
				args.longitude = j
			else
				args.longitude = args.longitude .. '/' .. j
			end
		end
	end

	if string.find(args.latitude or '', 'E') or string.find(args.latitude or '', 'W') then
		args.latitude, args.longitude = args.longitude, args.latitude
	end
	return p._coord(args)
end

function p.Coord(frame)
	return p.coord(frame)
end

function p.latitude(frame) -- helper function pour infobox, à déprécier
	local args = frame.args
	local latitude  = frame.args[1]
	if latitude and mw.text.trim(latitude) ~= '' then
		return latitude
	elseif frame.args['wikidata'] == 'true' then
		local lat, long = wikidatacoords()
		return lat
	end
end
function p.longitude(frame) -- helper function pour infobox, à déprécier
	local args = frame.args
	local longitude = frame.args[1]
	if longitude and mw.text.trim(longitude) ~= '' then
		return longitude
	elseif frame.args['wikidata'] == 'true' then
		local lat, long = wikidatacoords()
		return long
	end
end


return p