Ugrás a tartalomhoz

Modul:Coordinate

Ellenőrzött
Ez a lap műszaki védett
A Wikipédiából, a szabad enciklopédiából

Coordinate[mi ez?] • [dokumentáció: mutat, szerkeszt] • [tesztek: sikeres: 8, sikertelen: 0, kihagyva: 1 (részletek)]

Koordinátákkal kapcsolatos funkciók gyűjteménye, a koordinátasablonok megvalósítása.

Az infoboxok az {{infobox/Pozíciós térkép}} és {{infobox/Koordináta}} sablonokkal használják, a Modul:Infobox modulon keresztül.

Használat

A sablonok kiváltásához szükséges működés: en:Template:Coord#Usage.

Regressziós tesztelés: Sablon:Koord/regressziós tesztelés.

Lásd még

-------------------------------------------------------------------------------
-- Coordinate processing functions
-------------------------------------------------------------------------------

require('strict')
local lang = mw.getContentLanguage()
local current_page = mw.title.getCurrentTitle()
local page_name = mw.uri.encode(current_page.prefixedText, 'WIKI')
local coord_link = 'https://geohack.toolforge.org/geohack.php?language=hu&pagename=' .. page_name .. '&params='

-- The class used to internally represent a single coordinate
local Coordinate = {}

local getArgs = require('Modul:Arguments').getArgs
local bit32 = require( 'bit32' )

--Internal functions

--[[
	Normalize cardinal direction
	@param string cardDir Cardinal direction, English or Hungarian
	@return string
--]]
local function normCardDir(cardDir)
	if lang:uc(cardDir) == 'É'  then return 'N' end
	if cardDir:upper()  == 'D'  then return 'S' end
	if cardDir:upper()  == 'K'  then return 'E' end
	if cardDir:upper()  == 'NY' then return 'W' end
	return cardDir
end

local function mfloor(float)
	local result = math.floor(float)
	return result + 1 < float + 1e-12 and result + 1 or result
end

--[[ Helper function, used in detecting DMS formatting ]]
local function dmsTest(first, second)
	first = normCardDir(first or '')
	second = normCardDir(second or '')
	local concatenated = first:upper() .. second:upper()

	if concatenated == "NE" or concatenated == "NW" or concatenated == "SE" or concatenated == "SW" or
		concatenated == "EN" or concatenated == "WN" or concatenated == "ES" or concatenated == "WS" then
		return true
	end
	return false
end

