Hopp til innhold

Modul:Coordinates

Fra Wikipedia, den frie encyklopedi
Sideversjon per 31. des. 2018 kl. 21:52 av Danmichaelo (diskusjon | bidrag) (Wikidata-modulen vår var ikke akkurat den samme som den franske, nei… Siden vi bare skal hente ut koordinater lager jeg bare en lokal funksjon og kutter én avhengighet)
Moduldokumentasjon

Coordinates er en Lua-modul som formaterer koordinater for visning enten i løpetekst i tittelområdet (minikartet oppe til høyre). Den lager også koordinater til #coordinates-parserfunksjonen.

Modulen henter koordinater fra Wikidata. Hvis koordinater finnes både lokalt og på Wikidata vil modulen sammenligne dem og putte siden i en av kategoriene Kategori:Sider med koordinater som samsvarer med Wikidata (0) eller Kategori:Sider med koordinater som avviker fra Wikidata (0).

Bruk

Eksporte funksjoner :

  • coordinates.coord(frame) – formaterer geografiske koordinater for visning enten i løpetekst eller i

statusindikatorområdet (oppe til høyre). Lager også koordinater til #coordinates-parserfunksjonen.

  • p.dec2dms(frame) – konverterer koordinater uttrykt i titallsystemet til seksagesimalsystemet
  • p.dms2dec(frame) – konverterer koordinater uttrykt i seksagesimalsystemet til titallsystemet
  • p.latitude(frame) – henter ut breddegrad (fra lokale malparametre eller fra Wikidata). Spesielt nyttig for infobokser
  • p.longitude(frame) – henter ut lengdegrad (fra lokale malparametre eller fra Wikidata). Spesielt nyttig for infobokser
  • p.distance(frame) – beregner avstanden mellom to punkter
  • p._coord(args) – funksjon ála p.coord til bruk i andre Lua-moduler
  • p._dms2dec(dmsobject) – funksjon ála p.dms2dec til bruk i andre Lua-moduler
  • p._dec2dms(coordtype, precision) – funksjon ála p.dec2dms til bruk i andre Lua-moduler
  • p._distance(a, b, globe) – funksjon ála p.distance til bruk i andre Lua-moduler
  • p._parsedmsstring(str, dimension) - lager en dms-tabell fra en streng av typen "48/22/16/W".

Interne funksjoner:

  • makeerror - lager feilmeldinger
  • buildHTML - formaterer resultatet for p.coord() som en GeoHack-lenke
  • buildMaplinkHTML - formaterer resultatet for p.coord() som en maplink-lenke
  • displaydmsdimension - gjør om en dms-tabell som inneholder grader, minutter, sekunder, himmelretning og koordtype (= breddegrad eller lengdegrad) til en streng av typen 48° 29'32 "N
  • validdms - sjekker at en dms-tabell er gyldig (gyldig himmelretning og gyldig koordtype, gyldige verdier for grader, minutter og sekunder)
  • builddmsdimension - lager en dms-tabell
  • displaydec - gjør om en desimalbreddegrad og en desimallengdegrad til en streng av typen "34.294, 12.321"
  • parsedec - tolker og validerer desimalkoordinater
  • convertprcision - gjør om presisjonen funnet av Module:Math.precision til "d", "dm" eller "dms"
  • convertwikidataprecision - gjør om presisjonen fra Wikidata til "d", "dm" eller "dms"
  • determinedmsprec - beregner graden av presisjon som passer best for gitt desimalkoordinater
  • dec2dms_d - konverterer et desimalt koordinat til dms med presisjon på gradnivå
  • dec2dms_dm - konverterer et desimalt koordinat til dms med presisjon på minuttnivå
  • dec2dms_dms - konverterer et desimalt koordinat til dms med presisjon på sekundnivå
  • wikidatacoords - henter koordinater fra Wikidata

Interne variabler:

  • wikidatathreshold : For sider som har koordinater både lokalt og på Wikidata: Hvis koordinatene avviker med mer enn denne avstanden (i kilometer),

