Moduł:Współrzędne
Moduł pomocniczy do obsługi współrzędnych geograficznych.
współrzędne
[edytuj kod]Implementacja szablonu {{współrzędne}}. Jeśli argumenty przekazane do szablonu są nieprawidłowe, to wynikiem jest komunikat błędu.
parametry szablonu
[edytuj kod]Pole | Do czego służy? | Jak wypełnić? | Uwagi |
---|---|---|---|
1 | Współrzędne geograficzne tj. stopnie, minuty i sekundy szerokości geograficznej oraz stopnie, minuty i sekundy długości geograficznej. Akceptowane są dwie liczby interpretowane odpowiednio jako szerokość i długość geograficzna lub dwa zbiory do trzech nieujemnych liczb zakończonych znakiem N, S, W, E określających półkulę. Liczby mogą być ozdobione znakami stopni, minut i sekund lecz są one ignorowane. | {{...|54 22 11 N 18.913W}}
|
Pole jest obowiązkowe z wyjątkiem sytuacji opisanej przy polu umieść .
|
2 | Opcjonalny parametr zawierający dodatkowe parametry przekazywane w linku do geohacka. | {{...|54 22 11 N 18.913W|type:city}}
|
Jeśli pole nie jest podane a wywołanie szablonu nie zawiera żadnych pól oprócz nazwa to przyjmowana jest wartość domyślna scale:5000 , która pośrednio współgra z automatyczną dokładnością określaną na sek lub sek+ .
|
umieść
|
Parametr opcjonalny do pozycjonowania wyniku:
|
{{...|54 22 11 N 18.913W|umieść=na górze}}
|
Jeśli jest to jedyne pole w wywołaniu szablonu o postaci umieść=na górze to współrzędne są ładowane z Wikidanych wykorzystując cechę P625 (współrzędne geograficzne).
|
dokładność
|
Określa sposób formatowania wyniku:
|
{{...|54 22 11 N 18.913W|dokładność=min}}
|
Jeśli wywołanie szablonu nie zawiera żadnych pól nazwanych, z wyjątkiem pola nazwa to automatyczna dokładność jest ustalana w tryb sek lub sek+ , w zależności od współrzędnych wejściowych. W przeciwnym razie dobierana jest dokładność kątowa wystarczająca do zaprezentowania podanych współrzędnych.
|
nazwa
|
Opcjonalna nazwa obiektu geograficznego. | {{...|54 22 11 N 18.913W|nazwa=Trójmiasto}}
|
|
linkuj
|
Określania sposobu generowania linków do geohacka:
|
{{...|54 22 11 N 18.913W|linkuj=nie}}
|
|
symbol
|
Określania sposobu generowania symbolu ciała niebieskiego przed współrzędnymi:
|
{{...|54 22 11 N 18.913W|symbol=tak}}
|
Domyślnie symbol jest widoczny dla współrzędnych wszystkich ciał niebieskich oprócz Ziemi. |
przykłady
[edytuj kod]{{Koordynaty|...}}
|
wynik | uwagi |
---|---|---|
57 18 22 N 4 27 32 W
|
57°18′22″N 4°27′32″W/57,306111 -4,458889 | |
44.112N 87.913W
|
44°06′43″N 87°54′47″W/44,112000 -87,913000 | |
54 18|type:city
|
54°00′00″N 18°00′00″E/54,000000 18,000000 | |
44.112°N 4°27'32W
|
44°06′43″N 4°27′32″W/44,112000 -4,458889 | |
bzdury 60 80
|
Nieprawidłowe parametry: {bzdury 60 80} | błędne wywołanie |
60 80|umieść=w tekście
|
60°N 80°E/60,000000 80,000000 | automatyczna dokładność w stopniach |
60.0 80.0|umieść=w tekście
|
60°00′N 80°00′E/60,000000 80,000000 | automatyczna dokładność w minutach |
dokładność=3|60 80
|
60,000°N 80,000°E/60,000000 80,000000 | |
dokładność=sek+|60 80
|
60°00′00,0″N 80°00′00,0″E/60,000000 80,000000 | |
60.000000 80|umieść=w tekście
|
60°00′00,000″N 80°00′00,000″E/60,000000 80,000000 | automatyczna dokładność w ułamkach sekund |
54 22 18 38
|
Nieprawidłowe parametry: {54 22 18 38} | błędne wywołanie; przy dokładności do minut lub sekund wymagane jest podanie liter szerokości/długości w celu zwiększenia klarowności składni |
54 22 00.00 18 38 00
|
Nieprawidłowe parametry: {54 22 00.00 18 38 00} | błędne wywołanie; przy dokładności do minut lub sekund wymagane jest podanie liter szerokości/długości w celu zwiększenia klarowności składni |
57°18'22,8″N 4°27'32,2″W
|
57°18′22,8″N 4°27′32,2″W/57,306333 -4,458944 | |
97 18 22.8 N 4 27 32.2 W
|
Przekroczony zakres szerokości geograficznej (97.306333): {97 18 22.8 N 4 27 32.2 W} | błędne wywołanie, >90 stopni szerokości |
57 18 -22.8 N 4 27 32.2 W
|
Nieprawidłowe parametry: {57 18 -22.8 N 4 27 32.2 W} | błędne wywołanie, ujemne sekundy |
punkt
[edytuj kod]Implementacja szablonu {{Mapa lokalizacyjna/punkt}}. Funkcja przyjmuje parametry bezpośrednie, szablonowe lub bezpośrednio w tablicy dla wywołań z innych modułów. Jeśli nie podano żadnych współrzędnych to ładuje dane z Wikidanych wskazując cechę współrzędne geograficzne.
Opis parametrów
[edytuj kod]pole | opis | status | wartość domyślna |
---|---|---|---|
1 | współrzędne geograficzne wskazywanego punktu | obowiązkowy | brak |
opis | napis na mapie opisujący zaznaczony punkt | sugerowany | brak |
pozycja | wskazanie, z której strony punktu na mapie należy umieścić opis, przydatny jeśli automatyczne pozycjonowanie daje wyniki niepoprawne lub niefortunne | opcjonalny | brak |
znak | znak użyty do oznaczenia punktu na mapie o podanych współrzędnych | opcjonalny | Red pog.svg |
rozmiar znaku | rozmiar grafiki do oznaczania punktu na mapie | opcjonalny | 6 |
opcje geohack | dodatkowe parametry dla generowania linkujących współrzędnych (zobacz {{współrzędne}}) | opcjonalny | brak |
symbol | dodatkowe parametry dla generowania linkujących współrzędnych (zobacz {{współrzędne}}) | opcjonalny | brak |
alt | opis wyglądu znaku do wygenerowania opisu alt dla obrazu mapy | opcjonalny | brak[i] |
- ↑ Jeśli na mapie jest tylko jeden punkt to może zostać wygenerowany domyślny opis na podstawie nazwy pliku na przykład „punkt”.
Odległość
[edytuj kod]Funkcja diagnostyczna obliczająca przybliżoną odległość między dwoma punktami na globie. Domyślnie zwraca wynik w metrach dla Ziemi.
parametry
[edytuj kod]Pole | Do czego służy? | Jak wypełnić? | Uwagi |
---|---|---|---|
1 | Współrzędne geograficzne tj. stopnie, minuty i sekundy szerokości geograficznej oraz stopnie, minuty i sekundy długości geograficznej. Akceptowane są dwie liczby interpretowane odpowiednio jako szerokość i długość geograficzna lub dwa zbiory do trzech nieujemnych liczb zakończonych znakiem N, S, W, E określających półkulę. Liczby mogą być ozdobione znakami stopni, minut i sekund lecz są one ignorowane. | {{...|54 22 11 N 18.913W}}
|
W przypadku błędu wynik będzie pusty. |
2 | Opcjonalne współrzędne drugiego punktu. | {{...|...|54 22 11 N 18.913W}}
|
Jeśli pole nie jest podane to pobierane są współrzędne z Wikidanych (współrzędne geograficzne (P625)). Jeśli takich nie ma wynik jest pusty. |
mnożnik
|
Opcjonalny promień koła wielkiego przechodzącego przez dwa wskazane punkty. Domyślnie promień Ziemi w metrach. | {{...|54 22 11 N 18.913W|mnożnik=6730}}
|
Promień dokładności
[edytuj kod]Funkcja diagnostyczna obliczająca przybliżony promień okręgu opisanego na „prostokącie błędu” wynikającego z dokładności prezentacji współrzędnych. Domyślnie zwraca wynik w metrach dla Ziemi.
parametry
[edytuj kod]Pole | Do czego służy? | Jak wypełnić? | Uwagi |
---|---|---|---|
1 | Opcjonalne współrzędne geograficzne tj. stopnie, minuty i sekundy szerokości geograficznej oraz stopnie, minuty i sekundy długości geograficznej. Akceptowane są dwie liczby interpretowane odpowiednio jako szerokość i długość geograficzna lub dwa zbiory do trzech nieujemnych liczb zakończonych znakiem N, S, W, E określających półkulę. Liczby mogą być ozdobione znakami stopni, minut i sekund lecz są one ignorowane. | {{...|54 22 11 N 18.913W}}
|
Jeśli dane są nieprawidłowe to wynik jest pusty. Jeśli pole nie jest podane to pobierane są współrzędne z Wikidanych (współrzędne geograficzne (P625)). Jeśli takich nie ma to wynik jest pusty. |
mnożnik
|
Opcjonalny promień koła wielkiego przechodzącego przez wskazany punkt. Domyślnie promień Ziemi w metrach. | {{...|54 22 11 N 18.913W|mnożnik=673000000}}
|
|
dokładność
|
Opcjonalne wskazanie sposobu prezentacji wyniku:
|
{{...|54 22 11 N 18.913W|dokładność=min}}
|
Domyślna dokładność jest określana na podstawie najmniej znaczących cyfr w podanych współrzędnych. |
Błędy
[edytuj kod]Błędy należy zgłaszać na stronie Wikipedia:Kawiarenka/Kwestie techniczne lub Dyskusja wikiprojektu:Szablony lokalizacyjne.
Zobacz też
[edytuj kod]Zobacz podstrony tego modułu.
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, "¶ms=")
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, "|")
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,
}