--[[
	Try to find the relevant precision for a latitude or longitude as float
	@param float float
	@return number the precision
--]]
local function detectPrecisionForFloat(float)
	local parts = mw.text.split(tostring(float), '%.')
	if parts[2] then
		return math.pow(10, -1 * math.min(#parts[2], 6))
	else
		return 1
	end
end

--[[
	Validate DMS coordinates
	@return string|nil Error message
--]]
function Coordinate.validateDms(degrees, minutes, seconds, hemisphere, direction)
	if not degrees then
		return 'Missing degrees'
	end
	local d = tonumber(degrees)
	if not d then
		return 'Invalid degrees'
	end
	if not direction or direction ~= 'latitude' and direction ~= 'longitude' then
		error('Invalid direction', 2)
	end
	if direction == 'latitude' and d > 90 or direction == 'longitude' and d >= 360 then
		return 'Invalid degrees'
	end
	if not minutes and not seconds then
		-- Allow decimal format
		if direction == 'latitude' then
			if d < -90 or d < 0 and hemisphere == 'S' then
				return 'Invalid degrees'
			end
		elseif direction == 'longitude' then
			if d < -180 or d < 0 and hemisphere == 'W' then
				return 'Invalid degrees'
			end
		end
	end
	if not minutes and seconds then
		return 'Missing minutes'
	end
	if minutes then
		if d < 0 or select(2, math.modf(d)) ~= 0 then
			return 'Invalid degrees'
		end
		local m = tonumber(minutes)
		if not m or m < 0 or m >= 60 then
			return 'Invalid minutes'
		end
		if seconds then
			if select(2, math.modf(m)) ~= 0 then
				return 'Invalid minutes'
			end
			local s = tonumber(seconds)
			if not s or s < 0 or s >= 60 then
				return 'Invalid seconds'
			end
		end
	end
end

--[[
	Transform degrees, minutes, seconds format latitude and longitude
	into the a structure to be used in displaying coordinates
--]]
local function parseDMS(lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f)
	lat_f = normCardDir(lat_f):upper()
	long_f = normCardDir(long_f):upper()

	-- Check if specified backward
	if lat_f == 'E' 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

	local errm = Coordinate.validateDms(lat_d, lat_m, lat_s, lat_f, 'latitude')
	if errm then
		return nil, { { 'parseDMS', errm } }
	end
	errm = Coordinate.validateDms(long_d, long_m, long_s, long_f, 'longitude')
	if errm then
		return nil, { { 'parseDMS', errm } }
	end

	local lat = (lat_f:upper() == 'S' and -1 or 1) * ((tonumber(lat_d) or 0) + (tonumber(lat_m) or 0) / 60
		+ (tonumber(lat_s) or 0) / 3600)
	local long = (long_f:upper() == 'W' and -1 or 1) * ((tonumber(long_d) or 0) + (tonumber(long_m) or 0) / 60
		+ (tonumber(long_s) or 0) / 3600)

	local prec = math.min(detectPrecisionForFloat(lat_d), detectPrecisionForFloat(long_d))
	if lat_m ~= nil or long_m ~= nil then
		if prec < 1 then
			return nil, {{'parseDMS', 'Nem értelmezhető adatok'}}
		else
			prec = math.min(detectPrecisionForFloat(lat_m), detectPrecisionForFloat(long_m))
			if prec < 0.0001 then
				prec = 1e-6
			elseif prec < 1 then
				prec = prec / 100
			else
				prec = Coordinate.PRECISION.M
			end
		end
	end
	if lat_s ~= nil or long_s ~= nil then
		if prec == Coordinate.PRECISION.M then
			prec = math.max(math.min(detectPrecisionForFloat(lat_s), detectPrecisionForFloat(long_s)), 0.001) * Coordinate.PRECISION.S
		else
			return nil, {{'parseDMS', 'Nem értelmezhető adatok'}}
		end
	end
	local coord = Coordinate:new{latitude = lat, longitude = long, precision = prec}
	return coord, coord and {} or {{'parseDMS', 'Nem értelmezhető adatok'}}
end

--[[
	Format any error messages generated for display
--]]
local function errorPrinter(errors)
	return nil, nil, nil, errors
	--[[
	local result = ""
	for _, v in ipairs(errors) do
		local errorHTML = '<strong class="error">Coordinates: ' .. v[2] .. '</strong>'
		result = result .. errorHTML .. '<br>'
	end
	return result
	--]]
end

Coordinate.DISPLAY_TYPE = {
	INLINE = 1, -- 2^0
	TITLE  = 2  -- 2^1
}

Coordinate.PRECISION = {
	MS      = 1 / 3600 / 1000,  -- to 1/1000 of an arcsecond
	D000001 = 1e-6,             -- ±0.000001°
	MS10    = 1 / 3600 / 100,   -- to 1/100 of an arcsecond
	D00001  = 1e-5,             -- ±0.00001°
	MS100   = 1 / 3600 / 10,    -- to 1/10 of an arcsecond
	D0001   = 0.0001,           -- ±0.0001°
	S       = 1 / 3600,         -- to an arcsecond
	D001    = 0.001,            -- ±0.001°
	D01     = 0.01,             -- ±0.01°
	M       = 1 / 60,           -- to an arcminute
	D1      = 0.1,              -- ±0.1°
	D       = 1,                -- to a degree
	D10     = 10                -- ±10°
}

local orderedPrecisions = {
	Coordinate.PRECISION.MS,
	Coordinate.PRECISION.MS10,
	Coordinate.PRECISION.MS100,
	Coordinate.PRECISION.S,
	Coordinate.PRECISION.M,
	Coordinate.PRECISION.D10
}

local function detectWikidataPrecision(float)
	local precision
	for _, v in ipairs(orderedPrecisions) do
		local m = tonumber(float) / v
		if math.abs(mfloor(m + 0.5) - m) < 1e-8 / v + 1e-12 then
			precision = v
		end
	end
	local s = tostring(float)
	local i = s:find('%.')
	if precision == Coordinate.PRECISION.MS and i and #s - i > 6 then
		return precision
	end
	local decPrecision = detectPrecisionForFloat(float)
	return precision and decPrecision < precision and precision or decPrecision
end

--[[
	Check the input arguments for coord to determine the kind of data being provided
	and then make the necessary processing.
--]]
local function formatTest(args)
	local errors, result, format, coordParams = {}

	if not args[1] then
		-- no lat logic
		return errorPrinter{{"formatTest", "Missing latitude"}}
	elseif not args[4] and not args[5] and not args[6] then
		-- dec logic
		if args.precision == 'wikidata' then
			result = Coordinate:new{latitude = tonumber(args[1]), longitude = tonumber(args[2])}
			if not result then
				return errorPrinter{{'formatTest', 'Nem értelmezhető adatok'}}
			end
			local logPrec = -1 * math.log10(result.precision)
			format = logPrec == math.floor(logPrec) and 'dec' or 'dms'
		else
			result = Coordinate:new{latitude = tonumber(args[1]), longitude = tonumber(args[2]),
				precision = math.min(detectPrecisionForFloat(args[1]), detectPrecisionForFloat(args[2]))}
			if not result then
				return errorPrinter{{'formatTest', 'Nem értelmezhető adatok'}}
			end
			format = 'dec'
		end
		coordParams = 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])
		format = 'dms'
		coordParams = 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])
		format = 'dms'
		coordParams = 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])
		format = result and result.precision < 1 and 'dec' or 'dms'
		coordParams = args[5]
		if args[6] then
			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})
		end
	else
		-- Error
		return errorPrinter{{"formatTest", "Unknown argument format"}}
	end

	return result, format, coordParams, errors
