Moduł:Współrzędne
Moduł pomocniczy do obsługi współrzędnych geograficznych.
współrzędne
edytuj
Implementacja szablonu {{współrzędne}}. Jeśli argumenty przekazane do szablonu są nieprawidłowe, to wynikiem jest komunikat błędu.
parametry szablonu
edytujPole | 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{{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
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
edytujpole | 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
Funkcja diagnostyczna obliczająca przybliżoną odległość między dwoma punktami na globie. Domyślnie zwraca wynik w metrach dla Ziemi.
parametry
edytujPole | 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
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
edytujPole | 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
edytujBłędy należy zgłaszać na stronie Wikipedia:Kawiarenka/Kwestie techniczne lub Dyskusja wikiprojektu:Szablony lokalizacyjne.
Zobacz też
edytujZobacz 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,
}