UWAGA
Strona jest ponownie oddana do uzytku po zabiegach konfiguracyjnych. Jeśli zobaczą Państwo na niej jakieś błedy techniczne, prosimy o ich zgłoszenie.

Większość artykułów w portalu to nasze własne teksty z kluczowych dziedzin związanych z naszą misją. Spora część materiałów pochodzi też z polskiej wersji Wikipedii, gdzie były odrzucone ze względu na politykę redaktorów (przeczytaj o krytyce Wikipedii). Są też i takie, które zostały przeniesione na nasze strony, gdyż stanowią istotne uzupełnienie merytorycznej treści naszego serwisu. Wszystkie artykuły podlegają edycji przez naszych Użytkowników, dlatego ich wersje mogą się różnić od prezentowanych na innych witrynach.

Moduł:Współrzędne

Z Wedapedia
Przejdź do nawigacji Przejdź do wyszukiwania

Dokumentacja dla tego modułu może zostać utworzona pod nazwą Moduł:Współrzędne/opis

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

	displayGlobes = {
		earth     = { mode = "EW", Q="Q2",    symbol="⨁", },
		moon      = { mode = "EW", Q="Q405",  symbol="☾", },
		mercury   = { mode = "W",  Q="Q308",  symbol="☿", },
		mars      = { mode = "W",  Q="Q111",  symbol="♂", },
		phobos    = { mode = "W",  Q="Q7547", },
		deimos    = { mode = "W",  Q="Q7548", },
		ganymede  = { mode = "W",  Q="Q3169", },
		callisto  = { mode = "W",  Q="Q3134", },
		io        = { mode = "W",  Q="Q3123", },
		europa    = { mode = "W",  Q="Q3143", },
		mimas     = { mode = "W",  Q="Q15034", },
		enceladus = { mode = "W",  Q="Q3303", },
		tethys    = { mode = "W",  Q="Q15047", },
		dione     = { mode = "W",  Q="Q15040", },
		rhea      = { mode = "W",  Q="Q15050", },
		titan     = { mode = "W",  Q="Q2565", },
		hyperion  = { mode = "?",  Q="Q15037", },
		iapetus   = { mode = "W",  Q="Q17958", },
		phoebe    = { mode = "W",  Q="Q17975", },
		venus     = { mode = "E",  Q="Q313",  symbol="♀", },
		ceres     = { mode = "E",  Q="Q596",  symbol="⚳", },
		vesta     = { mode = "E",  Q="Q3030", symbol="⚶", },
		miranda   = { mode = "E",  Q="Q3352", },
		ariel     = { mode = "E",  Q="Q3343", },
		umbriel   = { mode = "E",  Q="Q3338", },
		titania   = { mode = "E",  Q="Q3322", },
		oberon    = { mode = "E",  Q="Q3332", },
		triton    = { mode = "E",  Q="Q3359", },
		pluto     = { mode = "E",  Q="Q339",  symbol="♇", },
	},

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

	displayDecimalSeparator = ",",
	coordinatesSeparator = "\194\160",
	topPrefix = "Na mapach: ",
	displayGlobesDefaultSymbol = "⌘",
	defaultSymbolSeparator = "\194\160",
	documentationSubpage = "opis",
	defaultWDPrecision = 0.00027777777777777800, -- WD wyświetla domyślnie z dokładnością do sekund
	defaultSizePrecision = 1.0,
	defaultDistanceScalingFactor = 6731000, -- promień Ziemi w metrach

	geohack_root = "//tools.wmflabs.org/geohack/geohack.php?language=pl",
	geohack_hint = "Mapy, zdjęcia satelitarne i inne informacje dotyczące miejsca o współrzędnych geograficznych %s %s",

	-- template API data
	apiCoordinates = "współrzędne",
	apiMapPoint = "punkt",

	apiCheckDistance = "Sprawdź odległość współrzędnych",
	apiDistance = "Odległość",
	apiPrecisionRadius = "Promień dokładności",
	
	argScalingFactor = "mnożnik",
	argMaximumDistance = "odległość",
	argErrorMessage = "komunikat",

	wrappersCoordinates = "Szablon:Współrzędne",
	wrappersMapPoint = "Szablon:Mapa lokalizacyjna/punkt",

	argCoordinatesCoordinates = 1,
	argCoordinatesGeohack = 2,

	argLocation = "umieść",
	valLocationTop = "na górze",
	valLocationInline = "w tekście",
	valLocationTopAndInline = "w tekście i na górze",

	argPrecision = "dokładność",
	valPrecisionAutoDecimal = "dziesiętnie",
	valPrecisionAutoDMS = "kątowo",

	argLink = "linkuj",
	valLinkYes = "tak",
	valLinkNo = "nie",
	valLinkGMS = "zgodnie",

	argSymbol = "symbol",
	valSymbolYes = "tak",
	valSymbolNo = "nie",

	argName = "nazwa",
	
	-- apiMapPoint
	argMapPointCoordinates = 1,
	argMark = "znak",
	argMarkSize = "rozmiar znaku",
	argDescription = "opis",
	argMapPointGeohack = "opcje geohack",
	argDescriptionPosition = "pozycja",
	argAlt = "alt",

	defArgMark = "Red pog.svg",
	defArgMarkSize = 6,
	defArgGeohack = "type:city",

	mapPointMapping = {
		["Mars"] = "globe:Mars",
		["Księżyc"] = "globe:Moon",
		["Wenus"] = "globe:Venus",
		["Merkury"] = "globe:Mercury",
	},

	-- categories
	errorCategory = "[[Kategoria:Strony z błędami w parametrach szablonów współrzędnych geograficznych]]",

	-- error messages
	errorInvalidMinutes = "Wartość minut jest nieprawidłowa (%s°%s')",
	errorExpectedIntegerMinutes = "Oczekiwana liczba minut bez kropki dziesiętnej jeśli podawane są sekundy (%s°%s'%s”)",
	errorInvalidSeconds = "Wartość sekund jest nieprawidłowa (%s°%s'%s”)",
	errorInvalidPositionalArguments = "Nieprawidłowe parametry",
	errorLatitudeOutOfRange = "Przekroczony zakres szerokości geograficznej (%f)",
	errorLongitudeOutOfRange = "Przekroczony zakres długości geograficznej (%f)",
	errorUnrecognizedLinkOption = "Niedozwolona wartość parametru ''linkuj'': %s",
	errorUnrecognizedLocationOption = "Niedozwolona wartość parametru ''umieść'': %s",
	errorUnrecognizedPrecisionOption = "Niedozwolona wartość parametru ''dokładność'': %s",
	errorEmptySymbolOption = "Pusty parametr ''symbol''",
	errorMissingCoordinates = "Brak współrzędnych",
}