end

--[[
	Check if a value is a number in the given range
	@param mixed value
	@param number min
	@param number max
	@return boolean
--]]
local function validateNumberInRange(value, min, max)
	return type(value) == 'number' and value >= min and value <= max
end

--[[
	Validate precision
--]]
local function validatePrecision(precision)
	for _, v in pairs(Coordinate.PRECISION) do
		if v == precision then
			return true
		end
	end
	return false
end

--[[
	Validate a Coordinate defintion
	@param table definition data
	@return boolean
--]]
local function validate(definition)
	--Validate precision
	if not validatePrecision(definition.precision) then
		return false
	end

	--Validate latitude and longitude
	if not validateNumberInRange(definition.latitude, -180, 360) or not validateNumberInRange(definition.longitude, -180, 360) then
		return false
	end

	return true
end

--[[
	Try to find the relevant precision for a GlobeCoordinate definition
	@param table GlobeCoordinate definition
	@return number the precision
--]]
local function guessPrecision(definition)
	return math.min(detectWikidataPrecision(definition.latitude), detectWikidataPrecision(definition.longitude))
end

-------------------------------------------------------------------------------
-- Creates a new Coordinate
-- @param float latitude Latitude ("vertical" position) as a signed floating-point value (North is positive, South is negative)
-- @param float longitude Longitude ("horizontal" position) as a signed floating-point value (East is positive, West is negative)
-- @example Coordinate.create(12.3456, -98.7654)
--
function Coordinate.create(latitude, longitude)
	local coord = {}
	setmetatable(coord, Coordinate)
	coord.latitude = latitude
	coord.longitude = longitude
	return coord
end

-------------------------------------------------------------------------------
-- Build a new Coordinate
-- @param table definition Definition of the coordinate
-- @return Coordinate|nil
-- @example Coordinate:new{latitude = 12.3456, longitude = -98.7654}
--
function Coordinate:new(definition)
	--Default values
	if definition.precision == nil then
		definition.precision = guessPrecision(definition)
	else
		for _, v in pairs(Coordinate.PRECISION) do
			if math.abs(definition.precision - v) < 1e-12 then
				definition.precision = v
			end
		end
	end

	if not validate(definition) then
		return nil
	end

	local coord = {
		latitude = definition.latitude,
		longitude = definition.longitude,
		precision = definition.precision or 0
	}

	setmetatable(coord, self)
	self.__index = self

	return coord
