Changes

Jump to navigation Jump to search
10,229 bytes added ,  08:26, 17 January 2019
revert to Revision as of 23:12, 27 November 2018 (later edits broke something)
Line 1: Line 1:  
--[[
 
--[[
This module is intended to replace the functionality of {{Coord}} and related
+
  __  __          _      _        ____                    _ _            _           
templates. It provides several methods, including
+
|  \/  | ___  __| |_  _| | ___ _ / ___|___  ___  _ __ __| (_)_ __  __ _| |_ ___  ___
 +
| |\/| |/ _ \ / _` | | | | |/ _ (_) |  / _ \ / _ \| '__/ _` | | '_ \ / _` | __/ _ \/ __|
 +
| |  | | (_) | (_| | |_| | |  __/_| |__| (_) | (_) | | | (_| | | | | | (_| | ||  __/\__ \
 +
  |_|  |_|\___/ \__,_|\__,_|_|\___(_)\____\___/ \___/|_|  \__,_|_|_| |_|\__,_|\__\___||___/
 +
                                                                                         
   −
{{#invoke:Coordinates | coord }} : General function formatting and displaying
+
This module is intended to provide functionality of {{location}} and related
coordinate values.
+
templates. It was developed on Wikimedia Commons, so if you find this code on
 +
other sites, check there for updates and discussions.
   −
{{#invoke:Coordinates | dec2dms }} : Simple function for converting decimal
+
Please do not modify this code without applying the changes first at Module:Coordinates/sandbox and testing
degree values to DMS format.
+
at Module:Coordinates/sandbox/testcases and Module talk:Coordinates/sandbox/testcases.
   −
{{#invoke:Coordinates | dms2dec }} : Simple function for converting DMS format
+
Authors and maintainers:
to decimal degree format.
+
* User:Jarekt
 +
* User:Ebraminio
   −
{{#invoke:Coordinates | link }} : Export the link used to reach the tools
+
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)
    
]]
 
]]
   −
require('Module:No globals')
+
-- =======================================
 +
-- === 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')
   −
local math_mod = require("Module:Math")
+
-- =======================================
local coordinates = {};
+
-- === Hardwired parameters ==============
 +
-- =======================================
   −
local current_page = mw.title.getCurrentTitle()
+
-- Angles associated with each abbreviation of compass point names. See [[:en:Points of the compass]]
local page_name = mw.uri.encode( current_page.prefixedText, 'WIKI' );
+
local compass_points = {
local coord_link = '//tools.wmflabs.org/geohack/geohack.php?pagename=' .. page_name .. '&params='
+
  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,
 +
}
   −
--[[ Helper function, replacement for {{coord/display/title}} ]]
+
-- files to use for different headings
local function displaytitle(s, notes)
+
local heading_icon = {
local l = "[[Geographic coordinate system|Coordinates]]: " .. s
+
[ 1] = 'File:Compass-icon bb N.svg',
local co = '<span id="coordinates">' .. l .. notes .. '</span>';
+
[ 2] = 'File:Compass-icon bb NbE.svg',
return '<span style="font-size: small;">' .. co .. '</span>';
+
[ 3] = 'File:Compass-icon bb NNE.svg',
end
+
[ 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'
 +
}
   −
--[[ Helper function, Replacement for {{coord/display/inline}} ]]
+
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr, $page will be
local function displayinline(s, notes)
+
-- replaced with latitude, longitude, language code, GeoHack attribution parameters and full-page-name strings.
return s .. notes
+
local SiteURL = {
end
+
GeoHack        = '//tools.wmflabs.org/geohack/geohack.php?pagename=$page&params=$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'
 +
}
 +
}
   −
--[[ Helper function, used in detecting DMS formatting ]]
+
-- Categories
local function dmsTest(first, second)
+
local CoorCat = {
if type(first) ~= 'string' or type(second) ~= 'string' then
+
File          = '[[Category:Media with locations]]',
return nil
+
Gallery      = '[[Category:Galleries with coordinates]]',
end
+
Category      = '[[Category:Categories with coordinates]]',
local s = (first .. second):upper()
+
wikidata0    = '[[Category:Pages with coordinates from Wikidata]]',
return s:find('^[NS][EW]$') or s:find('^[EW][NS]$')
+
wikidata1    = '[[Category:Pages with local coordinates and matching Wikidata coordinates]]',
end
+
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 Functions ===================
 +
-- =======================================
   −
--[[ Wrapper function to grab args, see Module:Arguments for this function's documentation. ]]
+
local function getArgs(frame)
local function makeInvokeFunc(funcName)
+
local args = frame.args
return function (frame)
+
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then
local args = require('Module:Arguments').getArgs(frame, {
+
args.lang = frame:callParserFunction("int","lang") -- get user's chosen language
wrappers = 'Template:Coord'
  −
})
  −
return coordinates[funcName](args, frame)
   
end
 
end
 +
return args
 
end
 
end
   −
--[[ Helper function, handle optional args. ]]
+
local NoLatLonString = 'latitude, longitude'
local function optionalArg(arg, supplement)
  −
return arg and arg .. supplement or ''
  −
end
     −
--[[
+
local function langSwitch(list,lang)
Formats any error messages generated for display
+
local langList = mw.language.getFallbacksFor(lang)
]]
+
table.insert(langList,1,lang)
local function errorPrinter(errors)
+
for i,language in ipairs(langList) do
local result = ""
+
if list[language] then
for i,v in ipairs(errors) do
+
return list[language]
local errorHTML = '<strong class="error">Coordinates: ' .. v[2] .. '</strong>'
+
end
result = result .. errorHTML .. "<br />"
   
end
 
end
return result
   
end
 
end
   −
--[[
+
local function add_maplink(lat, lon, marker, text)
Determine the required CSS class to display coordinates
+
local tstr = ''
 
+
if text then
Usually geo-nondefault is hidden by CSS, unless a user has overridden this for himself
+
tstr = string.format('text="%s" ', text)
default is the mode as specificied by the user when calling the {{coord}} template
  −
mode is the display mode (dec or dms) that we will need to determine the css class for
  −
]]
  −
local function displayDefault(default, mode)
  −
if default == "" then
  −
default = "dec"
  −
end
  −
  −
if default == mode then
  −
return "geo-default"
  −
else
  −
return "geo-nondefault"
   
end
 
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
 
end
   −
--[[
+
local function add_maplink2(lat1, lon1, lat2, lon2)
specPrinter
+
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
   −
Output formatter.  Takes the structure generated by either parseDec
+
local function info_box(text)
or parseDMS and formats it for inclusion on Wikipedia.
+
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; ">'..
local function specPrinter(args, coordinateSpec)
+
'<tr>'..
local uriComponents = coordinateSpec["param"]
+
'<td class="mbox-image" style="padding-left:.9em;">'..
if uriComponents == "" then
+
' [[File:Commons-emblem-issue.svg|class=noviewer|45px]]</td>'..
-- RETURN error, should never be empty or nil
+
'<td class="mbox-text" style="">%s</td>'..
return "ERROR param was empty"
+
'</tr></table>', text)
end
+
end
if args["name"] then
  −
uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec["name"])
  −
end
  −
  −
local geodmshtml = '<span class="geo-dms" title="Maps, aerial photos, and other data for this location">'
  −
.. '<span class="latitude">' .. coordinateSpec["dms-lat"] .. '</span> '
  −
.. '<span class="longitude">' ..coordinateSpec["dms-long"] .. '</span>'
  −
.. '</span>'
     −
local lat = tonumber( coordinateSpec["dec-lat"] ) or 0
+
local function mergeWithWikidata(q, lat1, lon1)
local geodeclat
+
-- we are given wikidata q-code so look up the coordinates
if lat < 0 then
+
local dist_str=''
-- FIXME this breaks the pre-existing precision
+
local entity
geodeclat = tostring(coordinateSpec["dec-lat"]):sub(2) .. "°S"
+
-- Wikiata coordinates
 +
if q:match( '^[Qq]%d+$' ) then
 +
entity = mw.wikibase.getEntity(q)
 
else
 
else
geodeclat = (coordinateSpec["dec-lat"] or 0) .. "°N"
+
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
 
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",
 +
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/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span>",
 +
d, q, q, info, precision, q, lat1, lon1)
   −
local long = tonumber( coordinateSpec["dec-long"] ) or 0
+
if d<20 or d<precision then -- will consider location within 20 meters or precisi0on distance as the same
local geodeclong
+
cat = CoorCat.wikidata1
if long < 0 then
+
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
-- FIXME does not handle unicode minus
+
elseif d>1000 and d>5*precision then -- locations 1 km off and 5 precision distances away are likely wrong
geodeclong = tostring(coordinateSpec["dec-long"]):sub(2) .. "°W"
+
cat = CoorCat.wikidata3 .. info_box(info)  
else
+
else
geodeclong = (coordinateSpec["dec-long"] or 0) .. "°E"
+
cat = CoorCat.wikidata2 .. info_box(info)  
 +
end
 
end
 
end
 
 
local geodechtml = '<span class="geo-dec" title="Maps, aerial photos, and other data for this location">'
+
-- verify proper P31 (instance of). List is based on https://www.wikidata.org/wiki/Property_talk:P625
.. geodeclat .. ' '
+
local QCodes =
.. geodeclong
+
Q5        = 1, -- human
.. '</span>'
+
Q11879590 = 1, -- female given name
 
+
Q202444  = 1, -- given name
local geonumhtml = '<span class="geo">'
+
Q12308941 = 1, -- male given name
.. coordinateSpec["dec-lat"] .. '; '
+
Q4167836  = 1, -- Wikimedia category
.. coordinateSpec["dec-long"]
+
Q4167410  = 1, -- Wikimedia disambiguation page
.. '</span>'
+
Q783794  = 2, -- company
 
+
Q4830453  = 2, -- business enterprise
local inner = '<span class="' .. displayDefault(coordinateSpec["default"], "dms" ) .. '">' .. geodmshtml .. '</span>'
+
}
.. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'
+
local s = entity:getBestStatements( 'P31' )
.. '<span class="' .. displayDefault(coordinateSpec["default"], "dec" ) .. '">';
+
if s[1] and s[1].mainsnak.datavalue.value['id'] then
 
+
local instanceOf = s[1].mainsnak.datavalue.value['id']
if not args["name"] then
+
if QCodes[instanceOf] then
inner = inner .. geodechtml
+
cat = '' -- wipe out categories
.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span></span>'
+
if QCodes[instanceOf]==1 then -- add problem category
else
+
cat = CoorCat.wikidata5
inner = inner .. '<span class="vcard">' .. geodechtml
+
end
.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span>'
+
end
.. '<span style="display:none">&#xfeff; (<span class="fn org">'
  −
.. args["name"] .. '</span>)</span></span></span>'
   
end
 
end
  −
return '<span class="plainlinks nourlexpansion">' ..
  −
'[' .. coord_link .. uriComponents .. ' ' .. inner .. ']' .. '</span>'
  −
end
  −
  −
--[[ Helper function, convert decimal to degrees ]]
  −
local function convert_dec2dms_d(coordinate)
  −
local d = math_mod._round( coordinate, 0 ) .. "°"
  −
return d .. ""
  −
end
  −
  −
--[[ Helper function, convert decimal to degrees and minutes ]]
  −
local function convert_dec2dms_dm(coordinate)
  −
coordinate = math_mod._round( coordinate * 60, 0 );
  −
local m = coordinate % 60;
  −
coordinate = math.floor( (coordinate - m) / 60 );
  −
local d = coordinate % 360 .."°"
   
 
return d .. string.format( "%02d′", m )
+
return lat1, lon1, q, cat, dist_str
 
end
 
end
   −
--[[ Helper function, convert decimal to degrees, minutes, and seconds ]]
+
-- =======================================
local function convert_dec2dms_dms(coordinate)
+
-- === External Functions ================
coordinate = math_mod._round( coordinate * 60 * 60, 0 );
+
-- =======================================
local s = coordinate % 60
+
local p = {}
coordinate = math.floor( (coordinate - s) / 60 );
  −
local m = coordinate % 60
  −
coordinate = math.floor( (coordinate - m) / 60 );
  −
local d = coordinate % 360 .."°"
     −
return d .. string.format( "%02d′", m ) .. string.format( "%02d″", s )
+
-- 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 function, convert decimal latitude or longitude to
+
function p._getHeading(attributes)
degrees, minutes, and seconds format based on the specified precision.
+
if attributes == nil then
]]
+
return nil
local function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
+
end
local coord = tonumber(coordinate)
+
local hStr = string.match(mw.text.decode(attributes), 'heading:([^_]*)')
local postfix
+
if hStr == nil then
if coord >= 0 then
+
return nil
postfix = firstPostfix
+
end
else
+
local hNum = tonumber( hStr )
postfix = secondPostfix
+
if hNum == nil then
 +
hStr = string.upper (hStr)
 +
hNum = compass_points[hStr] 
 
end
 
end
 
+
if hNum ~= nil then
precision = precision:lower();
+
hNum = hNum%360
if precision == "dms" then
  −
return convert_dec2dms_dms( math.abs( coord ) ) .. postfix;
  −
elseif precision == "dm" then
  −
return convert_dec2dms_dm( math.abs( coord ) ) .. postfix;
  −
elseif precision == "d" then
  −
return convert_dec2dms_d( math.abs( coord ) ) .. postfix;
   
end
 
end
 +
return hNum
 
end
 
end
   −
--[[
+
--[[============================================================================
Convert DMS format into a N or E decimal coordinate
+
Parse attribute variable returning heading field. If heading is a string than
]]
+
try to convert it to an angle
local function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str)
+
==============================================================================]]
local degrees = tonumber(degrees_str)
+
 
local minutes = tonumber(minutes_str) or 0
+
function p.getHeading(frame)
local seconds = tonumber(seconds_str) or 0
+
local attributes
+
if frame.args[1] then
local factor = 1
+
attributes = frame.args[1]
if direction == "S" or direction == "W" then
+
elseif frame.args.attributes then
factor = -1
+
attributes = frame.args.attributes
 +
else
 +
return ''
 
end
 
end
+
local hNum = p._getHeading(attributes)
local precision = 0
+
if hNum == nil then
if seconds_str then
+
return ''
precision = 5 + math.max( math_mod._precision(seconds_str), 0 );
  −
elseif minutes_str and minutes_str ~= '' then
  −
precision = 3 + math.max( math_mod._precision(minutes_str), 0 );
  −
else
  −
precision = math.max( math_mod._precision(degrees_str), 0 );
   
end
 
end
+
return tostring(hNum)
local decimal = factor * (degrees+(minutes+seconds/60)/60)
  −
return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based.
   
end
 
end
   −
--[[
  −
Checks input values to for out of range errors.
  −
]]
  −
local function validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, source, strong )
  −
local errors = {};
  −
lat_d = tonumber( lat_d ) or 0;
  −
lat_m = tonumber( lat_m ) or 0;
  −
lat_s = tonumber( lat_s ) or 0;
  −
long_d = tonumber( long_d ) or 0;
  −
long_m = tonumber( long_m ) or 0;
  −
long_s = tonumber( long_s ) or 0;
     −
if strong then
+
--[[============================================================================
if lat_d < 0 then
+
Helper core function for deg2dms. deg2dms can be called by templates, while
table.insert(errors, {source, "latitude degrees < 0 with hemisphere flag"})
+
_deg2dms should be called from Lua.
end
+
Inputs:
if long_d < 0 then
+
* degree - positive coordinate in degrees
table.insert(errors, {source, "longitude degrees < 0 with hemisphere flag"})
+
* degPrec - coordinate precision in degrees will result in different angle format
end
+
* lang - language to used when formatting the number
--[[
+
==============================================================================]]
#coordinates is inconsistent about whether this is an error. If globe: is
+
function p._deg2dms(degree, degPrec, lang)
specified, it won't error on this condition, but otherwise it will.
+
local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k, d, zero
+
local Lang = mw.language.new(lang)
For not simply disable this check.
+
 
+
-- adjust number display based on precision
if long_d > 180 then
+
secPrec = degPrec*3600.0                    -- coordinate precision in seconds
table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"})
+
if secPrec<0.05 then                        -- degPrec<1.3889e-05
end
+
formatStr = '%s°&nbsp;%s′&nbsp;%s″'     -- use DD° MM′ SS.SS″ format
]]
+
c = 360000
end
+
elseif secPrec<0.5 then                      -- 1.3889e-05<degPrec<1.3889e-04
+
formatStr = '%s°&nbsp;%s′&nbsp;%s″'      -- use DD° MM′ SS.S″ format
if lat_d > 90 then
+
c = 36000
table.insert(errors, {source, "latitude degrees > 90"})
+
elseif degPrec*60.0<0.5 then                 -- 1.3889e-04<degPrec<0.0083
 +
formatStr = '%s°&nbsp;%s′&nbsp;%s″'      -- use DD° MM′ SS″ format
 +
c = 3600
 +
elseif degPrec<0.5 then                      -- 0.0083<degPrec<0.5
 +
formatStr = '%s°&nbsp;%s′'              -- use DD° MM′ format
 +
c = 60
 +
else -- if degPrec>0.5 then                
 +
formatStr = '%s°'                        -- use DD° format
 +
c = 1
 
end
 
end
if lat_d < -90 then
+
table.insert(errors, {source, "latitude degrees < -90"})
+
-- create degree, minute and seconds numbers and string
end
+
d = c/60
if lat_m >= 60 then
+
= 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
table.insert(errors, {source, "latitude minutes >= 60"})
+
dNum = math.floor(k/c) % 360          -- degree number (integer in 0-360 range)
end
+
mNum = math.floor(k/d) %  60         -- minute number (integer in 0-60 range)
if lat_m < 0 then
+
sNum =      3600*(k%d) / c            -- seconds number (float in 0-60 range with 0, 1 or 2 decimal digits)
table.insert(errors, {source, "latitude minutes < 0"})
+
dStr = Lang:formatNum(dNum)          -- degree string
end
+
mStr = Lang:formatNum(mNum)           -- minute string
if lat_s >= 60 then
+
sStr = Lang:formatNum(sNum)          -- second string
table.insert(errors, {source, "latitude seconds >= 60"})
+
zero = Lang:formatNum(0)              -- zero string in local language
end
+
if mNum<10 then
if lat_s < 0 then
+
mStr = zero .. mStr                -- pad with zero if a single digit
table.insert(errors, {source, "latitude seconds < 0"})
  −
end
  −
if long_d >= 360 then
  −
table.insert(errors, {source, "longitude degrees >= 360"})
  −
end
  −
if long_d <= -360 then
  −
table.insert(errors, {source, "longitude degrees <= -360"})
  −
end
  −
if long_m >= 60 then
  −
table.insert(errors, {source, "longitude minutes >= 60"})
   
end
 
end
if long_m < 0 then
+
if sNum<10 then
table.insert(errors, {source, "longitude minutes < 0"})
+
sStr = zero .. sStr                -- pad with zero if less than ten
 
end
 
end
if long_s >= 60 then
+
return string.format(formatStr, dStr, mStr, sStr);
table.insert(errors, {source, "longitude seconds >= 60"})
  −
end
  −
if long_s < 0 then
  −
table.insert(errors, {source, "longitude seconds < 0"})
  −
end
  −
  −
return errors;
   
end
 
end
   −
--[[
+
--[[============================================================================
parseDec
+
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
   −
Transforms decimal format latitude and longitude into the
+
if degree==nil then
structure to be used in displaying coordinates
+
return args[1];
]]
+
else
local function parseDec( lat, long, format )
+
return p._deg2dms(degree, degPrec, args.lang)
local coordinateSpec = {}
  −
local errors = {}
  −
  −
if not long then
  −
return nil, {{"parseDec", "Missing longitude"}}
  −
elseif not tonumber(long) then
  −
return nil, {{"parseDec", "Longitude could not be parsed as a number: " .. long}}
   
end
 
end
+
end
errors = validate( lat, nil, nil, long, nil, nil, 'parseDec', false );
  −
coordinateSpec["dec-lat"]  = lat;
  −
coordinateSpec["dec-long"] = long;
     −
local mode = coordinates.determineMode( lat, long );
+
--[[============================================================================
coordinateSpec["dms-lat"] = convert_dec2dms( lat, "N", "S", mode-- {{coord/dec2dms|{{{1}}}|N|S|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}
+
Format coordinate location string, by creating and joining DMS strings for
coordinateSpec["dms-long"] = convert_dec2dms( long, "E", "W", mode) -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}
+
latitude and longitude. Also convert precision from meters to degrees.
+
INPUTS:
if format then
+
* lat        = latitude in degrees
coordinateSpec.default = format
+
* 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
 
else
coordinateSpec.default = "dec"
+
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&nbsp;%s, %s&nbsp;%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
 +
end
   −
return coordinateSpec, errors
+
function p.lat_lon(frame)
 +
local args = getArgs(frame)
 +
return p._lat_lon(args.lat, args.lon, args.prec, args.lang)
 
end
 
end
   −
--[[
+
--[[============================================================================
parseDMS
+
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, '%%', '%%%%')
   −
Transforms degrees, minutes, seconds format latitude and longitude
+
if site == 'GoogleMaps' then
into the a structure to be used in displaying coordinates
+
URLstr = SiteURL.GoogleMaps[globe]
]]
+
elseif site == 'GeoHack' then
local function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format )
+
attributes = string.format('globe:%s_%s', globe, attributes)
local coordinateSpec, errors, backward = {}, {}
+
URLstr = mw.ustring.gsub( URLstr, '$attr', attributes)
  −
lat_f = lat_f:upper();
  −
long_f = long_f:upper();
  −
  −
-- Check if specified backward
  −
if lat_f == 'E' or lat_f == 'W' then
  −
lat_d, long_d, lat_m, long_m, lat_s, long_s, lat_f, long_f, backward = long_d, lat_d, long_m, lat_m, long_s, lat_s, long_f, lat_f, true;
  −
end
  −
  −
errors = validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, 'parseDMS', true );
  −
if not long_d then
  −
return nil, {{"parseDMS", "Missing longitude" }}
  −
elseif not tonumber(long_d) then
  −
return nil, {{"parseDMS", "Longitude could not be parsed as a number:" .. long_d }}
   
end
 
end
+
URLstr = mw.ustring.gsub( URLstr, '$lat' , latStr)
if not lat_m and not lat_s and not long_m and not long_s and #errors == 0 then
+
URLstr = mw.ustring.gsub( URLstr, '$lon' , lonStr)
if math_mod._precision( lat_d ) > 0 or math_mod._precision( long_d ) > 0 then
+
URLstr = mw.ustring.gsub( URLstr, '$lang' , lang)
if lat_f:upper() == 'S' then
+
URLstr = mw.ustring.gsub( URLstr, '$level', level)
lat_d = '-' .. lat_d;
+
URLstr = mw.ustring.gsub( URLstr, '$page' , pageName)
end
+
URLstr = mw.ustring.gsub( URLstr, '+', '')
if long_f:upper() == 'W' then
+
URLstr = mw.ustring.gsub( URLstr, ' ', '_')
long_d = '-' .. long_d;
+
return URLstr
end
+
end
  −
return parseDec( lat_d, long_d, format );
  −
end
  −
end 
  −
  −
coordinateSpec["dms-lat"]  = lat_d.."°"..optionalArg(lat_m,"′") .. optionalArg(lat_s,"″") .. lat_f
  −
coordinateSpec["dms-long"] = long_d.."°"..optionalArg(long_m,"′") .. optionalArg(long_s,"″") .. long_f
  −
coordinateSpec["dec-lat"]  = convert_dms2dec(lat_f, lat_d, lat_m, lat_s) -- {{coord/dms2dec|{{{4}}}|{{{1}}}|0{{{2}}}|0{{{3}}}}}
  −
coordinateSpec["dec-long"] = convert_dms2dec(long_f, long_d, long_m, long_s) -- {{coord/dms2dec|{{{8}}}|{{{5}}}|0{{{6}}}|0{{{7}}}}}
     −
if format then
+
--[[============================================================================
coordinateSpec.default = format
+
Create URL for different sites.
else
+
INPUTS:
coordinateSpec.default = "dms"
+
* site      = Possible sites: GeoHack, GoogleEarth, Proximityrama,
end 
+
                OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
 
+
* globe      = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
return coordinateSpec, errors, backward
+
                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
   −
--[[  
+
--[[============================================================================
Check the input arguments for coord to determine the kind of data being provided
+
Adjust GeoHack attributes depending on the template that calls it
and then make the necessary processing.
+
INPUTS:
]]
+
* attributes = attributes to be passed to GeoHack
local function formatTest(args)
+
* mode = set by each calling template
local result, errors
+
==============================================================================]]
local backward, primary = false, false
+
function p.alterAttributes(attributes, mode)
 
+
-- indicate which template called it
local function getParam(args, lim)
+
if mode=='camera' then                                   -- Used by {{Location}} and {{Location dec}}
local ret = {}
+
if string.find(attributes, 'type:camera')==nil then
for i = 1, lim do
+
attributes = 'type:camera_' .. attributes
ret[i] = args[i] or ''
  −
end
  −
return table.concat(ret, '_')
  −
end
  −
  −
if not args[1] then
  −
-- no lat logic
  −
return errorPrinter( {{"formatTest", "Missing latitude"}} )
  −
elseif not tonumber(args[1]) then
  −
-- bad lat logic
  −
return errorPrinter( {{"formatTest", "Unable to parse latitude as a number:" .. args[1]}} )
  −
elseif not args[4] and not args[5] and not args[6] then
  −
-- dec logic
  −
result, errors = parseDec(args[1], args[2], args.format)
  −
if not result then
  −
return errorPrinter(errors);
  −
end
  −
-- formatting for geohack: geohack expects D_N_D_E notation or D;D notation
  −
-- wikiminiatlas doesn't support D;D notation
  −
-- #coordinates parserfunction doesn't support negative decimals with NSWE
  −
result.param = table.concat({
  −
math.abs(tonumber(args[1])),
  −
((tonumber(args[1]) or 0) < 0) and 'S' or 'N',
  −
math.abs(tonumber(args[2])),  
  −
((tonumber(args[2]) or 0) < 0) and 'W' or 'E',
  −
args[3] or ''}, '_')
  −
elseif dmsTest(args[4], args[8]) then
  −
-- dms logic
  −
result, errors, backward = parseDMS(args[1], args[2], args[3], args[4],
  −
args[5], args[6], args[7], args[8], args.format)
  −
if args[10] then
  −
table.insert(errors, {'formatTest', 'Extra unexpected parameters'})
  −
end
  −
if not result then
  −
return errorPrinter(errors)
  −
end
  −
result.param = getParam(args, 9)
  −
elseif dmsTest(args[3], args[6]) then
  −
-- dm logic
  −
result, errors, backward = parseDMS(args[1], args[2], nil, args[3],
  −
args[4], args[5], nil, args[6], args['format'])
  −
if args[8] then
  −
table.insert(errors, {'formatTest', 'Extra unexpected parameters'})
   
end
 
end
if not result then
+
elseif mode=='object'or mode =='globe' then              -- Used by {{Object location}}
return errorPrinter(errors)
+
if mode=='object' and string.find(attributes, 'type:')==nil then
 +
attributes = 'type:object_' .. attributes
 
end
 
end
result.param = getParam(args, 7)
+
if string.find(attributes, 'class:object')==nil then
elseif dmsTest(args[2], args[4]) then
+
attributes = 'class:object_' .. attributes
-- d logic
  −
result, errors, backward = parseDMS(args[1], nil, nil, args[2],
  −
args[3], nil, nil, args[4], args.format)
  −
if args[6] then
  −
table.insert(errors, {'formatTest', 'Extra unexpected parameters'})
  −
end
  −
if not result then
  −
return errorPrinter(errors)
   
end
 
end
result.param = getParam(args, 5)
+
elseif mode=='inline' then                              -- Used by {{Inline coordinates}} (actually that template does not set any attributes at the moment)
else
+
elseif mode=='user' then                                -- Used by {{User location}}
-- Error
+
attributes = 'type:user_location'
return errorPrinter({{"formatTest", "Unknown argument format"}})
+
elseif mode=='institution' then                          --Used by {{Institution/coordinates}} (categories only)
 +
attributes = 'type:institution'
 
end
 
end
result.name = args.name
+
return attributes
 +
end
 
 
local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type'}
+
--[[============================================================================
for _, v in ipairs(extra_param) do
+
Create link to GeoHack tool which displays latitude and longitude coordinates
if args[v] then  
+
in DMS format
table.insert(errors, {'formatTest', 'Parameter: "' .. v .. '=" should be "' .. v .. ':"' })
+
INPUTS:
end
+
* 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
  −
local ret = specPrinter(args, result)
  −
if #errors > 0 then
  −
ret = ret .. ' ' .. errorPrinter(errors) .. '[[Category:Pages with malformed coordinate tags]]'
  −
end
  −
return ret, backward
   
end
 
end
   −
--[[
+
function p.GeoHack_link(frame)
Generate Wikidata tracking categories.
+
return p._GeoHack_link(getArgs(frame))
]]
  −
local function makeWikidataCategories(qid)
  −
local ret
  −
if mw.wikibase and current_page.namespace == 0 then
  −
local entity = qid and mw.wikibase.getEntityObject(qid) or mw.wikibase.getEntityObject()
  −
if entity and entity.claims and entity.claims.P625 and entity.claims.P625[1] then
  −
local snaktype = entity.claims.P625[1].mainsnak.snaktype
  −
if snaktype == 'value' then
  −
-- coordinates exist both here and on Wikidata, and can be compared.
  −
ret = 'Coordinates on Wikidata'
  −
elseif snaktype == 'somevalue' then
  −
ret = 'Coordinates on Wikidata set to unknown value'
  −
elseif snaktype == 'novalue' then
  −
ret = 'Coordinates on Wikidata set to no value'
  −
end
  −
else
  −
-- We have to either import the coordinates to Wikidata or remove them here.
  −
ret = 'Coordinates not on Wikidata'
  −
end
  −
end
  −
if ret then
  −
return string.format('[[Category:%s]]', ret)
  −
else
  −
return ''
  −
end
   
end
 
end
   −
--[[
  −
link
     −
Simple function to export the coordinates link for other uses.
+
--[[============================================================================
 
+
Create full external links section of {{Location}} or {{Object location}}
Usage:
+
templates, based on:
{{#invoke:Coordinates | link }}
+
* 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().namespace
 +
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
function coordinates.link(frame)
  −
return coord_link;
   
end
 
end
   −
--[[
+
function p.externalLinksSection(frame)
dec2dms
+
return p._externalLinksSection(getArgs(frame))
 +
end
   −
Wrapper to allow templates to call dec2dms directly.
+
--[[============================================================================
 
+
Core section of template:Location, template:Object location and template:Globe location.
Usage:
+
This method requires several arguments to be passed to it or it's parent method/template:
{{#invoke:Coordinates | dec2dms | decimal_coordinate | positive_suffix |
+
* globe      = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
negative_suffix | precision }}
+
* 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().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
 +
local attributes0 = args.attributes
 +
args.attributes = p.alterAttributes(args.attributes or '', args.mode)
 
 
decimal_coordinate is converted to DMS format.  If positive, the positive_suffix
+
-- Convert coordinates from string to numbers
is appended (typical N or E), if negative, the negative suffix is appended.  The
+
local lat = tonumber(args.lat)
specified precision is one of 'D', 'DM', or 'DMS' to specify the level of detail
+
local lon = tonumber(args.lon)
to use.
+
if lon then -- get longitude to be in -180 to 180 range
]]
+
lon=lon%360
coordinates.dec2dms = makeInvokeFunc('_dec2dms')
+
if lon>180 then
function coordinates._dec2dms(args)
+
lon = lon-360
local coordinate = args[1]
+
end
local firstPostfix = args[2] or ''
  −
local secondPostfix = args[3] or ''
  −
local precision = args[4] or ''
  −
 
  −
return convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
  −
end
  −
 
  −
--[[
  −
Helper function to determine whether to use D, DM, or DMS
  −
format depending on the precision of the decimal input.
  −
]]
  −
function coordinates.determineMode( value1, value2 )
  −
local precision = math.max( math_mod._precision( value1 ), math_mod._precision( value2 ) );
  −
if precision <= 0 then
  −
return 'd'
  −
elseif precision <= 2 then
  −
return 'dm';
  −
else
  −
return 'dms';
   
end
 
end
end
  −
  −
--[[
  −
dms2dec
  −
  −
Wrapper to allow templates to call dms2dec directly.
  −
  −
Usage:
  −
{{#invoke:Coordinates | dms2dec | direction_flag | degrees |
  −
minutes | seconds }}
   
 
Converts DMS values specified as degrees, minutes, seconds too decimal format.
+
-- If wikidata link provided than compare coordinates
direction_flag is one of N, S, E, W, and determines whether the output is
+
local Categories, geoMicroFormat, coorTag, wikidata_link = '', '', '', ''
positive (i.e. N and E) or negative (i.e. S and W).
+
if args.wikidata and args.wikidata~='' then
]]
+
local dist_str, q
coordinates.dms2dec = makeInvokeFunc('_dms2dec')
+
  -- if lat/lon is not provided but we are given wikidata q-code than look up the coordinates
function coordinates._dms2dec(args)
+
lat, lon, q, Categories, dist_str = mergeWithWikidata(args.wikidata, lat, lon)
local direction = args[1]
+
wikidata_link = string.format("\n[[File:Wikidata-logo.svg|20px|Edit coordinates on Wikidata%s|link=wikidata:%s]]", dist_str, q);
local degrees = args[2]
+
end
local minutes = args[3]
  −
local seconds = args[4]
     −
return convert_dms2dec(direction, degrees, minutes, seconds)
+
args.lat = string.format('%010.6f', lat or 0)
end
+
args.lon = string.format('%011.6f', lon or 0)
   −
--[[
+
local frame = mw.getCurrentFrame()
coord
     −
Main entry point for Lua function to replace {{coord}}
+
-- 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
Usage:
+
if lat and lon then -- if lat and lon are numbers...
{{#invoke:Coordinates | coord }}
+
if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources
{{#invoke:Coordinates | coord | lat | long }}
+
Categories = Categories .. CoorCat.default
{{#invoke:Coordinates | coord | lat | lat_flag | long | long_flag }}
+
end
...
+
if attributes0 and string.find(attributes0, '=') then
+
Categories = Categories .. CoorCat.attribute
Refer to {{coord}} documentation page for many additional parameters and  
+
end
configuration options.
+
if args.error=='1' or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors )
+
Categories = Categories .. CoorCat.erroneous
Note: This function provides the visual display elements of {{coord}}. In
+
end
order to load coordinates into the database, the {{#coordinates:}} parser
+
local cat = CoorCat[args.namespace]
function must also be called, this is done automatically in the Lua
+
if cat then -- add category based on namespace
version of {{coord}}.
+
Categories = Categories .. cat
]]
+
end
coordinates.coord = makeInvokeFunc('_coord')
+
-- if not earth than add a category for each globe
function coordinates._coord(args)
+
if args.mode and args.globe and args.mode=='globe' and args.globe~='Earth' then
if (not args[1] or not tonumber(args[1])) and not args[2] and mw.wikibase.getEntityObject() then
+
Categories = Categories .. string.format(CoorCat[args.mode], args.globe)
args[3] = args[1]; args[1] = nil
+
end
local entity = args.qid and mw.wikibase.getEntityObject(args.qid) or mw.wikibase.getEntityObject()
+
-- add  <span class="geo"> Geo (microformat) code: it is included for machine readability
if entity
+
geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon)
and entity.claims
+
-- add {{#coordinates}} tag, see https://www.mediawiki.org/wiki/Extension:GeoData
and entity.claims.P625
+
if args.namespace == 'File' and Status == 'primary' and args.mode=='camera' then  
and entity.claims.P625[1].mainsnak.snaktype == 'value'
+
coorTag = frame:callParserFunction( '#coordinates', { 'primary', lat, lon, args.attributes } )
then
+
elseif args.namespace == 'File' and args.mode=='object' then
local precision = entity.claims.P625[1].mainsnak.datavalue.value.precision
+
coorTag = frame:callParserFunction( '#coordinates', { lat, lon, args.attributes } )
args[1]=entity.claims.P625[1].mainsnak.datavalue.value.latitude
  −
args[2]=entity.claims.P625[1].mainsnak.datavalue.value.longitude
  −
if precision then
  −
precision=-math_mod._round(math.log(precision)/math.log(10),0)
  −
args[1]=math_mod._round(args[1],precision)
  −
args[2]=math_mod._round(args[2],precision)
   
end
 
end
 +
else -- if lat and lon are not numbers then add error category
 +
Categories = Categories .. CoorCat.erroneous
 
end
 
end
 
end
 
end
  −
local contents, backward = formatTest(args)
  −
local Notes = args.notes or ''
  −
local Display = args.display and args.display:lower() or 'inline'
     −
local function isInline(s)
+
-- Call helper functions to render different parts of the template
-- Finds whether coordinates are displayed inline.
+
local coor,  info_link, inner_table, heading, OSM = '','','','','','',''
return s:find('inline') ~= nil or s == 'i' or s == 'it' or s == 'ti'
+
coor = p._GeoHack_link(args) -- the p and link to GeoHack
 +
heading = p._getHeading(attributes0) -- 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&nbsp;&nbsp;<span title="%s°">[[%s|25px|link=|alt=Heading=%s°]]</span>', coor, heading, fname, heading)
 
end
 
end
local function isInTitle(s)
+
if args.globe=='Earth' then
-- Finds whether coordinates are displayed in the title.
+
local icon = 'marker'
return s:find('title') ~= nil or s == 't' or s == 'it' or s == 'ti'
+
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
 
end
+
local external_link = p._externalLinksSection(args) -- external link section
local function coord_wrapper(in_args)
+
if external_link and args.namespace == 'File' then
-- Calls the parser function {{#coordinates:}}.
+
external_link = langSwitch(i18n.LocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{location}} template
return mw.getCurrentFrame():callParserFunction('#coordinates', in_args) or ''
+
elseif external_link then
 +
external_link = langSwitch(i18n.ObjectLocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{Object location}} template
 
end
 
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&nbsp;%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)
 
 
local text = ''
+
-- combine strings into a table
if isInline(Display) then
+
local templateText
text = text .. displayinline(contents, Notes)
+
if bare then
end
+
templateText  = string.format('<table style="width:100%%"><tr>%s</tr></table>', inner_table)
if isInTitle(Display) then
+
else
text = text
+
-- choose name of the field
.. displaytitle(contents, Notes)
+
local field_name = 'Location'
.. makeWikidataCategories(args.qid)
+
if args.mode=='camera' then
end
+
field_name = langSwitch(i18n.CameraLocation, args.lang)
if not args.nosave then
+
elseif args.mode=='object' then  
local page_title, count = mw.title.getCurrentTitle(), 1
+
field_name = langSwitch(i18n.ObjectLocation, args.lang)
if backward then
+
elseif args.mode=='globe' then
local tmp = {}
+
local field_list = langSwitch(i18n.GlobeLocation, args.lang)
while not string.find((args[count-1] or ''), '[EW]') do tmp[count] = (args[count] or ''); count = count+1 end
+
if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized
tmp.count = count; count = 2*(count-1)
+
field_name = field_list[args.globe]
while count >= tmp.count do table.insert(tmp, 1, (args[count] or '')); count = count-1 end
+
end
for i, v in ipairs(tmp) do args[i] = v end
  −
else
  −
while count <= 9 do args[count] = (args[count] or ''); count = count+1 end
   
end
 
end
if isInTitle(Display) and not page_title.isTalkPage and page_title.subpageText ~= 'doc' and page_title.subpageText ~= 'testcases' then args[10] = 'primary' end
+
--Create HTML text
args.notes, args.format, args.display = nil
+
local dir, text_align
text = text .. coord_wrapper(args)
+
if mw.language.new( args.lang ):isRTL() then
end
+
dir = 'rtl'
return text
+
text_align = 'right'
end
  −
 
  −
--[[
  −
coord2text
  −
 
  −
Extracts a single value from a transclusion of {{Coord}}.
  −
IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED.
  −
 
  −
Usage:
  −
 
  −
    {{#invoke:Coordinates | coord2text | {{Coord}} | parameter }}
  −
 
  −
Valid values for the second parameter are: lat (signed integer), long (signed integer), type, scale, dim, region, globe, source
  −
 
  −
]]
  −
function coordinates.coord2text(frame)
  −
if frame.args[1] == '' or frame.args[2] == '' or not frame.args[2] then return nil end
  −
frame.args[2] = mw.text.trim(frame.args[2])
  −
if frame.args[2] == 'lat' or frame.args[2] == 'long' then
  −
local result, negative = mw.text.split((mw.ustring.match(frame.args[1],'[%.%d]+°[NS] [%.%d]+°[EW]') or ''), ' ')
  −
if frame.args[2] == 'lat' then
  −
result, negative = result[1], 'S'
   
else
 
else
result, negative = result[2], 'W'
+
dir = 'ltr'
end
+
text_align = 'left'
result = mw.text.split(result, '°')
+
end  
if result[2] == negative then result[1] = '-'..result[1] end
+
local style = string.format('class="toccolours mw-content-%s layouttemplate commons-file-information-table" cellpadding="2" style="width: 100%%; direction:%s;" lang="%s"',
return result[1]
+
args.lang, dir, text_align, args.lang)
else
+
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)
return mw.ustring.match(frame.args[1], 'params=.-_'..frame.args[2]..':(.-)[ _]')
   
end
 
end
 +
return templateText, Categories, coorTag
 
end
 
end
   −
--[[
+
function p.LocationTemplateCore(frame)
coordinsert
+
local args = frame.args
 
+
if not args or not args.lat then -- if no arguments provided than use parent arguments
Injects some text into the Geohack link of a transclusion of {{Coord}} (if that text isn't already in the transclusion). Outputs the modified transclusion of {{Coord}}.
+
args = mw.getCurrentFrame():getParent().args
IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED.
  −
 
  −
Usage:
  −
 
  −
    {{#invoke:Coordinates | coordinsert | {{Coord}} | parameter:value | parameter:value | … }}
  −
 
  −
Do not make Geohack unhappy by inserting something which isn't mentioned in the {{Coord}} documentation.
  −
 
  −
]]
  −
function coordinates.coordinsert(frame)
  −
for i, v in ipairs(frame.args) do
  −
if i ~= 1 then
  −
if not mw.ustring.find(frame.args[1], (mw.ustring.match(frame.args[i], '^(.-:)') or '')) then
  −
frame.args[1] = mw.ustring.gsub(frame.args[1], '(params=.-)_? ', '%1_'..frame.args[i]..' ')
  −
end
  −
end
   
end
 
end
if frame.args.name then
+
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then  
if not mw.ustring.find(frame.args[1], '<span class="vcard">') then
+
args.lang = frame:callParserFunction("int","lang") -- get user's chosen language
local namestr = frame.args.name
  −
frame.args[1] = mw.ustring.gsub(frame.args[1],
  −
'(<span class="geo%-default">)(<span[^<>]*>[^<>]*</span><span[^<>]*>[^<>]*<span[^<>]*>[^<>]*</span></span>)(</span>)',  
  −
'%1<span class="vcard">%2<span style="display:none">&#xfeff; (<span class="fn org">' .. namestr .. '</span>)</span></span>%3')
  −
frame.args[1] = mw.ustring.gsub(frame.args[1], '(&params=[^&"<>%[%] ]*) ', '%1&title=' .. mw.uri.encode(namestr) .. ' ')
  −
end
   
end
 
end
return frame.args[1]
+
local templateText, Categories, coorTag = p._LocationTemplateCore(args)
 +
return templateText .. Categories .. coorTag
 
end
 
end
   −
return coordinates
+
return p
Anonymous user

Navigation menu