「モジュール:Coordinates」の版間の差分
ナビゲーションに移動
検索に移動
細 (1版) |
bsd>Jarekt (support of SDC) |
||
1行目: | 1行目: | ||
--[[ | --[[ | ||
__ __ _ _ ____ _ _ _ | |||
| \/ | ___ __| |_ _| | ___ _ / ___|___ ___ _ __ __| (_)_ __ __ _| |_ ___ ___ | |||
| |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \ / _ \| '__/ _` | | '_ \ / _` | __/ _ \/ __| | |||
| | | | (_) | (_| | |_| | | __/_| |__| (_) | (_) | | | (_| | | | | | (_| | || __/\__ \ | |||
|_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/ \___/|_| \__,_|_|_| |_|\__,_|\__\___||___/ | |||
This module is intended to | This module is intended to provide functionality of {{location}} and related | ||
templates. | templates. It was developed on Wikimedia Commons, so if you find this code on | ||
other sites, check there for updates and discussions. | |||
*function | Please do not modify this code without applying the changes first at Module:Coordinates/sandbox and testing | ||
**function | at Module:Coordinates/sandbox/testcases and Module talk:Coordinates/sandbox/testcases. | ||
***function | |||
****function | Authors and maintainers: | ||
***function | * User:Jarekt | ||
****function | * User:Ebraminio | ||
**function | |||
**function | Functions: | ||
***function | *function p.LocationTemplateCore(frame) | ||
*function | **function p.GeoHack_link(frame) | ||
*function | ***function p.lat_lon(frame) | ||
****function p._deg2dms(deg,lang) | |||
***function p.externalLink(frame) | |||
****function p._externalLink(site, globe, latStr, lonStr, lang, attributes) | |||
**function p._getHeading(attributes) | |||
**function p.externalLinksSection(frame) | |||
***function p._externalLink(site, globe, latStr, lonStr, lang, attributes) | |||
*function p.getHeading(frame) | |||
*function p.deg2dms(frame) | |||
]] | ]] | ||
-- ======================================= | -- ======================================= | ||
-- === Dependencies ====================== | -- === Dependencies ====================== | ||
-- ======================================= | -- ======================================= | ||
require('Module:No globals') -- used for debugging purposes as it detects cases of unintended global variables | |||
local | local i18n = require('Module:I18n/coordinates') -- get localized translations of site names | ||
local yesno | local yesno = require('Module:Yesno') | ||
-- ======================================= | -- ======================================= | ||
31行目: | 44行目: | ||
-- ======================================= | -- ======================================= | ||
-- Angles associated with each | -- Angles associated with each abbreviation of compass point names. See [[:en:Points of the compass]] | ||
local compass_points = { | local compass_points = { | ||
N = 0, | N = 0, | ||
67行目: | 80行目: | ||
} | } | ||
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr will be replaced with latitude, longitude, language code | -- files to use for different headings | ||
local heading_icon = { | |||
[ 1] = 'File:Compass-icon bb N.svg', | |||
[ 2] = 'File:Compass-icon bb NbE.svg', | |||
[ 3] = 'File:Compass-icon bb NNE.svg', | |||
[ 4] = 'File:Compass-icon bb NEbN.svg', | |||
[ 5] = 'File:Compass-icon bb NE.svg', | |||
[ 6] = 'File:Compass-icon bb NEbE.svg', | |||
[ 7] = 'File:Compass-icon bb ENE.svg', | |||
[ 8] = 'File:Compass-icon bb EbN.svg', | |||
[ 9] = 'File:Compass-icon bb E.svg', | |||
[10] = 'File:Compass-icon bb EbS.svg', | |||
[11] = 'File:Compass-icon bb ESE.svg', | |||
[12] = 'File:Compass-icon bb SEbE.svg', | |||
[13] = 'File:Compass-icon bb SE.svg', | |||
[14] = 'File:Compass-icon bb SEbS.svg', | |||
[15] = 'File:Compass-icon bb SSE.svg', | |||
[16] = 'File:Compass-icon bb SbE.svg', | |||
[17] = 'File:Compass-icon bb S.svg', | |||
[18] = 'File:Compass-icon bb SbW.svg', | |||
[19] = 'File:Compass-icon bb SSW.svg', | |||
[20] = 'File:Compass-icon bb SWbS.svg', | |||
[21] = 'File:Compass-icon bb SW.svg', | |||
[22] = 'File:Compass-icon bb SWbW.svg', | |||
[23] = 'File:Compass-icon bb WSW.svg', | |||
[24] = 'File:Compass-icon bb WbS.svg', | |||
[25] = 'File:Compass-icon bb W.svg', | |||
[26] = 'File:Compass-icon bb WbN.svg', | |||
[27] = 'File:Compass-icon bb WNW.svg', | |||
[28] = 'File:Compass-icon bb NWbW.svg', | |||
[29] = 'File:Compass-icon bb NW.svg', | |||
[30] = 'File:Compass-icon bb NWbN.svg', | |||
[31] = 'File:Compass-icon bb NNW.svg', | |||
[32] = 'File:Compass-icon bb NbW.svg' | |||
} | |||
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr, $page will be | |||
-- replaced with latitude, longitude, language code, GeoHack attribution parameters and full-page-name strings. | |||
local SiteURL = { | local SiteURL = { | ||
GeoHack = ' | GeoHack = '//tools.wmflabs.org/geohack/geohack.php?pagename=$page¶ms=$lat_N_$lon_E_$attr&language=$lang', | ||
GoogleEarth = ' | GoogleEarth = '//tools.wmflabs.org/geocommons/earth.kml?latdegdec=$lat&londegdec=$lon&scale=10000&commons=1', | ||
Proximityrama = ' | Proximityrama = '//tools.wmflabs.org/geocommons/proximityrama?latlon=$lat,$lon', | ||
WikimediaMap = '//maps.wikimedia.org/#16/$lat/$lon', | |||
OpenStreetMap1 = '//tools.wmflabs.org/wiwosm/osm-on-ol/commons-on-osm.php?zoom=16&lat=$lat&lon=$lon', | |||
OpenStreetMap2 = '//tools.wmflabs.org/osm4wiki/cgi-bin/wiki/wiki-osm.pl?project=Commons&article=$page&l=$level', | |||
GoogleMaps = { | GoogleMaps = { | ||
Mars = ' | Mars = '//www.google.com/mars/#lat=$lat&lon=$lon&zoom=8', | ||
Moon = ' | Moon = '//www.google.com/moon/#lat=$lat&lon=$lon&zoom=8', | ||
Earth = ' | Earth = '//tools.wmflabs.org/wp-world/googlmaps-proxy.php?page=http://tools.wmflabs.org/kmlexport/%3Fproject%3DCommons%26article%3D$page&l=$level&output=classic' | ||
} | } | ||
} | } | ||
85行目: | 137行目: | ||
Gallery = '[[Category:Galleries with coordinates]]', | Gallery = '[[Category:Galleries with coordinates]]', | ||
Category = '[[Category:Categories with coordinates]]', | Category = '[[Category:Categories with coordinates]]', | ||
wikidata0 = '[[Category:Pages with coordinates from Wikidata]]', | |||
wikidata1 = '[[Category:Pages with local coordinates and matching Wikidata coordinates]]', | |||
wikidata2 = '[[Category:Pages with local coordinates and similar Wikidata coordinates]]', | |||
wikidata3 = '[[Category:Pages with local coordinates and mismatching Wikidata coordinates]]', | |||
wikidata4 = '[[Category:Pages with local coordinates and missing Wikidata coordinates]]', | |||
wikidata5 = '[[Category:Pages with locations and Wikidata ID to wrong type of entry]]', | |||
sdc0 = '[[Category:Pages with coordinates from SDC]]', | |||
sdc1 = '[[Category:Pages with local coordinates and matching SDC coordinates]]', | |||
sdc2 = '[[Category:Pages with local coordinates and similar SDC coordinates]]', | |||
sdc3 = '[[Category:Pages with local coordinates and mismatching SDC coordinates]]', | |||
sdc4 = '[[Category:Pages with local coordinates and missing SDC coordinates]]', | |||
globe = '[[Category:Media with %s locations]]', | globe = '[[Category:Media with %s locations]]', | ||
default = '[[Category:Media with default locations]]', | default = '[[Category:Media with default locations]]', | ||
erroneous = '[[Category:Media with erroneous locations]]<span style="color:red;font-weight:bold">Error: Invalid parameters!</span>\n' | attribute = '[[Category:Media with erroneous geolocation attributes]]', | ||
erroneous = '[[Category:Media with erroneous locations]]<span style="color:red;font-weight:bold">Error: Invalid parameters!</span>\n', | |||
dms = '[[Category:Media with coordinates in DMS format]]' | |||
} | } | ||
-- ======================================= | -- ======================================= | ||
-- === Functions | -- === Local Functions =================== | ||
-- ======================================= | -- ======================================= | ||
-- | local function normalize_input_args(input_args, output_args) | ||
function | for name, value in pairs( input_args ) do | ||
if value ~= '' then -- nuke empty strings | |||
if type(name)=='string' then | |||
name = string.lower(name) | |||
end | |||
output_args[name] = string.gsub(value, "^%s*(.-)%s*$", "%1") -- trim whitespaces from the beggining and the end of the string | |||
end | |||
end | |||
return output_args | |||
end | |||
local function getArgs(frame) | |||
local args = {} | |||
args = normalize_input_args(frame:getParent().args, args) | |||
args = normalize_input_args(frame.args, args) | |||
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then | |||
args.lang = frame:callParserFunction("int","lang") -- get user's chosen language | |||
end | |||
return args | |||
end | |||
local NoLatLonString = 'latitude, longitude' | |||
local function langSwitch(list,lang) | |||
local langList = mw.language.getFallbacksFor(lang) | |||
table.insert(langList,1,lang) | |||
for i,language in ipairs(langList) do | |||
if list[language] then | |||
return list[language] | |||
end | |||
end | |||
end | |||
local function add_maplink(lat, lon, marker, text) | |||
local tstr = '' | |||
if text then | |||
tstr = string.format('text="%s" ', text) | |||
end | |||
return string.format('<maplink %szoom="13" latitude="%f" longitude="%f" class="no-icon">{'.. | |||
' "type": "Feature",'.. | |||
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'.. | |||
' "properties": { "marker-symbol":"%s", "marker-size": "large", "marker-color": "0050d0" }'.. | |||
'}</maplink>', tstr, lat, lon, lon, lat, marker) | |||
end | |||
local function add_maplink2(lat1, lon1, lat2, lon2) | |||
return string.format('<maplink zoom="13" latitude="%f" longitude="%f" class="no-icon">[{'.. | |||
' "type": "Feature",'.. | |||
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'.. | |||
' "properties": { "marker-symbol":"c", "marker-size": "large", "marker-color": "0050d0", "title": "Location on Wikimedia Commons" }'.. | |||
'},{'.. | |||
' "type": "Feature",'.. | |||
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'.. | |||
' "properties": { "marker-symbol":"w", "marker-size": "large", "marker-color": "228b22", "title": "Location on Wikidata" }'.. | |||
'}]</maplink>', lat2, lon2, lon1, lat1, lon2, lat2) | |||
end | |||
local function info_box(text) | |||
return string.format('<table class="messagebox plainlinks layouttemplate" style="border-collapse:collapse; border-width:2px; border-style:solid; width:100%%; clear: both; '.. | |||
'border-color:#f28500; background:#ffe;direction:ltr; border-left-width: 8px; ">'.. | |||
'<tr>'.. | |||
'<td class="mbox-image" style="padding-left:.9em;">'.. | |||
' [[File:Commons-emblem-issue.svg|class=noviewer|45px]]</td>'.. | |||
'<td class="mbox-text" style="">%s</td>'.. | |||
'</tr></table>', text) | |||
end | |||
local function distance(lat1, lon1, lat2, lon2) | |||
-- calculate distance | |||
local dLat = math.rad(lat1-lat2) | |||
local dLon = math.rad(lon1-lon2) | |||
local d = math.pow(math.sin(dLat/2),2) + math.pow(math.sin(dLon/2),2) * math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) | |||
d = 2 * math.atan2(math.sqrt(d), math.sqrt(1-d)) -- angular distance in radians | |||
d = 6371000 * d -- radians to meters conversion | |||
d = math.floor(d+0.5) -- round it to even meters | |||
return d | |||
end | end | ||
-- | local function mergeWithWikidata(qID, lat1, lon1) | ||
-- we are given wikidata q-code so look up the coordinates | |||
local | local dist_str='' | ||
if | local entity | ||
-- Wikiata coordinates | |||
elseif | if qID==nil then | ||
entity = mw.wikibase.getEntity() | |||
elseif type(qID)=='string' and qID:match( '^[Qq]%d+$' ) then | |||
entity = mw.wikibase.getEntity(qID) | |||
else | else | ||
return '' | entity = qID | ||
end | |||
if not entity then | |||
return lat1, lon1, nil, '', dist_str | |||
end | |||
qID = entity.id | |||
local v, lat2, lon2, precision | |||
if entity then | |||
local P625 = entity:getBestStatements( 'P625' ) -- coordinate location | |||
local P159 = entity:getBestStatements( 'P159' ) -- headquarters location | |||
if P625[1] and P625[1].mainsnak.datavalue.value.latitude then | |||
v = P625[1].mainsnak.datavalue.value | |||
elseif P159[1] and P159[1].qualifiers and P159[1].qualifiers.P625 then | |||
v = P159[1].qualifiers.P625[1].datavalue.value | |||
end | |||
if v and v.globe == 'http://www.wikidata.org/entity/Q2' then | |||
lat2 = v.latitude | |||
lon2 = v.longitude | |||
precision = v.precision or 1e-4 | |||
precision = math.floor(precision*111000) -- convert precision from degrees to meters and round | |||
precision = math.max(math.min(precision,111000),5) -- bound precision to a number between 5 meters and 1 degree | |||
end | |||
end | |||
-- compare coordinates | |||
local cat = '' | |||
if not lat1 or not lon1 then -- wikidata coordinates only | |||
lat1 = lat2 | |||
lon1 = lon2 | |||
cat = CoorCat.wikidata0 | |||
elseif lat1 and lon1 and not lat2 and not lon2 then | |||
cat = string.format('The above coordinates are missing from linked Wikidata item [[d:%s|%s]]. Click <span class=\"plainlinks\" title=\"Click to copy to wikidata\">'.. | |||
"[https://tools.wmflabs.org/quickstatements/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span> to copy it", | |||
qID, qID, qID, lat1, lon1) | |||
cat = CoorCat.wikidata4 .. info_box(cat) | |||
elseif lat1 and lon1 and lat2 and lon2 then | |||
local d = distance(lat1, lon1, lat2, lon2) -- calculate distance | |||
local frame = mw.getCurrentFrame() | |||
local info = frame:preprocess(add_maplink2(lat1, lon1, lat2, lon2)) -- fancy link to OSM | |||
info = string.format("There is a discrepancy of %i meters between the above coordinates and the ones stored at linked Wikidata item [[d:%s|%s]] (%s, precision: %i m). ".. | |||
'Please reconcile them. To copy Commons coordinates to Wikidata, click <span class=\"plainlinks\" title=\"Click to copy to wikidata\">'.. | |||
"[https://tools.wmflabs.org/quickstatements/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span>", | |||
d, qID, qID, info, precision, qID, lat1, lon1) | |||
if d<20 or d<precision then -- will consider location within 20 meters or precisi0on distance as the same | |||
cat = CoorCat.wikidata1 | |||
dist_str = string.format(' (discrepancy of %i meters between the above coordinates and the ones stored on Wikidata)', d) -- will be displayed when hovering a mouse above wikidata icon | |||
elseif d>1000 and d>5*precision then -- locations 1 km off and 5 precision distances away are likely wrong | |||
cat = CoorCat.wikidata3 .. info_box(info) | |||
else | |||
cat = CoorCat.wikidata2 .. info_box(info) | |||
end | |||
end | |||
-- verify proper P31 (instance of). List is based on https://www.wikidata.org/wiki/Property_talk:P625 | |||
local QCodes = { | |||
Q5 = 1, -- human | |||
Q11879590 = 1, -- female given name | |||
Q202444 = 1, -- given name | |||
Q12308941 = 1, -- male given name | |||
Q4167836 = 1, -- Wikimedia category | |||
Q4167410 = 1, -- Wikimedia disambiguation page | |||
Q783794 = 2, -- company | |||
Q4830453 = 2, -- business enterprise | |||
} | |||
local s = entity:getBestStatements( 'P31' ) | |||
if s[1] and s[1].mainsnak.datavalue.value['id'] then | |||
local instanceOf = s[1].mainsnak.datavalue.value['id'] | |||
if QCodes[instanceOf] then | |||
cat = '' -- wipe out categories | |||
if QCodes[instanceOf]==1 then -- add problem category | |||
cat = CoorCat.wikidata5 | |||
end | |||
end | |||
end | |||
return lat1, lon1, qID, cat, dist_str | |||
end | |||
local function mergeWithSDC(lat1, lon1, heading1) | |||
-- we are given SDC m-code so look up the coordinates | |||
local entity = nil | |||
-- Wikiata coordinates | |||
entity = mw.wikibase.getEntity() | |||
if not entity and lat1 and lon1 then | |||
return lat1, lon1, heading1, nil, CoorCat.sdc4 | |||
elseif not entity then | |||
return lat1, lon1, heading1, nil, '' | |||
end | |||
local v, lat2, lon2, heading2, precision | |||
if entity then | |||
local P1259 = entity:getBestStatements( 'P1259' ) -- coordinates of the point of view | |||
if P1259[1] and P1259[1].mainsnak.datavalue.value.latitude then | |||
if P1259[1].qualifiers and P1259[1].qualifiers.P7787 then | |||
v = P1259[1].qualifiers.P7787[1].datavalue.value | |||
if v.unit == "http://www.wikidata.org/entity/Q28390" then -- in degrees | |||
heading2 = v.amount | |||
elseif v.unit == "http://www.wikidata.org/entity/Q33680" then -- in radians | |||
heading2 = v.amount*57.2957795131 | |||
end | |||
end | |||
v = P1259[1].mainsnak.datavalue.value -- get coordinates | |||
end | |||
if v and v.globe == 'http://www.wikidata.org/entity/Q2' then | |||
lat2 = v.latitude | |||
lon2 = v.longitude | |||
precision = v.precision or 1e-4 | |||
precision = math.floor(precision*111000) -- convert precision from degrees to meters and round | |||
precision = math.max(math.min(precision,111000),5) -- bound precision to a number between 5 meters and 1 degree | |||
end | |||
end | |||
-- compare coordinates | |||
local cat = '' | |||
if not lat1 or not lon1 then -- SDC coordinates only | |||
lat1 = lat2 | |||
lon1 = lon2 | |||
heading1 = heading2 | |||
cat = CoorCat.sdc0 | |||
elseif lat1 and lon1 and not lat2 and not lon2 then | |||
cat = CoorCat.sdc4 | |||
elseif lat1 and lon1 and lat2 and lon2 then | |||
local d = distance(lat1, lon1, lat2, lon2) -- calculate distance | |||
if d<20 or d<precision then -- will consider location within 20 meters or precision distance as the same | |||
cat = CoorCat.sdc1 | |||
else | |||
cat = CoorCat.sdc2 | |||
end | |||
end | |||
return lat1, lon1, heading1, entity.id, cat | |||
end | |||
local function dms2deg_ ( d, m, s, h ) | |||
d,m,s = tonumber(d), tonumber(m), tonumber(s) | |||
if not (d and m and s and h) then | |||
return nil | |||
end | end | ||
local | local LUT = {N=1, S=-1, E=1, W=-1} -- look up table | ||
if | h = LUT[mw.ustring.upper( h )] | ||
return | if not h then | ||
return nil | |||
end | end | ||
return | return h * (d + m/60.0 + s/3600.0) | ||
end | |||
local function dms2deg ( dms ) | |||
local ltab = mw.text.split(dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", ""), "/") | |||
local degre = dms2deg_ (ltab[1], ltab[2], ltab[3], ltab[4]) | |||
--return dms .. '->' .. dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", "") .. '->' .. (degre or 'nil') | |||
return degre or dms | |||
end | |||
-- ======================================= | |||
-- === External Functions ================ | |||
-- ======================================= | |||
local p = {} | |||
-- parse attribute variable returning desired field (used for debugging) | |||
function p.parseAttribute(frame) | |||
return string.match(mw.text.decode(frame.args[1]), mw.text.decode(frame.args[2]) .. ':' .. '([^_]*)') or '' | |||
end | end | ||
-- Helper core function for getHeading. | -- Helper core function for getHeading. | ||
function | function p._getHeading(attributes) | ||
if attributes == nil then | if attributes == nil then | ||
return nil | return nil | ||
135行目: | 434行目: | ||
end | end | ||
-- | --[[============================================================================ | ||
function | Parse attribute variable returning heading field. If heading is a string than | ||
local | try to convert it to an angle | ||
==============================================================================]] | |||
function p.getHeading(frame) | |||
else | local attributes | ||
if frame.args[1] then | |||
attributes = frame.args[1] | |||
elseif frame.args.attributes then | |||
attributes = frame.args.attributes | |||
else | |||
return '' | |||
end | end | ||
if | local hNum = p._getHeading(attributes) | ||
return | if hNum == nil then | ||
return '' | |||
end | end | ||
return tostring(hNum) | |||
end | end | ||
-- Helper core function for deg2dms. | |||
function | |||
local dNum, mNum, sNum, dStr, mStr, sStr | --[[============================================================================ | ||
Helper core function for deg2dms. deg2dms can be called by templates, while | |||
_deg2dms should be called from Lua. | |||
Inputs: | |||
* degree - positive coordinate in degrees | |||
* degPrec - coordinate precision in degrees will result in different angle format | |||
* lang - language to used when formatting the number | |||
==============================================================================]] | |||
function p._deg2dms(degree, degPrec, lang) | |||
local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k, d, zero | |||
local Lang = mw.language.new(lang) | local Lang = mw.language.new(lang) | ||
dNum = math.floor( | -- adjust number display based on precision | ||
mNum = math.floor( | secPrec = degPrec*3600.0 -- coordinate precision in seconds | ||
sNum = | if secPrec<0.05 then -- degPrec<1.3889e-05 | ||
dStr = Lang:formatNum(dNum) | formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.SS″ format | ||
mStr = Lang:formatNum(mNum) | c = 360000 | ||
sStr = Lang:formatNum(sNum) | elseif secPrec<0.5 then -- 1.3889e-05<degPrec<1.3889e-04 | ||
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.S″ format | |||
c = 36000 | |||
elseif degPrec*60.0<0.5 then -- 1.3889e-04<degPrec<0.0083 | |||
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS″ format | |||
c = 3600 | |||
elseif degPrec<0.5 then -- 0.0083<degPrec<0.5 | |||
formatStr = '%s° %s′' -- use DD° MM′ format | |||
c = 60 | |||
else -- if degPrec>0.5 then | |||
formatStr = '%s°' -- use DD° format | |||
c = 1 | |||
end | |||
-- create degree, minute and seconds numbers and string | |||
d = c/60 | |||
k = math.floor(c*(degree%360)+0.49) -- convert float to an integer. This step HAS to be identical for all conversions to avoid incorrect results due to different rounding | |||
dNum = math.floor(k/c) % 360 -- degree number (integer in 0-360 range) | |||
mNum = math.floor(k/d) % 60 -- minute number (integer in 0-60 range) | |||
sNum = 3600*(k%d) / c -- seconds number (float in 0-60 range with 0, 1 or 2 decimal digits) | |||
dStr = Lang:formatNum(dNum) -- degree string | |||
mStr = Lang:formatNum(mNum) -- minute string | |||
sStr = Lang:formatNum(sNum) -- second string | |||
zero = Lang:formatNum(0) -- zero string in local language | |||
if mNum<10 then | if mNum<10 then | ||
mStr = | mStr = zero .. mStr -- pad with zero if a single digit | ||
end | end | ||
if sNum<10 then | if sNum<10 then | ||
sStr = | sStr = zero .. sStr -- pad with zero if less than ten | ||
end | |||
return string.format(formatStr, dStr, mStr, sStr); | |||
end | |||
--[[============================================================================ | |||
Convert degrees to degrees/minutes/seconds notation commonly used when displaying | |||
coordinates. | |||
Inputs: | |||
1) latitude or longitude angle in degrees | |||
2) georeference precision in degrees | |||
3) language used in formatting of the number | |||
==============================================================================]] | |||
function p.deg2dms(frame) | |||
local args = getArgs(frame) | |||
local degree = tonumber(args[1]) | |||
local degPrec = tonumber(args[2]) or 0-- precision in degrees | |||
if degree==nil then | |||
return args[1]; | |||
else | |||
return p._deg2dms(degree, degPrec, args.lang) | |||
end | end | ||
end | end | ||
-- | function p.dms2deg(frame) | ||
function | return dms2deg(frame.args[1]) | ||
end | |||
if lon then -- get longitude | --[[============================================================================ | ||
Format coordinate location string, by creating and joining DMS strings for | |||
latitude and longitude. Also convert precision from meters to degrees. | |||
INPUTS: | |||
* lat = latitude in degrees | |||
* lon = longitude in degrees | |||
* lang = language code | |||
* prec = geolocation precision in meters | |||
==============================================================================]] | |||
function p._lat_lon(lat, lon, prec, lang) | |||
lat = tonumber(lat) | |||
lon = tonumber(lon) | |||
prec = math.abs(tonumber(prec) or 0) | |||
if lon then -- get longitude to be in -180 to 180 range | |||
lon=lon%360 | lon=lon%360 | ||
if lon>180 then | if lon>180 then | ||
lon = lon-360 | lon = lon-360 | ||
end | end | ||
end | end | ||
if lat==nil or lon==nil then | if lat==nil or lon==nil then | ||
return | return NoLatLonString | ||
else | else | ||
local nsew = | local nsew = langSwitch(i18n.NSEW, lang) -- find set of localized translation of N, S, W and E in the desired language | ||
local SN, EW, latStr, lonStr | local SN, EW, latStr, lonStr, lon2m, lat2m, phi | ||
if lat<0 then SN = nsew.S else SN = nsew.N end | if lat<0 then SN = nsew.S else SN = nsew.N end -- choose S or N depending on latitude degree sign | ||
if lon<0 then EW = nsew.W else EW = nsew.E end | if lon<0 then EW = nsew.W else EW = nsew.E end -- choose W or E depending on longitude degree sign | ||
latStr = | lat2m=1 | ||
lonStr = | lon2m=1 | ||
if prec>0 then -- if user specified the precision of the geo location... | |||
phi = math.abs(lat)*math.pi/180 -- latitude in radiants | |||
lon2m = 6378137*math.cos(phi)*math.pi/180 -- see https://en.wikipedia.org/wiki/Longitude | |||
lat2m = 111000 -- average latitude degree size in meters | |||
end | |||
latStr = p._deg2dms(math.abs(lat), prec/lat2m, lang) -- Convert latitude degrees to degrees/minutes/seconds | |||
lonStr = p._deg2dms(math.abs(lon), prec/lon2m, lang) -- Convert longitude degrees to degrees/minutes/seconds | |||
return string.format('%s %s, %s %s', latStr, SN, lonStr, EW) | return string.format('%s %s, %s %s', latStr, SN, lonStr, EW) | ||
--return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW) | --return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW) | ||
201行目: | 570行目: | ||
end | end | ||
function p.lat_lon(frame) | |||
function | local args = getArgs(frame) | ||
args = | return p._lat_lon(args.lat, args.lon, args.prec, args.lang) | ||
return | |||
end | end | ||
-- Helper core function for externalLink | |||
function | --[[============================================================================ | ||
local | Helper core function for externalLink. Create URL for different sites: | ||
INPUTS: | |||
* site = Possible sites: GeoHack, GoogleEarth, Proximityrama, | |||
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon) | |||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | |||
Ganymede are also supported but are unused as of 2013. | |||
* latStr = latitude string or number | |||
* lonStr = longitude string or number | |||
* lang = language code | |||
* attributes = attributes to be passed to GeoHack | |||
==============================================================================]] | |||
function p._externalLink(site, globe, latStr, lonStr, lang, attributes, level) | |||
local URLstr = SiteURL[site]; | |||
level = level or 1 | |||
local pageName = mw.uri.encode( mw.title.getCurrentTitle().prefixedText, 'WIKI' ) | |||
pageName = mw.ustring.gsub( pageName, '%%', '%%%%') | |||
if site == 'GoogleMaps' then | if site == 'GoogleMaps' then | ||
URLstr = SiteURL.GoogleMaps[globe] | |||
elseif site == 'GeoHack' then | |||
attributes = string.format('globe:%s_%s', globe, attributes) | attributes = string.format('globe:%s_%s', globe, attributes) | ||
URLstr = mw.ustring.gsub( URLstr, '$attr', attributes) | |||
end | end | ||
URLstr = mw.ustring.gsub( URLstr, '$lat' , latStr) | |||
URLstr = mw.ustring.gsub( URLstr, '$lon' , lonStr) | |||
URLstr = mw.ustring.gsub( URLstr, '$lang' , lang) | |||
return | URLstr = mw.ustring.gsub( URLstr, '$level', level) | ||
URLstr = mw.ustring.gsub( URLstr, '$page' , pageName) | |||
URLstr = mw.ustring.gsub( URLstr, '+', '') | |||
URLstr = mw.ustring.gsub( URLstr, ' ', '_') | |||
return URLstr | |||
end | |||
--[[============================================================================ | |||
Create URL for different sites. | |||
INPUTS: | |||
* site = Possible sites: GeoHack, GoogleEarth, Proximityrama, | |||
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon) | |||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | |||
Ganymede are also supported but are unused as of 2013. | |||
* lat = latitude string or number | |||
* lon = longitude string or number | |||
* lang = language code | |||
* attributes = attributes to be passed to GeoHack | |||
==============================================================================]] | |||
function p.externalLink(frame) | |||
local args = getArgs(frame) | |||
return p._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '') | |||
end | end | ||
-- | --[[============================================================================ | ||
function | Adjust GeoHack attributes depending on the template that calls it | ||
INPUTS: | |||
* attributes = attributes to be passed to GeoHack | |||
* mode = set by each calling template | |||
==============================================================================]] | |||
function p.alterAttributes(attributes, mode) | |||
-- indicate which template called it | -- indicate which template called it | ||
if mode=='camera' then -- Used by {{Location}} and {{Location dec}} | if mode=='camera' then -- Used by {{Location}} and {{Location dec}} | ||
234行目: | 638行目: | ||
attributes = 'type:camera_' .. attributes | attributes = 'type:camera_' .. attributes | ||
end | end | ||
elseif mode=='object'or mode =='globe' then | elseif mode=='object'or mode =='globe' then -- Used by {{Object location}} | ||
if mode=='object' and string.find(attributes, 'type:')==nil then | |||
attributes = 'type:object_' .. attributes | |||
end | |||
if string.find(attributes, 'class:object')==nil then | if string.find(attributes, 'class:object')==nil then | ||
attributes = 'class:object_' .. attributes | attributes = 'class:object_' .. attributes | ||
247行目: | 654行目: | ||
end | end | ||
-- Create link to GeoHack tool which displays latitude and longitude coordinates in DMS format | --[[============================================================================ | ||
function | Create link to GeoHack tool which displays latitude and longitude coordinates | ||
in DMS format | |||
INPUTS: | |||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | |||
Ganymede are also supported but are unused as of 2013. | |||
* lat = latitude in degrees | |||
* lon = longitude in degrees | |||
* lang = language code | |||
* prec = geolocation precision in meters | |||
* attributes = attributes to be passed to GeoHack | |||
==============================================================================]] | |||
function p._GeoHack_link(args) | |||
-- create link and coordintate string | -- create link and coordintate string | ||
local latlon = | local latlon = p._lat_lon(args.lat, args.lon, args.prec, args.lang) | ||
if latlon== | if latlon==NoLatLonString then | ||
return latlon | return latlon | ||
else | else | ||
local url = p._externalLink('GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '') | |||
return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion"> | return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion"> | ||
end | end | ||
end | end | ||
function | function p.GeoHack_link(frame) | ||
return p._GeoHack_link(getArgs(frame)) | |||
end | |||
--[[============================================================================ | |||
Create full external links section of {{Location}} or {{Object location}} | |||
if not args. | templates, based on: | ||
args.namespace = | * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013. | ||
* mode = Possible options: | |||
- camera - call from {{location}} | |||
- object - call from {{Object location}} | |||
- globe - call from {{Globe location}} | |||
* lat = latitude in degrees | |||
* lon = longitude in degrees | |||
* lang = language code | |||
* namespace = namespace name: File, Category, (Gallery) | |||
==============================================================================]] | |||
function p._externalLinksSection(args) | |||
local lang = args.lang | |||
if not args.namespace then | |||
args.namespace = mw.title.getCurrentTitle().nsText | |||
end | end | ||
local str | local str, link1, link2, link3, link4 | ||
if args.globe=='Earth' then -- Earth locations will have | if args.globe=='Earth' and args.namespace~="Category" then -- Earth locations for files will have 2 links | ||
link1 = p._externalLink('OpenStreetMap1', 'Earth', args.lat, args.lon, lang, '') | |||
link2 = p._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, '') | |||
str = string.format('[%s %s] - [%s %s]', | |||
link1, langSwitch(i18n.OpenStreetMaps, lang), | |||
link2, langSwitch(i18n.GoogleEarth, lang)) | |||
elseif args.globe=='Earth' and args.namespace=="Category" then -- Earth locations for categories will have 4 links | |||
link1 = p._externalLink('OpenStreetMap2', 'Earth', args.lat, args.lon, lang, '', args.catRecurse) | |||
--link2 = p._externalLink('GoogleMaps' , 'Earth', args.lat, args.lon, lang, '', args.catRecurse) | |||
link3 = p._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, '') | |||
link4 = p._externalLink('Proximityrama' , 'Earth', args.lat, args.lon, lang, '') | |||
str = string.format('[%s %s] - [%s %s] - [%s %s]', | str = string.format('[%s %s] - [%s %s] - [%s %s]', | ||
link1, langSwitch(i18n.OpenStreetMaps, lang), | |||
--link2, langSwitch(i18n.GoogleMaps, lang), | |||
link3, langSwitch(i18n.GoogleEarth, lang), | |||
link4, langSwitch(i18n.Proximityrama, lang)) | |||
elseif args.globe=='Mars' or args.globe=='Moon' then | elseif args.globe=='Mars' or args.globe=='Moon' then | ||
link1 = p._externalLink('GoogleMaps', args.globe, args.lat, args.lon, lang, '') | |||
str = string.format('[%s %s]', link1, langSwitch(i18n.GoogleMaps, lang)) | |||
end | end | ||
return str | return str | ||
end | end | ||
function p.externalLinksSection(frame) | |||
return p._externalLinksSection(getArgs(frame)) | |||
end | |||
--[[============================================================================ | |||
Core section of template:Location, template:Object location and template:Globe location. | Core section of template:Location, template:Object location and template:Globe location. | ||
This method requires several arguments to be passed to it or it's parent | This method requires several arguments to be passed to it or it's parent method/template: | ||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013. | * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013. | ||
* mode = Possible options: | * mode = Possible options: | ||
308行目: | 742行目: | ||
* attributes = attributes | * attributes = attributes | ||
* lang = language code | * lang = language code | ||
* namespace = namespace | * namespace = namespace: File, Category, Gallery | ||
]] | * prec = geolocation precision in meters | ||
function | ==============================================================================]] | ||
function p._LocationTemplateCore(args) | |||
-- prepare arguments | -- prepare arguments | ||
if not (args.namespace) then -- if namespace not provided than look it up | |||
args.namespace = mw.title.getCurrentTitle().nsText | |||
if not (args. | |||
args.namespace = | |||
end | end | ||
if args.namespace=='' then -- if empty than it is a gallery | if args.namespace=='' then -- if empty than it is a gallery | ||
330行目: | 758行目: | ||
Status = 'secondary' | Status = 'secondary' | ||
end | end | ||
args.globe = args.globe or 'Earth' | |||
local attributes0 = args.attributes | |||
args.attributes = p.alterAttributes(args.attributes or '', args.mode) | |||
-- | -- Convert coordinates from string to numbers | ||
local lat = tonumber(args.lat) | local lat = tonumber(args.lat) | ||
local lon = tonumber(args.lon) | local lon = tonumber(args.lon) | ||
if lon then -- get longitude | local heading = p._getHeading(attributes0) -- get heading arrow section | ||
if lon then -- get longitude to be in -180 to 180 range | |||
lon=lon%360 | lon=lon%360 | ||
if lon>180 then | if lon>180 then | ||
342行目: | 772行目: | ||
end | end | ||
end | end | ||
local Categories, geoMicroFormat, coorTag = '', '', '' | |||
-- If wikidata link provided than compare coordinates | |||
local Categories, geoMicroFormat, coorTag, wikidata_link = '', '', '', '' | |||
if (args.mode=='object') and (args.namespace~='File') then | |||
local dist_str, qID | |||
-- look up the coordinates on Wikidata | |||
lat, lon, qID, Categories, dist_str = mergeWithWikidata(args.wikidata, lat, lon) | |||
if qID then | |||
wikidata_link = string.format("\n[[File:Wikidata-logo.svg|20px|Edit coordinates on Wikidata%s|link=wikidata:%s]]", dist_str, qID); | |||
args.wikidata = args.wikidata or qID | |||
end | |||
elseif (args.mode=='camera') and (args.namespace=='File') then | |||
local dist_str, mID | |||
-- look up lat/lon on SDC | |||
lat, lon, heading, mID, Categories = mergeWithSDC(lat, lon, heading) | |||
if mID then | |||
wikidata_link = "\n[[File:Commons structured data logo.svg|16px|Edit coordinates on Structured Data on Commons|link=]]"; | |||
end | |||
end | |||
args.lat = string.format('%010.6f', lat or 0) | |||
args.lon = string.format('%011.6f', lon or 0) | |||
local frame = mw.getCurrentFrame() | |||
-- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages | -- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages | ||
348行目: | 800行目: | ||
if lat and lon then -- if lat and lon are numbers... | if lat and lon then -- if lat and lon are numbers... | ||
if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources | if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources | ||
Categories = CoorCat.default | Categories = Categories .. CoorCat.default | ||
end | |||
if attributes0 and string.find(attributes0, '=') then | |||
Categories = Categories .. CoorCat.attribute | |||
end | end | ||
if | if (math.abs(lon)>180) or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors ) | ||
Categories = Categories .. CoorCat.erroneous | Categories = Categories .. CoorCat.erroneous | ||
end | end | ||
363行目: | 818行目: | ||
-- add <span class="geo"> Geo (microformat) code: it is included for machine readability | -- add <span class="geo"> Geo (microformat) code: it is included for machine readability | ||
geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon) | geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon) | ||
-- https://www.mediawiki.org/wiki/Extension:GeoData | -- add {{#coordinates}} tag, see https://www.mediawiki.org/wiki/Extension:GeoData | ||
if args.namespace == 'File' and Status | if args.namespace == 'File' and Status == 'primary' and args.mode=='camera' then | ||
coorTag = frame:callParserFunction( '#coordinates', { 'primary', lat, lon, args.attributes } ) | |||
coorTag = | elseif args.namespace == 'File' and args.mode=='object' then | ||
coorTag = frame:callParserFunction( '#coordinates', { lat, lon, args.attributes } ) | |||
end | end | ||
else -- if lat and lon are not numbers then add error category | else -- if lat and lon are not numbers then add error category | ||
Categories = Categories .. CoorCat.erroneous | Categories = Categories .. CoorCat.erroneous | ||
end | end | ||
end | end | ||
-- Call helper functions to render different parts of the template | -- Call helper functions to render different parts of the template | ||
local | local coor, info_link, inner_table, OSM = '','','','','','' | ||
coor = p._GeoHack_link(args) -- the p and link to GeoHack | |||
heading = | if heading then | ||
local k = math.fmod(math.floor(0.5+math.fmod(heading+360,360)/11.25),32)+1 | |||
local fname = heading_icon[k] | |||
coor = string.format('%s <span title="%s°">[[%s|25px|link=|alt=Heading=%s°]]</span>', coor, heading, fname, heading) | |||
end | end | ||
if args.globe=='Earth' then | |||
local icon = 'marker' | |||
if args.mode=='camera' then | |||
inner_table = string.format('<td style="border:none;">%s</td><td style="border:none;"> | icon = 'camera' | ||
end | |||
OSM = frame:preprocess(add_maplink(args.lat, args.lon, icon, '[[File:Openstreetmap logo.svg|20px|link=|Kartographer map based on OpenStreetMap.]]')) -- fancy link to OSM | |||
end | |||
local external_link = p._externalLinksSection(args) -- external link section | |||
if external_link and args.namespace == 'File' then | |||
external_link = langSwitch(i18n.LocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{location}} template | |||
elseif external_link then | |||
external_link = langSwitch(i18n.ObjectLocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{Object location}} template | |||
end | |||
info_link = string.format('[[File:OOjs UI icon help.svg|18x18px|alt=info|link=%s]]', langSwitch(i18n.COM_GEO, args.lang) ) | |||
inner_table = string.format('<td style="border:none;">%s %s</td><td style="border:none;">%s</td><td style="border:none;">%s%s%s</td>', | |||
coor, OSM, external_link or '', wikidata_link, info_link, geoMicroFormat) | |||
-- combine strings into a table | -- combine strings into a table | ||
396行目: | 862行目: | ||
local field_name = 'Location' | local field_name = 'Location' | ||
if args.mode=='camera' then | if args.mode=='camera' then | ||
field_name = | field_name = langSwitch(i18n.CameraLocation, args.lang) | ||
elseif args.mode=='object' then | elseif args.mode=='object' then | ||
field_name = | field_name = langSwitch(i18n.ObjectLocation, args.lang) | ||
elseif args.mode=='globe' then | elseif args.mode=='globe' then | ||
field_list = | local field_list = langSwitch(i18n.GlobeLocation, args.lang) | ||
if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized | if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized | ||
field_name = field_list[args.globe] | field_name = field_list[args.globe] | ||
end | end | ||
end | end | ||
local style = | --Create HTML text | ||
templateText = string.format('<table %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', style, field_name, inner_table) | local dir, text_align | ||
if mw.language.new( args.lang ):isRTL() then | |||
dir = 'rtl' | |||
text_align = 'right' | |||
else | |||
dir = 'ltr' | |||
text_align = 'left' | |||
end | |||
local style = string.format('class="toccolours mw-content-%s layouttemplate commons-file-information-table" cellpadding="2" style="width: 100%%; direction:%s;" lang="%s"', | |||
args.lang, dir, text_align, args.lang) | |||
templateText = string.format('<table lang="%s" %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', args.lang, style, field_name, inner_table) | |||
end | |||
return templateText, Categories, coorTag | |||
end | |||
function p.LocationTemplateCore(frame) | |||
local args = getArgs(frame) | |||
args.namespace = mw.title.getCurrentTitle().nsText | |||
if not args.lat and not args.lon then -- if no lat and lon but numbered arguments present | |||
if args[4] then -- DMS with pipes format, ex. "34|5|32.36|N|116|9|24|55|W" | |||
args.lat = dms2deg_ ( args[1], args[2], args[3], args[4] ) | |||
args.lon = dms2deg_ ( args[5], args[6], args[7], args[8] ) | |||
args.attributes = args.attributes or args[9] | |||
elseif args[2] and not (type(args[2])=='string' and args[2]:find(":")) then -- decimal format or DMS with one pipe, ex. "34° 05′ 32.36″ N| 116° 09′ 24.55″ W" | |||
args.lat = args[1] | |||
args.lon = args[2] | |||
args.attributes = args.attributes or args[3] | |||
elseif args[1] then -- detect a single argument in the form "34° 05′ 32.36″ N, 116° 09′ 24.55″ W" or similar | |||
local v = mw.text.split(args[1]:gsub("([NnSs])", "%1/" ), "/") -- split into lat and lon using splitting point after any letter | |||
args.lat, args.lon = v[1], v[2] | |||
args.attributes = args.attributes or args[2] | |||
end | |||
end | |||
local cat = '' | |||
if args.lat and args.lon then | |||
local lat = tonumber(args.lat) | |||
local lon = tonumber(args.lon) | |||
if not lat or not lon then | |||
args.lat = dms2deg(args.lat or '') | |||
args.lon = dms2deg(args.lon or '') | |||
if (args.namespace == 'File' or args.namespace == 'Category') then | |||
cat = CoorCat.dms | |||
end | |||
end | |||
end | end | ||
return | local templateText, Categories, coorTag = p._LocationTemplateCore(args) | ||
return templateText .. Categories .. cat .. coorTag | |||
end | end | ||
return | return p |
2020年1月17日 (金) 13:29時点における版
このモジュールについての説明文ページを モジュール:Coordinates/doc に作成できます
--[[
__ __ _ _ ____ _ _ _
| \/ | ___ __| |_ _| | ___ _ / ___|___ ___ _ __ __| (_)_ __ __ _| |_ ___ ___
| |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \ / _ \| '__/ _` | | '_ \ / _` | __/ _ \/ __|
| | | | (_) | (_| | |_| | | __/_| |__| (_) | (_) | | | (_| | | | | | (_| | || __/\__ \
|_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/ \___/|_| \__,_|_|_| |_|\__,_|\__\___||___/
This module is intended to provide functionality of {{location}} and related
templates. It was developed on Wikimedia Commons, so if you find this code on
other sites, check there for updates and discussions.
Please do not modify this code without applying the changes first at Module:Coordinates/sandbox and testing
at Module:Coordinates/sandbox/testcases and Module talk:Coordinates/sandbox/testcases.
Authors and maintainers:
* User:Jarekt
* User:Ebraminio
Functions:
*function p.LocationTemplateCore(frame)
**function p.GeoHack_link(frame)
***function p.lat_lon(frame)
****function p._deg2dms(deg,lang)
***function p.externalLink(frame)
****function p._externalLink(site, globe, latStr, lonStr, lang, attributes)
**function p._getHeading(attributes)
**function p.externalLinksSection(frame)
***function p._externalLink(site, globe, latStr, lonStr, lang, attributes)
*function p.getHeading(frame)
*function p.deg2dms(frame)
]]
-- =======================================
-- === Dependencies ======================
-- =======================================
require('Module:No globals') -- used for debugging purposes as it detects cases of unintended global variables
local i18n = require('Module:I18n/coordinates') -- get localized translations of site names
local yesno = require('Module:Yesno')
-- =======================================
-- === Hardwired parameters ==============
-- =======================================
-- Angles associated with each abbreviation of compass point names. See [[:en:Points of the compass]]
local compass_points = {
N = 0,
NBE = 11.25,
NNE = 22.5,
NEBN = 33.75,
NE = 45,
NEBE = 56.25,
ENE = 67.5,
EBN = 78.75,
E = 90,
EBS = 101.25,
ESE = 112.5,
SEBE = 123.75,
SE = 135,
SEBS = 146.25,
SSE = 157.5,
SBE = 168.75,
S = 180,
SBW = 191.25,
SSW = 202.5,
SWBS = 213.75,
SW = 225,
SWBW = 236.25,
WSW = 247.5,
WBS = 258.75,
W = 270,
WBN = 281.25,
WNW = 292.5,
NWBW = 303.75,
NW = 315,
NWBN = 326.25,
NNW = 337.5,
NBW = 348.75,
}
-- files to use for different headings
local heading_icon = {
[ 1] = 'File:Compass-icon bb N.svg',
[ 2] = 'File:Compass-icon bb NbE.svg',
[ 3] = 'File:Compass-icon bb NNE.svg',
[ 4] = 'File:Compass-icon bb NEbN.svg',
[ 5] = 'File:Compass-icon bb NE.svg',
[ 6] = 'File:Compass-icon bb NEbE.svg',
[ 7] = 'File:Compass-icon bb ENE.svg',
[ 8] = 'File:Compass-icon bb EbN.svg',
[ 9] = 'File:Compass-icon bb E.svg',
[10] = 'File:Compass-icon bb EbS.svg',
[11] = 'File:Compass-icon bb ESE.svg',
[12] = 'File:Compass-icon bb SEbE.svg',
[13] = 'File:Compass-icon bb SE.svg',
[14] = 'File:Compass-icon bb SEbS.svg',
[15] = 'File:Compass-icon bb SSE.svg',
[16] = 'File:Compass-icon bb SbE.svg',
[17] = 'File:Compass-icon bb S.svg',
[18] = 'File:Compass-icon bb SbW.svg',
[19] = 'File:Compass-icon bb SSW.svg',
[20] = 'File:Compass-icon bb SWbS.svg',
[21] = 'File:Compass-icon bb SW.svg',
[22] = 'File:Compass-icon bb SWbW.svg',
[23] = 'File:Compass-icon bb WSW.svg',
[24] = 'File:Compass-icon bb WbS.svg',
[25] = 'File:Compass-icon bb W.svg',
[26] = 'File:Compass-icon bb WbN.svg',
[27] = 'File:Compass-icon bb WNW.svg',
[28] = 'File:Compass-icon bb NWbW.svg',
[29] = 'File:Compass-icon bb NW.svg',
[30] = 'File:Compass-icon bb NWbN.svg',
[31] = 'File:Compass-icon bb NNW.svg',
[32] = 'File:Compass-icon bb NbW.svg'
}
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr, $page will be
-- replaced with latitude, longitude, language code, GeoHack attribution parameters and full-page-name strings.
local SiteURL = {
GeoHack = '//tools.wmflabs.org/geohack/geohack.php?pagename=$page¶ms=$lat_N_$lon_E_$attr&language=$lang',
GoogleEarth = '//tools.wmflabs.org/geocommons/earth.kml?latdegdec=$lat&londegdec=$lon&scale=10000&commons=1',
Proximityrama = '//tools.wmflabs.org/geocommons/proximityrama?latlon=$lat,$lon',
WikimediaMap = '//maps.wikimedia.org/#16/$lat/$lon',
OpenStreetMap1 = '//tools.wmflabs.org/wiwosm/osm-on-ol/commons-on-osm.php?zoom=16&lat=$lat&lon=$lon',
OpenStreetMap2 = '//tools.wmflabs.org/osm4wiki/cgi-bin/wiki/wiki-osm.pl?project=Commons&article=$page&l=$level',
GoogleMaps = {
Mars = '//www.google.com/mars/#lat=$lat&lon=$lon&zoom=8',
Moon = '//www.google.com/moon/#lat=$lat&lon=$lon&zoom=8',
Earth = '//tools.wmflabs.org/wp-world/googlmaps-proxy.php?page=http://tools.wmflabs.org/kmlexport/%3Fproject%3DCommons%26article%3D$page&l=$level&output=classic'
}
}
-- Categories
local CoorCat = {
File = '[[Category:Media with locations]]',
Gallery = '[[Category:Galleries with coordinates]]',
Category = '[[Category:Categories with coordinates]]',
wikidata0 = '[[Category:Pages with coordinates from Wikidata]]',
wikidata1 = '[[Category:Pages with local coordinates and matching Wikidata coordinates]]',
wikidata2 = '[[Category:Pages with local coordinates and similar Wikidata coordinates]]',
wikidata3 = '[[Category:Pages with local coordinates and mismatching Wikidata coordinates]]',
wikidata4 = '[[Category:Pages with local coordinates and missing Wikidata coordinates]]',
wikidata5 = '[[Category:Pages with locations and Wikidata ID to wrong type of entry]]',
sdc0 = '[[Category:Pages with coordinates from SDC]]',
sdc1 = '[[Category:Pages with local coordinates and matching SDC coordinates]]',
sdc2 = '[[Category:Pages with local coordinates and similar SDC coordinates]]',
sdc3 = '[[Category:Pages with local coordinates and mismatching SDC coordinates]]',
sdc4 = '[[Category:Pages with local coordinates and missing SDC coordinates]]',
globe = '[[Category:Media with %s locations]]',
default = '[[Category:Media with default locations]]',
attribute = '[[Category:Media with erroneous geolocation attributes]]',
erroneous = '[[Category:Media with erroneous locations]]<span style="color:red;font-weight:bold">Error: Invalid parameters!</span>\n',
dms = '[[Category:Media with coordinates in DMS format]]'
}
-- =======================================
-- === Local Functions ===================
-- =======================================
local function normalize_input_args(input_args, output_args)
for name, value in pairs( input_args ) do
if value ~= '' then -- nuke empty strings
if type(name)=='string' then
name = string.lower(name)
end
output_args[name] = string.gsub(value, "^%s*(.-)%s*$", "%1") -- trim whitespaces from the beggining and the end of the string
end
end
return output_args
end
local function getArgs(frame)
local args = {}
args = normalize_input_args(frame:getParent().args, args)
args = normalize_input_args(frame.args, args)
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then
args.lang = frame:callParserFunction("int","lang") -- get user's chosen language
end
return args
end
local NoLatLonString = 'latitude, longitude'
local function langSwitch(list,lang)
local langList = mw.language.getFallbacksFor(lang)
table.insert(langList,1,lang)
for i,language in ipairs(langList) do
if list[language] then
return list[language]
end
end
end
local function add_maplink(lat, lon, marker, text)
local tstr = ''
if text then
tstr = string.format('text="%s" ', text)
end
return string.format('<maplink %szoom="13" latitude="%f" longitude="%f" class="no-icon">{'..
' "type": "Feature",'..
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'..
' "properties": { "marker-symbol":"%s", "marker-size": "large", "marker-color": "0050d0" }'..
'}</maplink>', tstr, lat, lon, lon, lat, marker)
end
local function add_maplink2(lat1, lon1, lat2, lon2)
return string.format('<maplink zoom="13" latitude="%f" longitude="%f" class="no-icon">[{'..
' "type": "Feature",'..
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'..
' "properties": { "marker-symbol":"c", "marker-size": "large", "marker-color": "0050d0", "title": "Location on Wikimedia Commons" }'..
'},{'..
' "type": "Feature",'..
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'..
' "properties": { "marker-symbol":"w", "marker-size": "large", "marker-color": "228b22", "title": "Location on Wikidata" }'..
'}]</maplink>', lat2, lon2, lon1, lat1, lon2, lat2)
end
local function info_box(text)
return string.format('<table class="messagebox plainlinks layouttemplate" style="border-collapse:collapse; border-width:2px; border-style:solid; width:100%%; clear: both; '..
'border-color:#f28500; background:#ffe;direction:ltr; border-left-width: 8px; ">'..
'<tr>'..
'<td class="mbox-image" style="padding-left:.9em;">'..
' [[File:Commons-emblem-issue.svg|class=noviewer|45px]]</td>'..
'<td class="mbox-text" style="">%s</td>'..
'</tr></table>', text)
end
local function distance(lat1, lon1, lat2, lon2)
-- calculate distance
local dLat = math.rad(lat1-lat2)
local dLon = math.rad(lon1-lon2)
local d = math.pow(math.sin(dLat/2),2) + math.pow(math.sin(dLon/2),2) * math.cos(math.rad(lat1)) * math.cos(math.rad(lat2))
d = 2 * math.atan2(math.sqrt(d), math.sqrt(1-d)) -- angular distance in radians
d = 6371000 * d -- radians to meters conversion
d = math.floor(d+0.5) -- round it to even meters
return d
end
local function mergeWithWikidata(qID, lat1, lon1)
-- we are given wikidata q-code so look up the coordinates
local dist_str=''
local entity
-- Wikiata coordinates
if qID==nil then
entity = mw.wikibase.getEntity()
elseif type(qID)=='string' and qID:match( '^[Qq]%d+$' ) then
entity = mw.wikibase.getEntity(qID)
else
entity = qID
end
if not entity then
return lat1, lon1, nil, '', dist_str
end
qID = entity.id
local v, lat2, lon2, precision
if entity then
local P625 = entity:getBestStatements( 'P625' ) -- coordinate location
local P159 = entity:getBestStatements( 'P159' ) -- headquarters location
if P625[1] and P625[1].mainsnak.datavalue.value.latitude then
v = P625[1].mainsnak.datavalue.value
elseif P159[1] and P159[1].qualifiers and P159[1].qualifiers.P625 then
v = P159[1].qualifiers.P625[1].datavalue.value
end
if v and v.globe == 'http://www.wikidata.org/entity/Q2' then
lat2 = v.latitude
lon2 = v.longitude
precision = v.precision or 1e-4
precision = math.floor(precision*111000) -- convert precision from degrees to meters and round
precision = math.max(math.min(precision,111000),5) -- bound precision to a number between 5 meters and 1 degree
end
end
-- compare coordinates
local cat = ''
if not lat1 or not lon1 then -- wikidata coordinates only
lat1 = lat2
lon1 = lon2
cat = CoorCat.wikidata0
elseif lat1 and lon1 and not lat2 and not lon2 then
cat = string.format('The above coordinates are missing from linked Wikidata item [[d:%s|%s]]. Click <span class=\"plainlinks\" title=\"Click to copy to wikidata\">'..
"[https://tools.wmflabs.org/quickstatements/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span> to copy it",
qID, qID, qID, lat1, lon1)
cat = CoorCat.wikidata4 .. info_box(cat)
elseif lat1 and lon1 and lat2 and lon2 then
local d = distance(lat1, lon1, lat2, lon2) -- calculate distance
local frame = mw.getCurrentFrame()
local info = frame:preprocess(add_maplink2(lat1, lon1, lat2, lon2)) -- fancy link to OSM
info = string.format("There is a discrepancy of %i meters between the above coordinates and the ones stored at linked Wikidata item [[d:%s|%s]] (%s, precision: %i m). "..
'Please reconcile them. To copy Commons coordinates to Wikidata, click <span class=\"plainlinks\" title=\"Click to copy to wikidata\">'..
"[https://tools.wmflabs.org/quickstatements/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span>",
d, qID, qID, info, precision, qID, lat1, lon1)
if d<20 or d<precision then -- will consider location within 20 meters or precisi0on distance as the same
cat = CoorCat.wikidata1
dist_str = string.format(' (discrepancy of %i meters between the above coordinates and the ones stored on Wikidata)', d) -- will be displayed when hovering a mouse above wikidata icon
elseif d>1000 and d>5*precision then -- locations 1 km off and 5 precision distances away are likely wrong
cat = CoorCat.wikidata3 .. info_box(info)
else
cat = CoorCat.wikidata2 .. info_box(info)
end
end
-- verify proper P31 (instance of). List is based on https://www.wikidata.org/wiki/Property_talk:P625
local QCodes = {
Q5 = 1, -- human
Q11879590 = 1, -- female given name
Q202444 = 1, -- given name
Q12308941 = 1, -- male given name
Q4167836 = 1, -- Wikimedia category
Q4167410 = 1, -- Wikimedia disambiguation page
Q783794 = 2, -- company
Q4830453 = 2, -- business enterprise
}
local s = entity:getBestStatements( 'P31' )
if s[1] and s[1].mainsnak.datavalue.value['id'] then
local instanceOf = s[1].mainsnak.datavalue.value['id']
if QCodes[instanceOf] then
cat = '' -- wipe out categories
if QCodes[instanceOf]==1 then -- add problem category
cat = CoorCat.wikidata5
end
end
end
return lat1, lon1, qID, cat, dist_str
end
local function mergeWithSDC(lat1, lon1, heading1)
-- we are given SDC m-code so look up the coordinates
local entity = nil
-- Wikiata coordinates
entity = mw.wikibase.getEntity()
if not entity and lat1 and lon1 then
return lat1, lon1, heading1, nil, CoorCat.sdc4
elseif not entity then
return lat1, lon1, heading1, nil, ''
end
local v, lat2, lon2, heading2, precision
if entity then
local P1259 = entity:getBestStatements( 'P1259' ) -- coordinates of the point of view
if P1259[1] and P1259[1].mainsnak.datavalue.value.latitude then
if P1259[1].qualifiers and P1259[1].qualifiers.P7787 then
v = P1259[1].qualifiers.P7787[1].datavalue.value
if v.unit == "http://www.wikidata.org/entity/Q28390" then -- in degrees
heading2 = v.amount
elseif v.unit == "http://www.wikidata.org/entity/Q33680" then -- in radians
heading2 = v.amount*57.2957795131
end
end
v = P1259[1].mainsnak.datavalue.value -- get coordinates
end
if v and v.globe == 'http://www.wikidata.org/entity/Q2' then
lat2 = v.latitude
lon2 = v.longitude
precision = v.precision or 1e-4
precision = math.floor(precision*111000) -- convert precision from degrees to meters and round
precision = math.max(math.min(precision,111000),5) -- bound precision to a number between 5 meters and 1 degree
end
end
-- compare coordinates
local cat = ''
if not lat1 or not lon1 then -- SDC coordinates only
lat1 = lat2
lon1 = lon2
heading1 = heading2
cat = CoorCat.sdc0
elseif lat1 and lon1 and not lat2 and not lon2 then
cat = CoorCat.sdc4
elseif lat1 and lon1 and lat2 and lon2 then
local d = distance(lat1, lon1, lat2, lon2) -- calculate distance
if d<20 or d<precision then -- will consider location within 20 meters or precision distance as the same
cat = CoorCat.sdc1
else
cat = CoorCat.sdc2
end
end
return lat1, lon1, heading1, entity.id, cat
end
local function dms2deg_ ( d, m, s, h )
d,m,s = tonumber(d), tonumber(m), tonumber(s)
if not (d and m and s and h) then
return nil
end
local LUT = {N=1, S=-1, E=1, W=-1} -- look up table
h = LUT[mw.ustring.upper( h )]
if not h then
return nil
end
return h * (d + m/60.0 + s/3600.0)
end
local function dms2deg ( dms )
local ltab = mw.text.split(dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", ""), "/")
local degre = dms2deg_ (ltab[1], ltab[2], ltab[3], ltab[4])
--return dms .. '->' .. dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", "") .. '->' .. (degre or 'nil')
return degre or dms
end
-- =======================================
-- === External Functions ================
-- =======================================
local p = {}
-- parse attribute variable returning desired field (used for debugging)
function p.parseAttribute(frame)
return string.match(mw.text.decode(frame.args[1]), mw.text.decode(frame.args[2]) .. ':' .. '([^_]*)') or ''
end
-- Helper core function for getHeading.
function p._getHeading(attributes)
if attributes == nil then
return nil
end
local hStr = string.match(mw.text.decode(attributes), 'heading:([^_]*)')
if hStr == nil then
return nil
end
local hNum = tonumber( hStr )
if hNum == nil then
hStr = string.upper (hStr)
hNum = compass_points[hStr]
end
if hNum ~= nil then
hNum = hNum%360
end
return hNum
end
--[[============================================================================
Parse attribute variable returning heading field. If heading is a string than
try to convert it to an angle
==============================================================================]]
function p.getHeading(frame)
local attributes
if frame.args[1] then
attributes = frame.args[1]
elseif frame.args.attributes then
attributes = frame.args.attributes
else
return ''
end
local hNum = p._getHeading(attributes)
if hNum == nil then
return ''
end
return tostring(hNum)
end
--[[============================================================================
Helper core function for deg2dms. deg2dms can be called by templates, while
_deg2dms should be called from Lua.
Inputs:
* degree - positive coordinate in degrees
* degPrec - coordinate precision in degrees will result in different angle format
* lang - language to used when formatting the number
==============================================================================]]
function p._deg2dms(degree, degPrec, lang)
local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k, d, zero
local Lang = mw.language.new(lang)
-- adjust number display based on precision
secPrec = degPrec*3600.0 -- coordinate precision in seconds
if secPrec<0.05 then -- degPrec<1.3889e-05
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.SS″ format
c = 360000
elseif secPrec<0.5 then -- 1.3889e-05<degPrec<1.3889e-04
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.S″ format
c = 36000
elseif degPrec*60.0<0.5 then -- 1.3889e-04<degPrec<0.0083
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS″ format
c = 3600
elseif degPrec<0.5 then -- 0.0083<degPrec<0.5
formatStr = '%s° %s′' -- use DD° MM′ format
c = 60
else -- if degPrec>0.5 then
formatStr = '%s°' -- use DD° format
c = 1
end
-- create degree, minute and seconds numbers and string
d = c/60
k = math.floor(c*(degree%360)+0.49) -- convert float to an integer. This step HAS to be identical for all conversions to avoid incorrect results due to different rounding
dNum = math.floor(k/c) % 360 -- degree number (integer in 0-360 range)
mNum = math.floor(k/d) % 60 -- minute number (integer in 0-60 range)
sNum = 3600*(k%d) / c -- seconds number (float in 0-60 range with 0, 1 or 2 decimal digits)
dStr = Lang:formatNum(dNum) -- degree string
mStr = Lang:formatNum(mNum) -- minute string
sStr = Lang:formatNum(sNum) -- second string
zero = Lang:formatNum(0) -- zero string in local language
if mNum<10 then
mStr = zero .. mStr -- pad with zero if a single digit
end
if sNum<10 then
sStr = zero .. sStr -- pad with zero if less than ten
end
return string.format(formatStr, dStr, mStr, sStr);
end
--[[============================================================================
Convert degrees to degrees/minutes/seconds notation commonly used when displaying
coordinates.
Inputs:
1) latitude or longitude angle in degrees
2) georeference precision in degrees
3) language used in formatting of the number
==============================================================================]]
function p.deg2dms(frame)
local args = getArgs(frame)
local degree = tonumber(args[1])
local degPrec = tonumber(args[2]) or 0-- precision in degrees
if degree==nil then
return args[1];
else
return p._deg2dms(degree, degPrec, args.lang)
end
end
function p.dms2deg(frame)
return dms2deg(frame.args[1])
end
--[[============================================================================
Format coordinate location string, by creating and joining DMS strings for
latitude and longitude. Also convert precision from meters to degrees.
INPUTS:
* lat = latitude in degrees
* lon = longitude in degrees
* lang = language code
* prec = geolocation precision in meters
==============================================================================]]
function p._lat_lon(lat, lon, prec, lang)
lat = tonumber(lat)
lon = tonumber(lon)
prec = math.abs(tonumber(prec) or 0)
if lon then -- get longitude to be in -180 to 180 range
lon=lon%360
if lon>180 then
lon = lon-360
end
end
if lat==nil or lon==nil then
return NoLatLonString
else
local nsew = langSwitch(i18n.NSEW, lang) -- find set of localized translation of N, S, W and E in the desired language
local SN, EW, latStr, lonStr, lon2m, lat2m, phi
if lat<0 then SN = nsew.S else SN = nsew.N end -- choose S or N depending on latitude degree sign
if lon<0 then EW = nsew.W else EW = nsew.E end -- choose W or E depending on longitude degree sign
lat2m=1
lon2m=1
if prec>0 then -- if user specified the precision of the geo location...
phi = math.abs(lat)*math.pi/180 -- latitude in radiants
lon2m = 6378137*math.cos(phi)*math.pi/180 -- see https://en.wikipedia.org/wiki/Longitude
lat2m = 111000 -- average latitude degree size in meters
end
latStr = p._deg2dms(math.abs(lat), prec/lat2m, lang) -- Convert latitude degrees to degrees/minutes/seconds
lonStr = p._deg2dms(math.abs(lon), prec/lon2m, lang) -- Convert longitude degrees to degrees/minutes/seconds
return string.format('%s %s, %s %s', latStr, SN, lonStr, EW)
--return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW)
end
end
function p.lat_lon(frame)
local args = getArgs(frame)
return p._lat_lon(args.lat, args.lon, args.prec, args.lang)
end
--[[============================================================================
Helper core function for externalLink. Create URL for different sites:
INPUTS:
* site = Possible sites: GeoHack, GoogleEarth, Proximityrama,
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
Ganymede are also supported but are unused as of 2013.
* latStr = latitude string or number
* lonStr = longitude string or number
* lang = language code
* attributes = attributes to be passed to GeoHack
==============================================================================]]
function p._externalLink(site, globe, latStr, lonStr, lang, attributes, level)
local URLstr = SiteURL[site];
level = level or 1
local pageName = mw.uri.encode( mw.title.getCurrentTitle().prefixedText, 'WIKI' )
pageName = mw.ustring.gsub( pageName, '%%', '%%%%')
if site == 'GoogleMaps' then
URLstr = SiteURL.GoogleMaps[globe]
elseif site == 'GeoHack' then
attributes = string.format('globe:%s_%s', globe, attributes)
URLstr = mw.ustring.gsub( URLstr, '$attr', attributes)
end
URLstr = mw.ustring.gsub( URLstr, '$lat' , latStr)
URLstr = mw.ustring.gsub( URLstr, '$lon' , lonStr)
URLstr = mw.ustring.gsub( URLstr, '$lang' , lang)
URLstr = mw.ustring.gsub( URLstr, '$level', level)
URLstr = mw.ustring.gsub( URLstr, '$page' , pageName)
URLstr = mw.ustring.gsub( URLstr, '+', '')
URLstr = mw.ustring.gsub( URLstr, ' ', '_')
return URLstr
end
--[[============================================================================
Create URL for different sites.
INPUTS:
* site = Possible sites: GeoHack, GoogleEarth, Proximityrama,
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
Ganymede are also supported but are unused as of 2013.
* lat = latitude string or number
* lon = longitude string or number
* lang = language code
* attributes = attributes to be passed to GeoHack
==============================================================================]]
function p.externalLink(frame)
local args = getArgs(frame)
return p._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '')
end
--[[============================================================================
Adjust GeoHack attributes depending on the template that calls it
INPUTS:
* attributes = attributes to be passed to GeoHack
* mode = set by each calling template
==============================================================================]]
function p.alterAttributes(attributes, mode)
-- indicate which template called it
if mode=='camera' then -- Used by {{Location}} and {{Location dec}}
if string.find(attributes, 'type:camera')==nil then
attributes = 'type:camera_' .. attributes
end
elseif mode=='object'or mode =='globe' then -- Used by {{Object location}}
if mode=='object' and string.find(attributes, 'type:')==nil then
attributes = 'type:object_' .. attributes
end
if string.find(attributes, 'class:object')==nil then
attributes = 'class:object_' .. attributes
end
elseif mode=='inline' then -- Used by {{Inline coordinates}} (actually that template does not set any attributes at the moment)
elseif mode=='user' then -- Used by {{User location}}
attributes = 'type:user_location'
elseif mode=='institution' then --Used by {{Institution/coordinates}} (categories only)
attributes = 'type:institution'
end
return attributes
end
--[[============================================================================
Create link to GeoHack tool which displays latitude and longitude coordinates
in DMS format
INPUTS:
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
Ganymede are also supported but are unused as of 2013.
* lat = latitude in degrees
* lon = longitude in degrees
* lang = language code
* prec = geolocation precision in meters
* attributes = attributes to be passed to GeoHack
==============================================================================]]
function p._GeoHack_link(args)
-- create link and coordintate string
local latlon = p._lat_lon(args.lat, args.lon, args.prec, args.lang)
if latlon==NoLatLonString then
return latlon
else
local url = p._externalLink('GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '')
return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion">
end
end
function p.GeoHack_link(frame)
return p._GeoHack_link(getArgs(frame))
end
--[[============================================================================
Create full external links section of {{Location}} or {{Object location}}
templates, based on:
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
* mode = Possible options:
- camera - call from {{location}}
- object - call from {{Object location}}
- globe - call from {{Globe location}}
* lat = latitude in degrees
* lon = longitude in degrees
* lang = language code
* namespace = namespace name: File, Category, (Gallery)
==============================================================================]]
function p._externalLinksSection(args)
local lang = args.lang
if not args.namespace then
args.namespace = mw.title.getCurrentTitle().nsText
end
local str, link1, link2, link3, link4
if args.globe=='Earth' and args.namespace~="Category" then -- Earth locations for files will have 2 links
link1 = p._externalLink('OpenStreetMap1', 'Earth', args.lat, args.lon, lang, '')
link2 = p._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, '')
str = string.format('[%s %s] - [%s %s]',
link1, langSwitch(i18n.OpenStreetMaps, lang),
link2, langSwitch(i18n.GoogleEarth, lang))
elseif args.globe=='Earth' and args.namespace=="Category" then -- Earth locations for categories will have 4 links
link1 = p._externalLink('OpenStreetMap2', 'Earth', args.lat, args.lon, lang, '', args.catRecurse)
--link2 = p._externalLink('GoogleMaps' , 'Earth', args.lat, args.lon, lang, '', args.catRecurse)
link3 = p._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, '')
link4 = p._externalLink('Proximityrama' , 'Earth', args.lat, args.lon, lang, '')
str = string.format('[%s %s] - [%s %s] - [%s %s]',
link1, langSwitch(i18n.OpenStreetMaps, lang),
--link2, langSwitch(i18n.GoogleMaps, lang),
link3, langSwitch(i18n.GoogleEarth, lang),
link4, langSwitch(i18n.Proximityrama, lang))
elseif args.globe=='Mars' or args.globe=='Moon' then
link1 = p._externalLink('GoogleMaps', args.globe, args.lat, args.lon, lang, '')
str = string.format('[%s %s]', link1, langSwitch(i18n.GoogleMaps, lang))
end
return str
end
function p.externalLinksSection(frame)
return p._externalLinksSection(getArgs(frame))
end
--[[============================================================================
Core section of template:Location, template:Object location and template:Globe location.
This method requires several arguments to be passed to it or it's parent method/template:
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
* mode = Possible options:
- camera - call from {{location}}
- object - call from {{Object location}}
- globe - call from {{Globe location}}
* lat = latitude in degrees
* lon = longitude in degrees
* attributes = attributes
* lang = language code
* namespace = namespace: File, Category, Gallery
* prec = geolocation precision in meters
==============================================================================]]
function p._LocationTemplateCore(args)
-- prepare arguments
if not (args.namespace) then -- if namespace not provided than look it up
args.namespace = mw.title.getCurrentTitle().nsText
end
if args.namespace=='' then -- if empty than it is a gallery
args.namespace = 'Gallery'
end
local bare = yesno(args.bare,false)
local Status = 'primary' -- used by {{#coordinates:}}
if yesno(args.secondary,false) then
Status = 'secondary'
end
args.globe = args.globe or 'Earth'
local attributes0 = args.attributes
args.attributes = p.alterAttributes(args.attributes or '', args.mode)
-- Convert coordinates from string to numbers
local lat = tonumber(args.lat)
local lon = tonumber(args.lon)
local heading = p._getHeading(attributes0) -- get heading arrow section
if lon then -- get longitude to be in -180 to 180 range
lon=lon%360
if lon>180 then
lon = lon-360
end
end
-- If wikidata link provided than compare coordinates
local Categories, geoMicroFormat, coorTag, wikidata_link = '', '', '', ''
if (args.mode=='object') and (args.namespace~='File') then
local dist_str, qID
-- look up the coordinates on Wikidata
lat, lon, qID, Categories, dist_str = mergeWithWikidata(args.wikidata, lat, lon)
if qID then
wikidata_link = string.format("\n[[File:Wikidata-logo.svg|20px|Edit coordinates on Wikidata%s|link=wikidata:%s]]", dist_str, qID);
args.wikidata = args.wikidata or qID
end
elseif (args.mode=='camera') and (args.namespace=='File') then
local dist_str, mID
-- look up lat/lon on SDC
lat, lon, heading, mID, Categories = mergeWithSDC(lat, lon, heading)
if mID then
wikidata_link = "\n[[File:Commons structured data logo.svg|16px|Edit coordinates on Structured Data on Commons|link=]]";
end
end
args.lat = string.format('%010.6f', lat or 0)
args.lon = string.format('%011.6f', lon or 0)
local frame = mw.getCurrentFrame()
-- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages
if (args.namespace == 'File' or args.namespace == 'Category' or args.namespace == 'Gallery') then
if lat and lon then -- if lat and lon are numbers...
if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources
Categories = Categories .. CoorCat.default
end
if attributes0 and string.find(attributes0, '=') then
Categories = Categories .. CoorCat.attribute
end
if (math.abs(lon)>180) or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors )
Categories = Categories .. CoorCat.erroneous
end
local cat = CoorCat[args.namespace]
if cat then -- add category based on namespace
Categories = Categories .. cat
end
-- if not earth than add a category for each globe
if args.mode and args.globe and args.mode=='globe' and args.globe~='Earth' then
Categories = Categories .. string.format(CoorCat[args.mode], args.globe)
end
-- add <span class="geo"> Geo (microformat) code: it is included for machine readability
geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon)
-- add {{#coordinates}} tag, see https://www.mediawiki.org/wiki/Extension:GeoData
if args.namespace == 'File' and Status == 'primary' and args.mode=='camera' then
coorTag = frame:callParserFunction( '#coordinates', { 'primary', lat, lon, args.attributes } )
elseif args.namespace == 'File' and args.mode=='object' then
coorTag = frame:callParserFunction( '#coordinates', { lat, lon, args.attributes } )
end
else -- if lat and lon are not numbers then add error category
Categories = Categories .. CoorCat.erroneous
end
end
-- Call helper functions to render different parts of the template
local coor, info_link, inner_table, OSM = '','','','','',''
coor = p._GeoHack_link(args) -- the p and link to GeoHack
if heading then
local k = math.fmod(math.floor(0.5+math.fmod(heading+360,360)/11.25),32)+1
local fname = heading_icon[k]
coor = string.format('%s <span title="%s°">[[%s|25px|link=|alt=Heading=%s°]]</span>', coor, heading, fname, heading)
end
if args.globe=='Earth' then
local icon = 'marker'
if args.mode=='camera' then
icon = 'camera'
end
OSM = frame:preprocess(add_maplink(args.lat, args.lon, icon, '[[File:Openstreetmap logo.svg|20px|link=|Kartographer map based on OpenStreetMap.]]')) -- fancy link to OSM
end
local external_link = p._externalLinksSection(args) -- external link section
if external_link and args.namespace == 'File' then
external_link = langSwitch(i18n.LocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{location}} template
elseif external_link then
external_link = langSwitch(i18n.ObjectLocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{Object location}} template
end
info_link = string.format('[[File:OOjs UI icon help.svg|18x18px|alt=info|link=%s]]', langSwitch(i18n.COM_GEO, args.lang) )
inner_table = string.format('<td style="border:none;">%s %s</td><td style="border:none;">%s</td><td style="border:none;">%s%s%s</td>',
coor, OSM, external_link or '', wikidata_link, info_link, geoMicroFormat)
-- combine strings into a table
local templateText
if bare then
templateText = string.format('<table style="width:100%%"><tr>%s</tr></table>', inner_table)
else
-- choose name of the field
local field_name = 'Location'
if args.mode=='camera' then
field_name = langSwitch(i18n.CameraLocation, args.lang)
elseif args.mode=='object' then
field_name = langSwitch(i18n.ObjectLocation, args.lang)
elseif args.mode=='globe' then
local field_list = langSwitch(i18n.GlobeLocation, args.lang)
if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized
field_name = field_list[args.globe]
end
end
--Create HTML text
local dir, text_align
if mw.language.new( args.lang ):isRTL() then
dir = 'rtl'
text_align = 'right'
else
dir = 'ltr'
text_align = 'left'
end
local style = string.format('class="toccolours mw-content-%s layouttemplate commons-file-information-table" cellpadding="2" style="width: 100%%; direction:%s;" lang="%s"',
args.lang, dir, text_align, args.lang)
templateText = string.format('<table lang="%s" %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', args.lang, style, field_name, inner_table)
end
return templateText, Categories, coorTag
end
function p.LocationTemplateCore(frame)
local args = getArgs(frame)
args.namespace = mw.title.getCurrentTitle().nsText
if not args.lat and not args.lon then -- if no lat and lon but numbered arguments present
if args[4] then -- DMS with pipes format, ex. "34|5|32.36|N|116|9|24|55|W"
args.lat = dms2deg_ ( args[1], args[2], args[3], args[4] )
args.lon = dms2deg_ ( args[5], args[6], args[7], args[8] )
args.attributes = args.attributes or args[9]
elseif args[2] and not (type(args[2])=='string' and args[2]:find(":")) then -- decimal format or DMS with one pipe, ex. "34° 05′ 32.36″ N| 116° 09′ 24.55″ W"
args.lat = args[1]
args.lon = args[2]
args.attributes = args.attributes or args[3]
elseif args[1] then -- detect a single argument in the form "34° 05′ 32.36″ N, 116° 09′ 24.55″ W" or similar
local v = mw.text.split(args[1]:gsub("([NnSs])", "%1/" ), "/") -- split into lat and lon using splitting point after any letter
args.lat, args.lon = v[1], v[2]
args.attributes = args.attributes or args[2]
end
end
local cat = ''
if args.lat and args.lon then
local lat = tonumber(args.lat)
local lon = tonumber(args.lon)
if not lat or not lon then
args.lat = dms2deg(args.lat or '')
args.lon = dms2deg(args.lon or '')
if (args.namespace == 'File' or args.namespace == 'Category') then
cat = CoorCat.dms
end
end
end
local templateText, Categories, coorTag = p._LocationTemplateCore(args)
return templateText .. Categories .. cat .. coorTag
end
return p