end

-------------------------------------------------------------------------------
-- == operator
-- (note that this is a naive implementation which requires exact equality if floating-point values;
-- this probably does not work very well in practice)
function Coordinate.__eq(coord1, coord2)
	return math.abs(coord1.latitude - coord2.latitude) < 1e-6 and math.abs(coord1.longitude - coord2.longitude) < 1e-6
end

-------------------------------------------------------------------------------
-- Transform coordinate to string
-- @param string format
-- Special characters in the format string:
--      %L  latitude as a signed float
--      %U  latitude as an unsigned float
--      %D  degree part of latitude (i.e. floor(latitude))
--      %M  minute part of latitude
--      %S  second part of latitude (including fractional part)
--      %C  cardinal direction for latitude as shortcut (N/S)
--      %I  internationalized cardinal direction for latitude as shortcut (currently always in in Hungarian: É/D)
--      ... same with lowercase for longitude
function Coordinate:format(format)
	local d, rem = math.modf(self.latitude) -- splits number into integer and fractional part
	local m, rem = math.modf(rem * 60)
	local s = math.floor(rem * 60 * 100 + 0.5) / 100
	format = format:gsub('%%L', lang:formatNum(self.latitude))
	format = format:gsub('%%U', lang:formatNum(math.abs(self.latitude)))
	format = format:gsub('%%D', d)
	format = format:gsub('%%M', m)
	format = format:gsub('%%S', s)
	format = format:gsub('%%C', (self.latitude >= 0) and 'N' or 'S')
	format = format:gsub('%%I', (self.latitude >= 0) and 'É' or 'D')

	local d, rem = math.modf(self.longitude) -- splits number into integer and fractional part
	local m, rem = math.modf(rem * 60)
	local s = math.floor(rem * 60 * 100 + 0.5) / 100
	format = format:gsub('%%l', lang:formatNum(self.longitude))
	format = format:gsub('%%u', lang:formatNum(math.abs(self.longitude)))
	format = format:gsub('%%d', d)
	format = format:gsub('%%m', m)
	format = format:gsub('%%s', s)
	format = format:gsub('%%c', (self.longitude >= 0) and 'E' or 'W')
	format = format:gsub('%%i', (self.longitude >= 0) and 'K' or 'Ny')

	return format
end

-------------------------------------------------------------------------------
-- These elements can be used in stringPatterns between $ marks
-- E.g. "$int$° $int$′ $int$″"
local patternElements = {
	uint = "[0-9]+",
	int = "[-+]?[0-9]+",
	ufloat = "[0-9]*[.,]?[0-9]+", -- english or hungarian separator notation
	float = "[-+]?[0-9]*[.,]?[0-9]+", -- english or hungarian separator notation
	cd = "[NSEWÉDK][Yy]?"  -- cardinal directions in english or hungarian
}

-------------------------------------------------------------------------------
-- FIXME move this to an intl module
-- string to number, handle english and hungarian separator
local function num(s)
	if type(s) == 'string' then
		s = s:gsub(",", ".")
		return tonumber(s)
	else
		return s
	end
end

-------------------------------------------------------------------------------
-- cardinal direction to sign of coordinate (+1/-1), handles english and hungarian shortcuts
local directionMap = {N = 1, S = -1, E = 1, W = -1, ["É"] = 1, D = -1, K = 1, Ny = -1, NY = -1}
local function dirsign(s) return directionMap[s] end

