Module:Coordinates

De Bibliothèque Lucas Lhardi
Sauter à la navigation Sauter à la recherche

local math_mod = require( "Module:Math" )

local p = {}

--Chargement de la liste En/Au/Aux/A local gdata local success, resultat = pcall (mw.loadData, "Module:Drapeau/Data" ) if success then gdata = resultat else -- Banque de données à minima en cas de bogue dans le Module:Langue/Data gdata={} gdata.data={}; gdata.data[142]={qid="Q142", label="France", genre="fs"} end

local i18n = { N = 'N', Nlong = 'nord', W = 'O', Wlong = 'ouest', E = 'E', Elong = 'est', S = 'S', Slong = 'sud', degrees = '° ', minutes = '′ ', seconds = '″ ', 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 : ', -- 'invalid globe:', } local coordParse = { NORTH = 'N', NORD = 'N', EAST = 'E', EST = 'E', WEST = 'W', O = 'W', OUEST = 'W', SOUTH = 'S', SUD = 'S', }

--Aide:Fonction_genre local genre = { ms = {le="le ", du="du ", de="du ", au="au ", en="au "}, msa = {le="l'", du="de l'", de="d'", au="à l'", en="en "}, msi = {le="", du="de ", de="de ", au="à ", en="à "}, msia = {le="", du="d'", de="d'", au="à ", en="à "}, msiae = {le="", du="d'", de="d'", au="à ", en="en "}, fs = {le="la ", du="de la ", de="de ", au="à la ",en="en "}, fsa = {le="l'", du="de l'", de="d'", au="à l'", en="en "}, fsi = {le="", du="de ", de="de ", au="à ", en="à "}, fsia = {le="", du="d'", de="d'", au="à ", en="à "}, mp = {le="les ", du="des ", de="des ", au="aux ", en="aux "}, fp = {le="les ", du="des ", de="des ", au="aux ", en="aux "} }

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 se seuil (en kilomètes), 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 else return 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 = ' Coordonnées : ' .. args.message .. '' 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 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) lat = lang:formatNum( latitude ) 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 = lat:sub( 2 ) 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 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=gdata.data[qid] else --Bir Tawil n'a pas de pays connu qid='?' end if gdataone ~= nil then 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 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

-- 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 local displayinline = string.find(displayplace, 'inline') local displaytitle = string.find(displayplace, 'title') if not displayinline and not displaytitle then displayinline = true if displayplace ~= then makeerror({sortkey = 'C'}) --error if display not empty, but not 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 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) -- parrses 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 type(i) == 'number' and 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