--------------------------------------------------------------------------------
-- Coordinates class methods
--------------------------------------------------------------------------------

local CoordinatesMetatable = {}
local CoordinatesMethodtable = {}

CoordinatesMetatable.__index = CoordinatesMethodtable

function CoordinatesMethodtable:parse(coordinates, params, displayPrecision)
	
	local lang = mw.getContentLanguage()

	local function calculateDecimalPrecision(s)
		local s1 = string.gsub(s,"%d","0")
		local s2 = string.gsub(s1,"^-","0")
		local s3 = string.gsub(s2,"0$","1")
		local result = lang:parseFormattedNumber(s3)
		return result > 0 and result or 1.0
	end

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

		-- select automatic precision
		local precision = p1 < p2 and p1 or p2

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

		self.precision = precision
	end

	local function analyzeAngle(degree, minutes, seconds)
		local result = lang:parseFormattedNumber(degree)
		if not result then
			return false, geoformatdata.errorInvalidPositionalArguments
		end

		if not string.match(degree, "^%d+$") then
			if (#minutes > 0) or (#seconds > 0) then
				-- expected empty minutes and empty seconds if float degree is given
				return false, geoformatdata.errorInvalidPositionalArguments
			end

			return true, result, calculateDecimalPrecision(degree)
		end

		if #minutes == 0 then
			if #seconds > 0 then
				-- expected empty seconds if minute is not given
				return false, geoformatdata.errorInvalidPositionalArguments
			end
		
			return true, result, calculateDecimalPrecision(degree)
		end

		local minute = lang:parseFormattedNumber(minutes)
		if not minute or (minute >= 60) then
			return false, string.format(geoformatdata.errorInvalidMinutes, degree, minutes)
		end

		result = result * 60 + minute
		if not string.match(minutes, "^%d+$") then
			if #seconds > 0 then
				return false, string.format(geoformatdata.errorExpectedIntegerMinutes, degree, minutes, seconds)
			end

			return true, result/60, 0.00027777777777777800
		end

		if #seconds == 0 then
			return true, result/60, 0.01666666666666670000
		end

		local second = lang:parseFormattedNumber(seconds)
		if not second or (second >= 60) then
			return false, string.format(geoformatdata.errorInvalidSeconds, degree, minutes, seconds)
		end

		result = result*60 + second
		return true, result/3600, calculateDecimalPrecision(seconds)*0.00027777777777777800
	end

	if not coordinates or (#mw.text.trim(coordinates) <= 0) then
		return false, geoformatdata.errorMissingCoordinates
	end

	local function parseSimpleText()

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

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

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

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

			self.latitude  = ((h1 == "N") or (h1 == "S")) and v1 or v2
			self.longitude = ((h1 == "E") or (h1 == "W")) and v1 or v2
			selectAutoPrecision(p1, p2)
			local nlat = ((h1 == "N") or (h1 == "S")) and d1 or d2
			local nlon = ((h1 == "E") or (h1 == "W")) and d1 or d2
			self.navi = (string.match(nlat, "^%d%d$") or string.match(nlat, "^%d%d[%.,]%d+$"))
				and (string.match(nlon, "^%d%d%d$") or string.match(nlon, "^%d%d%d[%.,]%d+$"))
			return nil
		end

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

		return geoformatdata.errorInvalidPositionalArguments
	end

	local data = false
	if params then
		local p = mw.text.trim(params)
		if #p > 0 then
			self.params = p
			local trace = false
			for i, v in ipairs(mw.text.split(p, '_', true)) do
				local globe = string.match(v, "^globe:(%a+)$")
				if globe then
					if data then
						-- more than one globe, data undetermined
						trace = "undetermined"
						data = nil
						break
					end
					
					globe = string.lower(globe)
					data = geoformatdata.displayGlobes[globe]
					if not data then
						-- unrecognized data
						trace = "unrecognized"
						data = nil
						break
					else
						trace = globe
					end
				end
			end
		
			if trace then
				_ = mw.title.new("Module:Współrzędne/globe:"..trace).id
			end
		end
	end

	if data and not displayPrecision then
		displayPrecision = data.Q == "Q2" and geoformatdata.valPrecisionAutoDMS or geoformatdata.valPrecisionAutoDecimal
	end
	
	self.displayData = data or geoformatdata.displayGlobes.earth

	local errorMessage = parseSimpleText()
	if errorMessage then
		return false, errorMessage
	end

	
	if (self.latitude < -90) or (self.latitude > 90) then
		return false, string.format(geoformatdata.errorLatitudeOutOfRange, self.latitude)
	end

	if (self.longitude < -360) or (self.longitude > 360) then
		return false, string.format(geoformatdata.errorLongitudeOutOfRange, self.longitude)
	end
	
	return true, nil
end

function CoordinatesMethodtable:normalize()
	assert(self,"Did you use '.' instead of ':' while calling the function?")
	local mode = false
	if self.displayData then
		mode = self.displayData.mode
	end
	if mode == "?" then
		-- unrecognized left as given
	elseif mode == "W" then
		if self.longitude > 0 then
			self.longitude = self.longitude - 360
		end
	elseif mode == "E" then
		if self.longitude < 0 then
			self.longitude = self.longitude + 360
		end
	elseif self.longitude < -180 then
		self.longitude = self.longitude + 360
	elseif self.longitude > 180 then
		self.longitude = self.longitude - 360
	end
end

function CoordinatesMethodtable:format()
	
	local function selectFormat(precision)
		local supportedFormats = geoformatdata.supportedFormats
		for i, v in ipairs(supportedFormats) do
			local prec = v.precision
			local eps = prec / 64
			local minPrec = prec - eps
			local maxPrec = prec + eps
			if (minPrec < precision) and (precision < maxPrec) then
				return v
			end
		end

		-- use the last one with highest precision
		return supportedFormats[#supportedFormats]
	end

	local function formatAngle(value, format, markers, decimalSeparator, navi)

		assert(type(value) == "number")
		local prefix = value < 0 and markers.negativePrefix or markers.positivePrefix
		local suffix = value < 0 and markers.negativeSuffix or markers.positiveSuffix

		value = math.abs(value)
		
		local result = nil

		if format.m == nil then
			-- format decimal value
			if format.precision > 1 then
				-- round the value
				value = math.floor(value / format.precision) * format.precision
			end

			result = string.format(format.d.."%s", value, markers.degree)
		elseif format.s == nil then
			-- format dm value
			local angle   = math.floor(value)
			local minutes = tonumber(string.format(format.m, (value - angle) * 60))
			-- fix rounded minutes
			if minutes == 60 then
				angle = angle + 1
				minutes = 0
			end
			
			local d = navi or format.d
			result = string.format(d.."%s"..format.m.."%s", angle, markers.degree, minutes, markers.minute)
		else
			-- format dms value
			local angle   = math.floor(value)
			local minutes = math.floor((value - angle) * 60)
			local seconds = tonumber(string.format(format.s, (value - angle) * 3600 - minutes * 60))

			-- fix rounded seconds
			if seconds == 60 then
				minutes = minutes + 1
				seconds = 0
				if minutes == 60 then
					angle = angle + 1
					minutes = 0
				end
			end

			local d = navi or format.d
			result = string.format(d.."%s"..format.m.."%s"..format.s.."%s", angle, markers.degree, minutes, markers.minute, seconds, markers.second)
		end

		if decimalSeparator then
			result = string.gsub(result, "%.", decimalSeparator)
		end

		return prefix .. result .. suffix
	end

	local function formatDegree(value, decimalSeparator)
		local result = string.format("%f", value)

		if decimalSeparator then
			result = string.gsub(result, "%.", decimalSeparator)
		end
	
		return result
	end

	local function fullpagenamee()
		local title = mw.title.getCurrentTitle()
		return title.namespace == 0
			and title:partialUrl()
			or  title.nsText .. ":" .. title:partialUrl()
	end

	local format = selectFormat(self.precision)
	local prettyLatitude  = formatAngle(self.latitude,  format, geoformatdata.latitudeGlobeMarkers,  geoformatdata.displayDecimalSeparator, self.navi and "%02.0f" or false)
	local prettyLongitude = formatAngle(self.longitude, format, geoformatdata.longitudeGlobeMarkers, geoformatdata.displayDecimalSeparator, self.navi and "%03.0f" or false)

	if not self.link then
		return mw.text.nowiki(prettyLatitude .. geoformatdata.coordinatesSeparator .. prettyLongitude)
	end

	local linkLatitude = false
	local linkLongitude = false
	if self.link == "gms" then
		linkLatitude = formatAngle(self.latitude,  format, geoformatdata.latitudeLinkMarkers)
		linkLongitude = formatAngle(self.longitude, format, geoformatdata.longitudeLinkMarkers)
	end

	local geohack_link = self:geohack(fullpagenamee(), linkLatitude, linkLongitude)

	local degreeLatitude  = formatDegree(self.latitude,  geoformatdata.displayDecimalSeparator)
	local degreeLongitude = formatDegree(self.longitude, geoformatdata.displayDecimalSeparator)
	local pretty_hint = string.format(geoformatdata.geohack_hint, prettyLatitude, prettyLongitude)
	local degree_hint = string.format(geoformatdata.geohack_hint, degreeLatitude, degreeLongitude)
	local separator = mw.text.nowiki(geoformatdata.coordinatesSeparator)

	local node = false
	local result = mw.html.create():wikitext("[", geohack_link, " ")
	node = result:tag("span"):attr("class", "geo-default")
		:tag("span"):attr("class", "geo-dms"):attr("title", mw.text.nowiki(pretty_hint))
	node:tag("span"):attr("class", "latitude"):wikitext(mw.text.nowiki(prettyLatitude))
	node:wikitext(separator)
	node:tag("span"):attr("class", "longitude"):wikitext(mw.text.nowiki(prettyLongitude))
	result:tag("span"):attr("class", "geo-multi-punct"):wikitext("/")
	node = result:tag("span"):attr("class", "geo-nondefault")
		:tag("span"):attr("class", "geo-dms"):attr("title", mw.text.nowiki(degree_hint))
	node:tag("span"):attr("class", "latitude"):wikitext(mw.text.nowiki(degreeLatitude))
	node:wikitext(separator)
	node:tag("span"):attr("class", "longitude"):wikitext(mw.text.nowiki(degreeLongitude))
	result:wikitext("]")
	
	return tostring(result)
end

function CoordinatesMethodtable:display(inlinePrefix)
	
	local text = self:format{}

	if not self.top and not self.inline then
		return text
	end

	local function drawGlobeSymbol(displayData)
		local symbol = displayData.symbol or geoformatdata.displayGlobesDefaultSymbol
		if not displayData.Q then
			return symbol..geoformatdata.defaultSymbolSeparator
		end

		local link = mw.wikibase.sitelink(displayData.Q)
		if not link then
			return symbol..geoformatdata.defaultSymbolSeparator
		end
		
		return "[["..link.."|"..symbol.."]]"..geoformatdata.defaultSymbolSeparator
	end

	if inlinePrefix == nil then
		if self.symbol == false then
			inlinePrefix = ""
		elseif self.symbol == true then
			inlinePrefix = drawGlobeSymbol(self.displayData) or ""
		elseif self.symbol then
			inlinePrefix = self.symbol
		elseif self.displayData.Q == "Q2" then
			-- !symbol & Q2
			inlinePrefix = ""
		else
			-- !symbol & !Q2
			inlinePrefix = drawGlobeSymbol(self.displayData) or ""
		end
	end
	
	local result = mw.html.create()

	if self.top then
		local indicator = mw.html.create("span")
			:attr("id", "coordinates")
			:attr("class", "coordinates plainlinks")
			:wikitext(geoformatdata.topPrefix, inlinePrefix or "", text)
		result:wikitext(mw.getCurrentFrame():extensionTag{name = 'indicator', content = tostring(indicator), args = { name='coordinates' } } or "")
	end

	if self.inline then
		result:tag("span")
				:attr("class", self.top and "coordinates inline inline-and-top plainlinks" or "coordinates inline plainlinks")
				:wikitext(inlinePrefix or "", text)
	end

	return tostring(result)
end

function CoordinatesMethodtable:extensionGeoData()
	local params = {}

	local title = mw.title.getCurrentTitle()
	if self.top and not title.isTalkPage and (title.subpageText ~= geoformatdata.documentationSubpage) then
		table.insert(params, "primary")
	end

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

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

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

	if self.name then
		params.name = self.name
	end
	
	-- https://bugzilla.wikimedia.org/show_bug.cgi?id=50863 RESOLVED
	return mw.getCurrentFrame():callParserFunction("#coordinates", params) or ""
end

function CoordinatesMethodtable:geohack(pagename, linkLatitude, linkLongitude)
	local result = {}
	table.insert(result, geoformatdata.geohack_root)
	if pagename then
		table.insert(result, "&pagename=")
		table.insert(result, pagename)
	end
	
	table.insert(result, "&params=")
	if linkLatitude and linkLongitude then
		table.insert(result, linkLatitude)
	elseif self.latitude < 0 then
		table.insert(result, tostring(-self.latitude))
		table.insert(result, "_S")
	else
		table.insert(result, tostring(self.latitude))
		table.insert(result, "_N")
	end
	
	table.insert(result, "_")
	if linkLatitude and linkLongitude then
		table.insert(result, linkLongitude)
	elseif self.longitude < 0 then
		table.insert(result, tostring(-self.longitude))
		table.insert(result, "_W")
	else
		table.insert(result, tostring(self.longitude))
		table.insert(result, "_E")
	end
	
	if self.params then
		table.insert(result, "_")
		table.insert(result, self.params)
	end

	if self.name then
		table.insert(result, "&title=")
		table.insert(result, mw.uri.encode(self.name))
	end
	
	return table.concat(result)
end

local function create()
	-- initialize default data
	local self = {
		latitude  = 0,
		longitude = 0,
		precision = 1,
		params    = nil,
		inline    = false,
		top       = false,
		link      = true,
	}
	setmetatable(self, CoordinatesMetatable)
	return self;
end

--------------------------------------------------------------------------------
-- utilities
--------------------------------------------------------------------------------

local function showError(message, args)
	if not message then
		return geoformatdata.errorCategory
	end

	local result = {}
	table.insert(result, "<span style=\"color:red\">")
	assert(type(message) == "string", "Expected string message")
	table.insert(result, message)
	local i = 1
	while args[i] do
		if i == 1 then
			table.insert(result, ": {")
		else
			table.insert(result, "&#x7C;")
		end
		
		table.insert(result, args[i])
		i = i + 1
	end
	if i > 1 then
		table.insert(result, "}")
	end

	table.insert(result, "</span>")

	if mw.title.getCurrentTitle().namespace == 0 then
		table.insert(result, geoformatdata.errorCategory)
	end

	return table.concat(result, "")
end

--------------------------------------------------------------------------------
-- Minimalistic Wikidata support
--------------------------------------------------------------------------------

local function selectProperty(claims, pid)
	local prop = claims[pid] if not prop then return false end -- missing property

	-- load preferred statements
	local result = {}
	for _, v in ipairs(prop) do
		if v.rank == "preferred" then
			table.insert(result, v)
		end
	end

	if #result ~= 0 then return true, result end

	for _, v in  ipairs(prop) do
		if v.rank == "normal" then
			table.insert(result, v)
		end
	end

	if #result ~= 0 then return true, result end

	return false -- empty property table
end

local function selectValue(prop, expectedType)
	if not prop then return false end
	if prop.type ~= "statement" then return false end
	local snak = prop.mainsnak
	if not snak or snak.snaktype ~= "value" then return false end
	local datavalue = snak.datavalue
	if not datavalue or datavalue.type ~= expectedType then return false end
	local value = datavalue.value
	if not value then return false end
	return true, value
end

local function wd(property, argGlobe)

	local entity = mw.wikibase.getEntity() if not entity then return nil end -- missing entity
	local claims = entity.claims if not claims then return nil end -- missing claims

	function selectGlobe(globe)
		-- the most often case
		if not globe or (globe == "http://www.wikidata.org/entity/Q2") then
			return { symbol=geoformatdata.displayGlobes.earth.symbol, link="" }
		end
		
		for k, v in pairs(geoformatdata.displayGlobes) do
			if globe == mw.wikibase.getEntityUrl(v.Q) then
				return { link="globe:"..k, data=v }
			end
		end

		return nil
	end

	function selectType()
		local types = {
			unknownType = "type:city",
			{
				property = "P300",
				[150093] = "type:adm1st",
				[247073] = "type:adm2nd",
				[925381] = "type:adm2nd",
				[3504085] = "type:adm3rd",
				[3491915] = "type:adm3rd",
				[2616791] = "type:adm3rd",
			},
			{
				property = "P31",
				[515]  = "type:city",
				[6256] = "type:country",
				[5107] = "type:satellite",
				[165]  = "type:satellite",
			},
		}

		for _, pset in ipairs(types) do
			local status, classes = selectProperty(claims, pset.property)
			if status then
				for _, p in ipairs(classes) do
					local status2, v = selectValue(p, "wikibase-entityid")
					if status2 and v["entity-type"] == "item" then
						local result = pset[v["numeric-id"]]
						if result then return result end
					end
				end
			end
		end
	
		return types.unknownType
	end

	local status1, coordinates = selectProperty(claims, property) if not status1 then return nil end
	local status2, autocoords = selectValue(coordinates[1], "globecoordinate") if not status2 then return nil end
	local globe = argGlobe == "" and { symbol="", link="", data=false } or selectGlobe(argGlobe or autocoords.globe) or { symbol="", link=false, data=false }
	if not globe.link then return nil end -- not supported globe

	local params = {
		selectType(),
	}
	if #globe.link > 0 then
		table.insert(params, globe.link)
	end
	
	local result = {
		latitude = autocoords.latitude,
		longitude = autocoords.longitude,
		precision = autocoords.precision or geoformatdata.defaultWDPrecision,
		params = table.concat(params,"_"),
		displayData = data or geoformatdata.displayGlobes.earth,
		globeSymbol = globe.symbol,
	}

	return result
end

local function parseDisplayPrecision(coordinates, displayPrecision)
	
	local function adjustPrecision(dms)
		if not coordinates.precision or (coordinates.precision >= 1) then
			return
		end

		local eps = coordinates.precision / 1024
		for i, v in ipairs(geoformatdata.supportedFormats) do
			if (v.dms == dms) and ((v.precision - coordinates.precision) < eps) then
				coordinates.precision = v.precision
				break
			end
		end
	end

	local function findAndSetPrecision()		
		-- find wikipedia template precision
		for i, v in ipairs(geoformatdata.supportedFormats) do
			if displayPrecision == v.prec then
				coordinates.precision = v.precision
				return true
			end
		end
	end

	if displayPrecision == geoformatdata.valPrecisionAutoDMS then
		adjustPrecision(true)
	elseif displayPrecision == geoformatdata.valPrecisionAutoDecimal then
		adjustPrecision(false)
	elseif not findAndSetPrecision() then
		return false
	end

	return true
end

local function distance(A, B)
	-- [[Ortodroma]]
	-- <math>D = \operatorname{arc cos}((\sin \varphi_1 \sin \varphi_2)+(\cos \varphi_1 \cos \varphi_2 \cos \Delta\lambda)),</math>
	local phiA = math.pi * A.latitude / 180.0
	local phiB = math.pi * B.latitude / 180.0
	local delta = math.pi * (B.longitude - A.longitude) / 180.0
	return math.acos(math.sin(phiA)*math.sin(phiB) + math.cos(phiA)*math.cos(phiB)*math.cos(delta))
end

local function size(A)
	local precision = A.precision or geoformatdata.defaultSizePrecision
	local B = {}
	B.latitude = A.latitude < 0 and A.latitude + precision or A.latitude - precision
	B.longitude = A.longitude + precision
	return distance(A,B)
end

--------------------------------------------------------------------------------
-- public module methods
--------------------------------------------------------------------------------

return {

[geoformatdata.apiCoordinates] = function (frame)
	local args = require('Module:Arguments').getArgs(frame, {
		trim = false,
		removeBlanks = false,
		wrappers = geoformatdata.wrappersCoordinates,
	})
	local coords = args[geoformatdata.argCoordinatesCoordinates]
	local geohack = args[geoformatdata.argCoordinatesGeohack]
	local name = args[geoformatdata.argName]
	local location = args[geoformatdata.argLocation]
	local displayPrecision = args[geoformatdata.argPrecision]
	local link = args[geoformatdata.argLink]
	local symbol = args[geoformatdata.argSymbol]
	
	if symbol == geoformatdata.valSymbolYes then
		symbol = true
	elseif symbol == geoformatdata.valSymbolNo then
		symbol = false
	elseif symbol and (#symbol==0) then
		return showError(geoformatdata.errorEmptySymbolOption, {})
	end
	
	if not coords and not geohack and not name and not displayPrecision and not link and (location == geoformatdata.valLocationTop) and (symbol == nil) then
		local autocoords = wd("P625", false)
		if not autocoords then
			-- missing data in  WD
			return
		end
		
		local coordinates = create()
		coordinates.latitude = autocoords.latitude
		coordinates.longitude = autocoords.longitude
		coordinates.precision = autocoords.precision
		coordinates.params = autocoords.params
		coordinates.displayData = autocoords.displayData
		coordinates.inline = false
		coordinates.top	   = true
		coordinates.link   = true
		coordinates:normalize()
		return coordinates:display()..coordinates:extensionGeoData()
	end
	
	local coordinates = create()
	local status, errorMessage = coordinates:parse(coords, geohack, displayPrecision)
	if not status then
		return showError(errorMessage, args)
	end

	coordinates.symbol = symbol
	coordinates.name = name

	local full = location or displayPrecision or link or (symbol ~= nil) or (coordinates.displayData and (coordinates.displayData.Q ~= "Q2"))
	if full then
		
		if displayPrecision and not parseDisplayPrecision(coordinates, displayPrecision) then
			return showError(string.format(geoformatdata.errorUnrecognizedPrecisionOption, displayPrecision), {})
		end
		
		if link == geoformatdata.valLinkYes then
			coordinates.link = true
		elseif link == geoformatdata.valLinkNo then
			coordinates.link = false
		elseif link == geoformatdata.valLinkGMS then
			coordinates.link = "gms"
		elseif link then
			return showError(string.format(geoformatdata.errorUnrecognizedLinkOption, link), {})
		else -- default is "yes"
			coordinates.link = true
		end

		if location == geoformatdata.valLocationTop then
			coordinates.top = true
			coordinates.inline = false
		elseif location == geoformatdata.valLocationInline then
			coordinates.top = false
			coordinates.inline = true
		elseif location == geoformatdata.valLocationTopAndInline then
			coordinates.top = true
			coordinates.inline = true
		elseif location then
			return showError(string.format(geoformatdata.errorUnrecognizedLocationOption, location), {})
		else -- default if not given
			coordinates.top = false
			coordinates.inline = true
		end
	else -- micro
		-- options are implied in micro variant
		if coordinates.precision > 0.00027777777777777800 then
			coordinates.precision = 0.00027777777777777800 -- seconds
		elseif coordinates.precision < 0.00002777777777777780 then
			coordinates.precision = 0.00002777777777777780 -- seconds with one decimal digit
		end

		if not coordinates.params then
			coordinates.params	= "scale:5000" -- bonus
		end

		coordinates.inline = true
		coordinates.top	   = false
		coordinates.link   = true
	end

	coordinates:normalize()
	local result = {}
	table.insert(result, coordinates:display())
	if full then
		table.insert(result, coordinates:extensionGeoData())
	end
	
	return table.concat(result)
end,

[geoformatdata.apiMapPoint] = function(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		trim = false,
		removeBlanks = false,
		wrappers = geoformatdata.wrappersMapPoint,
	})
	local coordinates = create()
	local description = args[geoformatdata.argDescription]
	local symbol = args[geoformatdata.argSymbol]

	geohack = geoformatdata.mapPointMapping[description] or args[geoformatdata.argMapPointGeohack]
	local status, errorMessage, fromWD = false, false, false
	if args[geoformatdata.argMapPointCoordinates] then
		status, errorMessage = coordinates:parse(args[geoformatdata.argMapPointCoordinates],geohack)
	else
		local autocoords = wd("P625", false)
		if not autocoords then
			-- missing data in WD
			return
		end

		coordinates.latitude = autocoords.latitude
		coordinates.longitude = autocoords.longitude
		coordinates.precision = autocoords.precision
		coordinates.params = autocoords.params or geohack
		coordinates.displayData = autocoords.displayData
		status = true
		fromWD = true
	end

	local point = {}
	if not status then
		point.error = showError(errorMessage, args)
	else
		coordinates:normalize()
		point.latitude = coordinates.latitude
		point.longitude = coordinates.longitude
		point.link = coordinates:geohack(false, false, false)
		if args.display then
			if symbol == geoformatdata.valSymbolYes then
				coordinates.symbol = true
			elseif symbol == geoformatdata.valSymbolNo then
				coordinates.symbol = false
			elseif symbol and (#symbol==0) then
				point.error = showError(geoformatdata.errorEmptySymbolOption, {})
			else
				coordinates.symbol = symbol
			end

			coordinates.top = mw.title.getCurrentTitle().namespace == 0
			coordinates.inline = true
			if fromWD and (args.display == "#coordinates") then
				point.display = coordinates:display()..coordinates:extensionGeoData()
			else
				point.display = coordinates:display()
			end
		end
	end

	point.mark = args[geoformatdata.argMark] or geoformatdata.defArgMark
	point.size = tonumber(args[geoformatdata.argMarkSize] or geoformatdata.defArgMarkSize)
	point.description = description
	point.position = args[geoformatdata.argDescriptionPosition]
	point.alt = args[geoformatdata.argAlt]
	if not coordinates.params then
		point.geohack = geoformatdata.defArgGeohack
	end
	
	return mw.text.jsonEncode(point)..","
end,

[geoformatdata.apiCheckDistance] = function(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		trim = false,
		removeBlanks = false,
	})
	local scalingFactor = tonumber(args[geoformatdata.argScalingFactor]) or 6731000
	local displayPrecision = args[geoformatdata.argPrecision]
	local maximumDistance = tonumber(args[geoformatdata.argMaximumDistance])
	local errorMessage = args[geoformatdata.argErrorMessage]
	local coords = args[geoformatdata.argCoordinatesCoordinates]
	if not errorMessage or not maximumDistance or not coords then
		-- nie ma nic do wyślwietlenia lub sprawdzenia
		mw.log("apiCheckDistance: nic nie ma")
		return
	end
	
	local A = create()
	local status, error
	status, error =  A:parse(coords, nil, displayPrecision)
	if not status then
		mw.logObject(error, "apiCheckDistance: parse error")
		return
	end
	
	-- precyzja jeszcze nie jest używana...
	if displayPrecision and not parseDisplayPrecision(A, display) then
		mw.logObject(error, "apiCheckDistance: parsePrecision error")
		return
	end
	
	local B = wd("P625", false)
	if not B then
		mw.logObject(B, "apiCheckDistance: missing data in WD")
		return
	end
	
	-- ... ale już jest wykorzystana na wyznaczenie promienia błędu
	A.radius = scalingFactor * size(A)
	B.radius = scalingFactor * size(B)
	
	local distance = scalingFactor * distance(A,B)
	if distance <= maximumDistance then
		-- brak błędów
		return
	end
	
	-- zalogujmy co się da
	mw.logObject({A, WD = B, distance = distance}, "apiCheckDistance")
	
	-- parametry komunikatu
	local parameters =
	{
		distance = tostring(math.floor(distance + 0.5)),
		radiusA = tostring(math.floor(A.radius + 0.5)),
		radiusB = tostring(math.floor(B.radius + 0.5)),
	}
	local message, _ = string.gsub(errorMessage, "%(%(%((.-)%)%)%)", parameters)
	
	return message
end,

[geoformatdata.apiDistance] = function(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		trim = false,
		removeBlanks = false,
	})

	local scalingFactor = tonumber(args[geoformatdata.argScalingFactor]) or geoformatdata.defaultDistanceScalingFactor
	local coordsA = args[1]
	local coordsB = args[2]

	local A = create()
	local status, error
	status, error =  A:parse(coordsA, nil, nil)
	if not status then
		mw.logObject(error, "apiDistance: parse error A")
		return
	end
	
	local B
	if coordsB then
		B = create()
		status, error =  B:parse(coordsB, nil, nil)
		if not status then
			mw.logObject(error, "apiDistance: parse error B")
			return
		end
	else
		B = wd("P625", false)
		if not B then
			mw.logObject(B, "apiDistance: missing data in WD")
			return
		end
	end

	return scalingFactor * distance(A,B)
end,

[geoformatdata.apiPrecisionRadius] = function(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		trim = false,
		removeBlanks = false,
	})

	local scalingFactor = tonumber(args[geoformatdata.argScalingFactor]) or geoformatdata.defaultDistanceScalingFactor
	local displayPrecision = args[geoformatdata.argPrecision]
	local coords = args[geoformatdata.argCoordinatesCoordinates]

	local A
	if coords then
		A = create()
		local status, error
		status, error =  A:parse(coords, nil, displayPrecision)
		if not status then
			mw.logObject(error, "apiPrecisionRadius: parse error")
			return
		end
		
		if displayPrecision and not parseDisplayPrecision(A, displayPrecision) then
			mw.logObject(displayPrecision, "apiPrecisionRadius: parsePrecision error")
			return
		end
		
	else
		A = wd("P625", false)
		if not A then
			mw.logObject(A, "apiPrecisionRadius: missing data in WD")
			return
		end
	end
	
	return scalingFactor * size(A)
end,

}