-------------------------------------------------------------------------------
-- Contains regexp - callback pairs. The regexp describes a possible human-readable representation of a coordinate,
-- the callback receives the match results and transforms them into a latitude-longitude pair (a pair of signed floats).
-- Can use patternElement keys for syntatic sugar.
local stringPatterns = {
	{"($float$), ($float$)", function(lat, long) return num(lat), num(long) end}, -- 12.3456, -98.7654
	{"($cd$) ($float$), ($cd$) ($float$)",
		function(lath, lat, longh, long)
			return dirsign(lath) * num(lat), dirsign(longh) * num(long)
		end},  -- É 48,621667, K 16,871528
	{"($int$)° ($int$)['′] ($float$)[\"″] ($cd$), ($int$)° ($int$)['′] ($float$)[\"″] ($cd$)",
		function(latd, latm, lats, lath, longd, longm, longs, longh)
			local lat = dirsign(lath) * (num(latd) + num(latm) / 60 + num(lats) / 3600)
			local long = dirsign(longh) * (num(longd) + num(longm) / 60 + num(longs) / 3600)
			return lat, long
		end}, -- 12° 20' 44" N, 98° 45' 55" W
	{"($cd$) ($int$)° ($int$)['′] ($float$)[\"″], ($cd$) ($int$)° ($int$)['′] ($float$)[\"″]",
		function(lath, latd, latm, lats, longh, longd, longm, longs)
			local lat = dirsign(lath) * (num(latd) + num(latm) / 60 + num(lats) / 3600)
			local long = dirsign(longh) * (num(longd) + num(longm) / 60 + num(longs) / 3600)
			return lat, long
		end}, -- N 12° 20' 44", W 98° 45' 55"
}

local stringPatternsOld, stringPatterns = stringPatterns, {}
for i, pair in ipairs(stringPatternsOld) do
	local pattern, callback = pair[1], pair[2]
	for key, value in pairs(patternElements) do
		pattern = pattern:gsub('%$' .. key .. '%$', value)
	end
	table.insert(stringPatterns, {pattern, callback})
end

-------------------------------------------------------------------------------
-- Creates a Coordinate object from a human-readable string representation.
-- @param string s
-- @return Coordinate|nil
-- @example
--
function Coordinate.fromString(s)
	for i, pair in ipairs(stringPatterns) do
		local pattern, callback = pair[1], pair[2]

		if mw.ustring.match(s, pattern) then
			local lat, long = callback(mw.ustring.match(s, pattern))
			return Coordinate:new{latitude = lat, longitude = long}
		end
	end
	return nil
end

-------------------------------------------------------------------------------
-- Returns coordinate in standard text format - two signed floats (12.3456, -98.7654)
-- @return string
--
function Coordinate:__tostring()
	return self:format('%L, %l')
end