havner siden i Kategori:Sider med koordinater som avviker fra Wikidata. Ellers havner den i Kategori:Sider med koordinater som samsvarer med Wikidata.

Modulavhengigheter:

  • Module:Math – For å håndtere avrunding og presisjon

Grunleggende eksempler

Grunnleggende sett støtter modulen tre måter å angi geografiske koordinater på:

  1. Med desimalgrader:
  2. Med tradisjonell angivelse:
  1. Fra d:Property:P625 på Wikidata (hvis artikkelsubjektet har flere koordinater brukes det første settet)
  2. * {{#invoke:Coordinates | coord | wikidata=true}}

Som standard er utdataformatet det samme som inngangsformatet, men det kan endres ved å sende et ekstra argument |format=xxx

  1. dms for tradisjonell angivelse med grader, minutter og sekunder
  2. dms long for tradisjonell angivelse med grader, minutter og sekunder samt himmelretning fullt utskrevet
  3. dec for desimalgrader:

Valg for funksjonen p.coord

Option de la fonction p.coord (utilisable depuis Lua)

  • latitude =
  • longitude =
  • globe = (planet, hvis annen planet enn jorda)
  • format = 'dms', 'dec' eller 'dms long'
  • displaytitle = "true" for å vise koordinater i statusindikatorområdet (tittel)
  • formattitle = hvis koordinatene i statusindikatorområdet skal ha et bestemt format
  • wikidata = "true" for å hente koordinater fra Wikidata
  • wikidataprop = Wikidata-egenskap som skal brukes, standard er P625

Detaljerte eksempler

Desimalgrader

Kode Resultat Resultat
|format=dec
Resultat
|format=dms
Resultat
|format=dms long
Notes
{{#invoke:Coordinates|coord|43.651234|-79.383333}} 43° 39′ 04″ N, 79° 23′ 00″ V 43,651234, −79,383333 43° 39′ 04″ N, 79° 23′ 00″ V 43° 39′ 04″ nord, 79° 23′ 00″ vest Toronto
{{#invoke:Coordinates|coord|-33.856111|151.1925}} 33° 51′ 22″ S, 151° 11′ 33″ Ø −33,856111, 151,1925 33° 51′ 22″ S, 151° 11′ 33″ Ø 33° 51′ 22″ sør, 151° 11′ 33″ øst Sydney
{{#invoke:Coordinates|coord|43.65|-79.38}} 43° 39′ N, 79° 23′ V 43,65, −79,38 43° 39′ N, 79° 23′ V 43° 39′ nord, 79° 23′ vest Toronto, med lavere presisjon
{{#invoke:Coordinates|coord|43.6500|-79.3800}} 43° 39′ 00″ N, 79° 22′ 48″ V 43,65, −79,38 43° 39′ 00″ N, 79° 22′ 48″ V 43° 39′ 00″ nord, 79° 22′ 48″ vest Toronto, med flere nuller for å angi høyere presisjon
{{#invoke:Coordinates|coord|43.651234|N|79.383333|W}} 43° 39′ 04″ N, 79° 23′ 00″ V 43,651234, −79,383333 43° 39′ 04″ N, 79° 23′ 00″ V 43° 39′ 04″ nord, 79° 23′ 00″ vest Toronto, med N/W i stedet for +/-

Tradisjonell angivelse

Kode Resultat Resultat
|format=dec
Resultat
|format=dms
Resultat
|format=dms long
Notes
{{#invoke:Coordinates|coord|43|39|N|79|23|W}} 43° 39′ N, 79° 23′ V 43,65, −79,383 43° 39′ N, 79° 23′ V 43° 39′ nord, 79° 23′ vest Toronto, med grader og minutter
{{#invoke:Coordinates|coord|43|39|4|N|79|23|0|W}} 43° 39′ 04″ N, 79° 23′ 00″ V 43,65111, −79,38333 43° 39′ 04″ N, 79° 23′ 00″ V 43° 39′ 04″ nord, 79° 23′ 00″ vest Toronto, med grader, minutter og sekunder
{{#invoke:Coordinates|coord|43|39|4.5|N|79|23|0.5|W}} 43° 39′ 04,5″ N, 79° 23′ 00,5″ V 43,65125, −79,383472 43° 39′ 04,5″ N, 79° 23′ 00,5″ V 43° 39′ 04,5″ nord, 79° 23′ 00,5″ vest Toronto, med grader, minutter, sekunder og sekunddeler
{{#invoke:Coordinates|coord|43/39/N|79/23/W}} 43° 39′ N, 79° 23′ V 43,65, −79,383 43° 39′ N, 79° 23′ V 43° 39′ nord, 79° 23′ vest Toronto, med hvert koordinat samlet i et felt hver

Enkelte av de gamle GeoHack-parametrene støttes, men ikke alle.

  • Zoomnivå for kartet kan settes enten med zoom: (fra 0 til 19).

Alternativt kan type: brukes med et sett av forhåndsdefinerte verdier (type:city gir for eksempel zoomnivå 9). Det gamle GeoHack-argumentet scale: er også støttet, det konverteres til zoomnivå internt. Det gamle GeoHack-argumentet dim: er ikke støttet.

  • region: er ikke støttet av maplink (enda???). Det betyr at vi ikke kan bruke region:NO for å angi at Norgeskart skal dukke opp i lista over eksterne kart f.eks.
  • Flere parametre skilles med understrek.

Trykk på lenkene under for å se resultatet av de forskjellige verdiene

Parameter Eksempel Resultat Notes
{{#invoke:Coordinates|coord|43.65|-79.38}} 43° 39′ N, 79° 23′ V Toronto, standardvisning
zoom: {{#invoke:Coordinates|coord|43.65|-79.38|zoom:5}} 43° 39′ N, 79° 23′ V Toronto, med zoomnivå 5 for å vise hele landet i kartvisningen
scale: {{#invoke:Coordinates|coord|43.65|-79.38|scale:3000000}} 43° 39′ N, 79° 23′ V Toronto, med skala 3000000 for å vise hele landet i kartvisningen
dim: {{#invoke:Coordinates|coord|40.6892|-74.0445|dim:100}} 40° 41′ 21″ N, 74° 02′ 40″ V Frihetsgudinnen, med dimensjon 100 for å gi en passende skala (ikke støttet)
type: {{#invoke:Coordinates|coord|43.65|-79.38|type:city}} 43° 39′ N, 79° 23′ V Toronto, med en skala som typisk passer for en by (type:city tilsvarer zoomnivå 9)
region: {{#invoke:Coordinates|coord|43.65|-79.38|region:CA}} 43° 39′ N, 79° 23′ V Toronto, ved å angi region:CA kan det vises karttjenester som er spesielt tilpasset Canada (ikke støttet)
globe: {{#invoke:Coordinates|coord|9.7|-20.0|globe:moon}} 9,7, −20 Copernicus (månekrater), med kartlag for månen
name= {{#invoke:Coordinates|coord|43.65|-79.38|name=Toronto}} 43° 39′ N, 79° 23′ V Toronto, med et navn som vises når du trykker på pekeren

Visning i statusindikatorområdet (tittellinjen)

Bruk |display= for å endre hvor koordinatene vises:

  • {{#invoke:Coordinates|coord|43.65|-79.38|display=inline}} : Vis bare i løpetekst (standard)
  • {{#invoke:Coordinates|coord|43.65|-79.38|display=title}} : Vis bare i statusindikatorområdet
  • {{#invoke:Coordinates|coord|43.65|-79.38|display=inline,title}} : Vis begge steder

For å vise koordinatene i statusindikatorområdet på et annet format går det an å bruke |formatitle:

  • {{#invoke:Coordinates|coord|43.65|-79.38|display=inline,title|format=dec|formatitle=dms}} : Koordinatene vises som desimalgrader i løpeteksten, men på tradisjonelt format i statusindikatorområdet

Feilmeldinger

Modulen viser en feilmelding hvis parametrene ikke utgjør gyldige koordinater.

Eksempel på feilaktig bruk
  • {{#invoke:Coordinates|coord|2843.65|-79.38}} : Koordinater : ugyldig himmelretning for lengdegrad, må være «E», «Ø», «V» eller «W»

Sider med feilaktig bruk havner i Kategori:Sider med feilaktige koordinattagger.

Bruk av andre funksjoner

Konvertering fra desimalgrader til seksagesimal

{{#invoke:Coordinates | dec2dms | verdi | positiv retning | negativ retning | presisjon}}

  • verdi : desimaltall
  • positiv retning : positiv himmelretning (N for breddegrad / E for lengdegrad)
  • negativ retning : negativ himmelretning (S for breddegrad / W for lengdegrad)
  • presisjon : D, DM eller DMS
Eksempel
  • {{#invoke:Coordinates|dec2dms|43.651234|N|S|DMS}} : 43° 39′ 04″ N
  • {{#invoke:Coordinates|dec2dms|43.651234|Ø|V|DM}} : 43° 39′ Ø

Konvertering fra seksagesimal til desimal

{{#invoke:Coordinates | dms2dec | retning | grader | minutter | sekunder}}

  • retning: himmelretning (N/S/V/Ø)
  • grader, minutter, sekunder
Eksempel
  • {{#invoke:Coordinates|dms2dec|N|43|39|4}} : 43.65111
  • {{#invoke:Coordinates|dms2dec|N|43|39}} : 43.65
  • {{#invoke:Coordinates|dms2dec|43/39/4/N}} : 43.65111
  • {{#invoke:Coordinates|dms2dec|43/39/N}} : 43.65

Sporingskategorier


require('Module:No globals')
local math_mod = require( "Module:Math" )

local p = {}

local i18n = {
	N = 'N',
	Nlong = 'nord',
	W = 'V',
	Wlong = 'vest',
	E = 'Ø',
	Elong = 'øst',
	S = 'S',
	Slong = 'sør',
	degrees = '° ',
	minutes = '′ ',
	seconds = '″ ',
	geohackurl = 'http://tools.wmflabs.org/geohack/geohack.php?language=nb',
	tooltip = 'Kart, flyfoto m.m.',
	errorcat = 'Sider med feilaktige koordinattagger',
	sameaswikidata = 'Sider med koordinater som samsvarer med Wikidata',
	notaswikidata = 'Sider med koordinater som avviker fra Wikidata',
	nowikidata = 'Sider med koordinater som mangler på Wikidata',
	throughwikidata = 'Sider med koordinater fra Wikidata',
	invalidFormat = 'ugyldig format ',                                                  -- 'invalid coordinate format',
	invalidNSEW = 'ugyldig himmelretning, må være «N», «S», «E», «Ø», «W» eller «V»',   -- 'invalid direction should be "N", "S", "E" or "W"',
	invalidNS = 'ugyldig himmelretning for breddegrad, må være «N» eller «S»',          -- 'could not find latitude direction (should be N or S)',
	invalidEW = 'ugyldig himmelretning for lengdegrad, må være «E», «Ø», «V» eller «W»', -- 'could not find longitude direction (should be W or E) ',
	noCardinalDirection = 'mangler himmelretning',                                      -- 'no cardinal direction found in coordinates',
	invalidDirection = 'ugyldig himmelretning',                                         -- 'invalid direction',
	latitude90 = 'breddegrad > 90',
	longitude360 = 'lengdegrad > 360',
	minSec60 = 'minutter eller sekunder > 60',
	negativeCoode = 'dms-koordinater skal ikke være negative',                          -- 'dms coordinates should be positive',
	dmIntergers = 'grader og minutter må være tall',                                    -- 'degrees and minutes should be integers',
	tooManyParam = 'for mange parametere',                                              -- 'too many parameters for coordinates',
	coordMissing = 'mangler lengdegrad eller breddegrad',                               -- 'latitude or longitude missing',
	invalidGlobe = 'ugyldig globe : ',                                                  -- 'invalid globe:',
}
local coordParse = {
	['Ø'] = 'E',
	['V'] = 'W',
}

local globedata = 	{
	--[[ notes:
		radius in kilometers (especially imprecise for non spheric bodies)
		defaultdisplay is currently disabled, activate it ?
	]]-- 
	ariel = {radius = 580, defaultdisplay = 'dec east'},
	callisto =  {radius = 2410, defaultdisplay = 'dec west'},
	ceres =  {radius = 470, defaultdisplay = 'dec east'},
	charon =  {radius = 1214, defaultdisplay = 'dec east'},
	deimos =  {radius = 7, defaultdisplay = 'dec west'},
	dione =  {radius = 560, defaultdisplay = 'dec west'},
	enceladus =  {radius = 255, defaultdisplay = 'dec west'},
	ganymede =  {radius = 2634, defaultdisplay = 'dec west'},
	earth = {radius = 6371, defaultdisplay = 'dms'},
	europa =  {radius = 1561, defaultdisplay = 'dec east'},
	hyperion =  {radius = 140, defaultdisplay = 'dec west'},
	iapetus =  {radius = 725, defaultdisplay = 'dec west'},
	['io'] =  {radius = 1322, defaultdisplay = 'dec west'},
	jupiter =  {radius = 68911, defaultdisplay = 'dec east'},
	mars =  {radius = 3389.5, defaultdisplay = 'dec east' },
	mercury =  {radius = 2439.7, defaultdisplay = 'dec west'},
	mimas =  {radius = 197, defaultdisplay = 'dec west'},
	miranda =  {radius = 335, defaultdisplay = 'dec east'},
	moon =  {radius = 1736, defaultdisplay = 'dec'},
	neptune =  {radius = 24553, defaultdisplay = 'dec east'},
	oberon =  {radius = 761, defaultdisplay = 'dec east'},
	phoebe =  {radius = 110, defaultdisplay = 'dec west'},
	phobos =  {radius = 11, defaultdisplay = 'dec west'},
	pluto =  {radius = 1185, defaultdisplay = 'dec east'},
	rhea =  {radius = 765, defaultdisplay = 'dec west'},
	saturn =  {radius = 58232, defaultdisplay = 'dec east'},
	titan =  {radius = 2575.5, defaultdisplay = 'dec west'},
	tethys =  {radius = 530, defaultdisplay = 'dec west'},
	titania =  {radius = 394, defaultdisplay = 'dec east'},
	triton = {radius = 1353, defaultdisplay = 'dec east'},
	umbriel =  {radius = 584, defaultdisplay = 'dec east'},
	uranus =  {radius = 25266, defaultdisplay = 'dec east'},
	venus =  {radius = 6051.8, defaultdisplay = 'dec east'},
	vesta =  {radius = 260, defaultdisplay = 'dec east'}
}
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 '[[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"> Koordinater : ' .. 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 = 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
	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(property) -- gets coordinates from wikidata
	property = property or 'P625'
	property = mw.ustring.upper(property)
	local entity = mw.wikibase.getEntityObject()
	if entity
		and entity.claims
		and entity.claims[property]
		and entity.claims[property][1]
		and entity.claims[property][1].mainsnak
		and entity.claims[property][1].mainsnak.snaktype == 'value'
	then
		local coords = entity.claims[property][1].mainsnak.value
		return coords.latitude, coords.longitude, coords.globe or 'earth', convertwikidataprecision(coords.precision or .001)
	end
	return nil
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 wikidataprop = args.wikidataprop -- Wikidata property to use, defaults to P625
	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(wikidataprop)
		
		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
	
-- 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