Module:Coordinates
Documentation for this module may be created at Module:Coordinates/doc
--[[
This module is intended to provide functionality of {{location}} and related
templates.
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 ======================
-- =======================================
local i18n = require('Module:I18n/coordinates') -- get localized translations of site names
local yesno = require('Module:Yesno')
-- =======================================
-- === Hardwired parameters ==============
-- =======================================
-- Angles associated with each abriviation 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',
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]]',
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'
}
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 mergeWithWikidata(q, lat1, lon1)
-- we are given wikidata q-code so look up the coordinates
local dist_str=''
-- Wikiata coordinates
if q:match( '^[Qq]%d+$' ) then
entity = mw.wikibase.getEntity(q)
else
entity = q
end
q = 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/#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span> to copy it",
q, q, q, lat1, lon1)
cat = CoorCat.wikidata4 .. info_box(cat)
elseif lat1 and lon1 and lat2 and lon2 then
-- 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) -- rind it to even meters
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/#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span>",
d, q, q, info, precision, q, 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
}
s = entity:getBestStatements( 'P31' )
if s[1] and s[1].mainsnak.datavalue.value['numeric-id'] then
local instanceOf = string.format('Q%i',s[1].mainsnak.datavalue.value['numeric-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, q, cat, dist_str
end
-- =======================================
-- === Functions =========================
-- =======================================
p = {}
-- parse attribute variable returning desired field
function p.parseAttribute(frame)
return string.match(mw.text.decode(frame.args[1]), mw.text.decode(frame.args[2]) .. ':' .. '([^_]*)') or ''
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 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
--[[============================================================================
Convert degrees to degrees/minutes/seconds notation comonly used when displaying
coordinates.
Inputs:
1) latitude or longitude angle in degrees
2) georeference precission in degrees
3) language used in formating of the number
==============================================================================]]
function p.deg2dms(frame)
local deg = tonumber(frame.args[1])
local degPrec = tonumber(frame.args[2]) or 0-- precision in degrees
local lang = frame.args.lang
if not (lang and mw.language.isSupportedLanguage(lang)) then
lang = frame:callParserFunction("int","lang") -- get user's chosen language
end
if deg==nil then
return frame.args[1];
else
return p._deg2dms(deg, degPrec, lang)
end
end
--[[============================================================================
Helper core function for deg2dms. deg2dms can be called by templates, while
_deg2dms should be called from Lua.
Inputs:
* deg - positive coordinate in degrees
* degPrec - coordinate precission in degrees will result in different angle format
* lang - language to used when formating the number
==============================================================================]]
function p._deg2dms(deg, degPrec, lang)
local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k
local Lang = mw.language.new(lang)
-- adjust number display based on precission
secPrec = degPrec*3600.0 -- coordinate precission 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*(deg%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
if mNum<10 then
mStr = '0' ..mStr -- pad with zero if a single digit
end
if sNum<10 then
sStr = '0' ..sStr -- pad with zero if less than ten
end
return string.format(formatStr, dStr, mStr, sStr);
end
--[[============================================================================
Format coordinate location string, by creating and joining DMS strings for
latutude and longitude. Also convert precission from meters to degrees.
INPUTS:
* lat = latitude in degrees
* lon = longitude in degrees
* lang = language code
* prec = geolocation precission in meters
==============================================================================]]
function p.lat_lon(frame)
local lat = tonumber(frame.args.lat)
local lon = tonumber(frame.args.lon)
local prec = math.abs(tonumber(frame.args.prec) or 0) -- location precision in meters
if lon then -- get longitude t0 be in -180 to 180 range
lon=lon%360
if lon>180 then
lon = lon-360
end
end
local lang = frame.args.lang
if not (lang and mw.language.isSupportedLanguage(lang)) then
lang = frame:callParserFunction("int","lang") -- get user's chosen language
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 precission 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
--[[============================================================================
Create URL for different sites.
INPUTS:
* site = Possinle 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)
args = frame.args
local lang = frame.args.lang
if not (lang and mw.language.isSupportedLanguage(lang)) then
lang = frame:callParserFunction("int","lang") -- get user's chosen language
end
return p._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, lang, args.attributes or '')
end
--[[============================================================================
Helper core function for externalLink. Create URL for different sites:
INPUTS:
* site = Possinle 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
--[[============================================================================
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 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 precission in meters
* attributes = attributes to be passed to GeoHack
==============================================================================]]
function p.GeoHack_link(frame)
-- create link and coordintate string
local latlon = p.lat_lon(frame)
if latlon==NoLatLonString then
return latlon
else
frame.args.site = 'GeoHack'
local url = p.externalLink(frame)
return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion">
end
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(frame)
local args = frame.args
local lang = frame.args.lang
if not (lang and mw.language.isSupportedLanguage(lang)) then
lang = frame:callParserFunction("int","lang") -- get user's chosen language
end
if not args.namespace then
args.namespace = mw.title.getCurrentTitle().namespace
end
local str, link1, link2
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] - [%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
--[[============================================================================
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 metchod/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 precission in meters
==============================================================================]]
function p.LocationTemplateCore(frame)
-- prepare arguments
args = frame.args
if not args or not args.lat then -- if no arguments provided than use parent arguments
args = mw.getCurrentFrame():getParent().args
end
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then
args.lang = frame:callParserFunction("int","lang") -- get user's chosen language
end
if not (args.namespace) then -- if namespace not provided than look it up
args.namespace = mw.title.getCurrentTitle().namespace
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.attributes = p.alterAttributes(args.attributes or '', args.mode)
-- Convert coordinates from string to numbers
local lat = tonumber(args.lat)
local lon = tonumber(args.lon)
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.wikidata and args.wikidata~='' then
local dist_str
-- if lat/lon is not provided but we are given wikidata q-code than look up the coordinates
lat, lon, q, Categories, dist_str = mergeWithWikidata(args.wikidata, lat, lon)
wikidata_link = string.format("\n[[File:Wikidata-logo.svg|20px|Edit coordinates on Wikidata%s|link=wikidata:%s]]", dist_str, q);
end
args.lat = string.format('%010.6f', lat or 0)
args.lon = string.format('%011.6f', lon or 0)
frame.args = args
-- 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 frame.args.attributes and string.find(frame.args.attributes, '=') then
Categories = Categories .. CoorCat.attribute
end
if args.error=='1' 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 = string.format('{{#coordinates:primary|%10.6f|%11.6f|%s}}', lat, lon, args.attributes)
coorTag = frame:preprocess(coorTag)
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, heading, OSM = '','','','','','',''
coor = p.GeoHack_link(frame) -- the p and link to GeoHack
heading = p._getHeading(frame.args.attributes) -- get heading arrow section
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
external_link = p.externalLinksSection(frame) -- 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:Circle-information.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 '', info_link, wikidata_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
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
return p