--[[
	Build params uri component and link text for GeoHack link
	@param string format dec|dms
	@return string, string|nil, nil
	@example coord:toGeoHack('dms')
--]]
function Coordinate:toGeoHack(format)
	if format ~= 'dec' and format ~= 'dms' then return nil, nil end
	local params = ''
	local text = ''
	local logPrec = -1 * math.log10(self.precision)
	local decimalPrecision = logPrec == math.floor(logPrec)

	if decimalPrecision then
		params = math.floor(self.latitude * 1e+6 + 0.5) / 1e+6 .. ';' .. math.floor(self.longitude * 1e+6 + 0.5) / 1e+6
	end

	if format == 'dec' then
		local decimals = math.floor(logPrec)
		if decimals < 1 then decimals = 0 end
		text = mw.text.tag('span', { class = 'latitude' }, (self.latitude >= 0 and 'é. sz.' or 'd. sz.') .. ' ' ..
			string.format('%.' .. decimals .. 'f', math.abs(self.latitude)):gsub('%.', ',') .. '°') .. ', ' ..
			mw.text.tag('span', { class = 'longitude' }, (self.longitude >= 0 and 'k. h.' or 'ny. h.') .. ' ' ..
			string.format('%.' .. decimals .. 'f', math.abs(self.longitude)):gsub('%.', ',') .. '°')
	end

	if format == 'dms' or not decimalPrecision then
		local d, m, s, ctext, decimals
		if decimalPrecision then
			local float = math.abs(self.latitude)
			d = mfloor(float)
			m = mfloor(float * 60 - d * 60)
			decimals = math.floor(logPrec) - 3
			if decimals < 0 then decimals = 0 end
			s = mfloor((float * 3600 - d * 3600 - m * 60) * 10^decimals + 0.5) / 10^decimals
			if s == 60 then s = 0; m = m + 1 end
			if m == 60 then m = 0; d = d + 1 end
		else
			local intToPrecision = mfloor(math.abs(self.latitude) / self.precision + 0.5)
			d = mfloor(intToPrecision * self.precision)
			m = mfloor(intToPrecision * (self.precision * 60) - d * 60)
			decimals = math.floor(-1 * math.log10(self.precision * 3600))
			if decimals < 1 then decimals = 0 end
			s = mfloor(intToPrecision - d / self.precision - m / (self.precision * 60) + 0.5) * (self.precision * 3600)
		end

		if not decimalPrecision then params = params .. d end
		if format == 'dms' then ctext = (self.latitude >= 0 and 'é. sz.' or 'd. sz.') .. ' ' .. d .. '°' end
		if self.precision < Coordinate.PRECISION.D then
			if not decimalPrecision then params = params .. '_' .. m end
			if format == 'dms' then ctext = ctext .. ' ' .. string.format('%02d′', m) end
			if self.precision < Coordinate.PRECISION.M then
				if not decimalPrecision then params = params .. '_' .. s end
				if format == 'dms' then ctext = ctext .. ' ' .. ('0%.0f'):format(s):sub(-2) .. '″' end
			end
		end
		if not decimalPrecision then params = params .. '_' .. (self.latitude >= 0 and 'N' or 'S') .. '_' end
		if format == 'dms' then text = text .. mw.text.tag('span', { class = 'latitude' }, ctext) .. ', ' end

		local d, m, s, ctext, decimals
		if decimalPrecision then
			local float = math.abs(self.longitude)
			d = mfloor(float)
			m = mfloor(float * 60 - d * 60)
			decimals = math.floor(logPrec) - 3
			if decimals < 0 then decimals = 0 end
			s = mfloor((float * 3600 - d * 3600 - m * 60) * 10^decimals + 0.5) / 10^decimals
			if s == 60 then s = 0; m = m + 1 end
			if m == 60 then m = 0; d = d + 1 end
		else
			local intToPrecision = mfloor(math.abs(self.longitude) / self.precision + 0.5)
			d = mfloor(intToPrecision * self.precision)
			m = mfloor(intToPrecision * (self.precision * 60) - d * 60)
			decimals = math.floor(-1 * math.log10(self.precision * 3600))
			if decimals < 1 then decimals = 0 end
			s = mfloor(intToPrecision - d / self.precision - m / (self.precision * 60) + 0.5) * (self.precision * 3600)
		end

		if not decimalPrecision then params = params .. d end
		if format == 'dms' then ctext = (self.longitude >= 0 and 'k. h.' or 'ny. h.') .. ' ' .. d .. '°' end
		if self.precision < Coordinate.PRECISION.D then
			if not decimalPrecision then params = params .. '_' .. m end
			if format == 'dms' then ctext = ctext .. ' ' .. string.format('%02d′', m) end
			if self.precision < Coordinate.PRECISION.M then
				if not decimalPrecision then params = params .. '_' .. s end
				if format == 'dms' then ctext = ctext .. ' ' .. ('0%.0f'):format(s):sub(-2) .. '″' end
			end
		end
		if not decimalPrecision then params = params .. '_' .. (self.longitude >= 0 and 'E' or 'W') end
		if format == 'dms' then text = text .. mw.text.tag('span', { class = 'longitude' }, ctext) end
	end

	return params, text
end

function Coordinate:geodecHtml()
	return ('<span class="geo-dec" title="Maps, aerial photos, and other data for this location">%f°%s %f°%s</span>'):format(
		math.abs(self.latitude),
		(self.latitude >= 0) and 'N' or 'S',
		math.abs(self.longitude),
		(self.longitude >= 0) and 'E' or 'W'
	)
end

--[[
	Get the display type as a binary number, to be processed using bitwise operators.

	@param string|number text User input text or type already stored as a binary number
	@return number The recognized types
--]]
local function getDisplay(text)
	if type(text) == 'number' then
		return text
	elseif text == nil then
		-- default
		return Coordinate.DISPLAY_TYPE.INLINE
	elseif type(text) ~= 'string' then
		return 0
	end
	local display = 0
	text = text:lower()
	if string.find(text, 'inline') ~= nil or text == 'i' or text == 'it' or text == 'ti' then
		display = Coordinate.DISPLAY_TYPE.INLINE
	end
	if string.find(text, 'title') ~= nil or text == 't' or text == 'it' or text == 'ti' then
		display = display + Coordinate.DISPLAY_TYPE.TITLE
	end
	return display
end

--[[
	Process {{#coordinates:}} parser function and return eventual error message.

	@param Coordinate coord The coordinate to process
	@param boolean|nil primary Whether it's the primary coordinate tag for the page
	@param string|nil geohack GeoHack parameters
	@param table|nil params Additional parameters
	@return string Error message wikitext, or empty string if there was no error
--]]
local function getParserFunction(coord, primary, geohack, params)
	params = params or {}
	table.insert(params, 1, coord.latitude)
	table.insert(params, 2, coord.longitude)
	if primary then
		table.insert(params, 3, 'primary')
	end
	if geohack then
		table.insert(params, 4, geohack)
	end
	return mw.getCurrentFrame():callParserFunction('#coordinates', params)
end

--[[
	Return a GlobeCoordinate in HTMl (with a <GlobeCoordinate> node)
	@param mw.language|string|nil language to use. By default the content language.
	@param table|nil attributes table of attributes to add to the <GlobeCoordinate> node.
	@return string
--]]
function Coordinate.coord(frame, args)
	if not args then
		args = getArgs(frame)
	end

	local coord, inputFormat, coordParams, errors = formatTest(args)
	if #errors > 0 then
		local result = ""
		for _, v in ipairs(errors) do
			local errorHTML = '<strong class="error">Coordinate: ' .. v[2] .. '</strong>'
			result = result .. errorHTML .. '<br>'
		end
		return result .. '[[Kategória:Hibás koordináták]]'
	end

	local format = args.format or inputFormat
	local params, linkText = coord:toGeoHack(format)
	if coordParams then params = params .. '_' .. coordParams end
	local title = args.name and '&title=' .. mw.uri.encode(args.name) or ''

	local inlineLink = mw.text.tag(
		'span', {
			class = 'plainlinks nourlexpansion'
		},
		'[' .. coord_link .. params .. title .. ' ' .. linkText ..
		mw.text.tag(
			'span', {
				["class"] = "h-geo geo",
				["style"] = "display:none;"
			},
			mw.text.tag( 'span', {
					["class"] = "p-latitude latitude"
				},
				coord.latitude
			) ..
			', ' ..
			mw.text.tag( 'span', {
					["class"] = "p-longitude longitude"
				},
				coord.longitude
			)
		) ..
		mw.text.tag('span', { class = 'geo-nondefault' }, mw.text.tag('span', { class = 'vcard' },
			coord:geodecHtml() ..
			mw.text.tag('span', {class = 'fn org'}, args.name or '')
		)) .. ']'
	) .. (args.notes or '')

	local display = getDisplay(args.display)
	local text = ''
	if bit32.btest(display, Coordinate.DISPLAY_TYPE.INLINE) then
		text = inlineLink
	end
	if bit32.btest(display, Coordinate.DISPLAY_TYPE.TITLE) then
		text = text .. ('<span id="coordinates">[[Földrajzi koordináta-rendszer|Koordináták]]: %s</span>'):format(inlineLink)
	end
	text = mw.getCurrentFrame():extensionTag {
		name = 'templatestyles', args = { src = 'Modul:Coordinate/styles.css' }
	} .. text
	if args.with_pfunc then
		text = text .. getParserFunction(coord, bit32.btest(display, Coordinate.DISPLAY_TYPE.TITLE), coordParams, {name = args.name})
	end

	return text
end

return Coordinate