વિભાગ:Coordinates: આવૃત્તિઓ વચ્ચેનો તફાવત

Content deleted Content added
localize help link
No edit summary
લીટી ૧:
--[[
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.
 
{{#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.
Authors and maintainers:
* User:Jarekt
* User:Ebraminio
 
{{#Invoke:Coordinates | dms2dec }} : Simple function for converting DMS format
Functions:
to decimal degree format.
*function coordinates.LocationTemplateCore(frame)
 
**function coordinates.GeoHack_link(frame)
{{#Invoke:Coordinates | link }} : Export the link used to reach the tools
***function coordinates.lat_lon(frame)
****function coordinates._deg2dms(deg,lang)
***function coordinates.externalLink(frame)
****function coordinates._externalLink(site, globe, latStr, lonStr, lang, attributes)
**function coordinates._getHeading(attributes)
**function coordinates.externalLinksSection(frame)
***function coordinates._externalLink(site, globe, latStr, lonStr, lang, attributes)
*function coordinates.getHeading(frame)
*function coordinates.deg2dms(frame)
 
]]
 
local math_mod = require("Module:Math")
coordinates = {};
local coordinates = {};
 
local current_page = mw.title.getCurrentTitle()
-- =======================================
local page_name = mw.uri.encode( current_page.prefixedText, 'WIKI' );
-- === Dependencies ======================
local coord_link = '//tools.wmflabs.org/geohack/geohack.php?pagename=' .. page_name .. '&params='
-- =======================================
local i18n = require('Module:I18n/coordinates') -- get localized translations of site names
local Fallback = require('Module:Fallback') -- get fallback functions
local yesno = require('Module:Yesno')
 
--[[ Helper function, replacement for {{coord/display/title}} ]]
-- =======================================
local function displaytitle(s, notes)
-- === Hardwired parameters ==============
local l = "[[Geographic coordinate system|Coordinates]]: " .. s
-- =======================================
local co = '<span id="coordinates">' .. l .. notes .. '</span>';
return '<span style="font-size: small;">' .. co .. '</span>';
end
 
--[[ Helper function, Replacement for {{coord/display/inline}} ]]
-- Angles associated with each abriviation of compass point names. See [[:en:Points of the compass]]
local function displayinline(s, notes)
local compass_points = {
return s .. notes
N = 0,
end
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, used in detecting DMS formatting ]]
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr, $page will be
local function dmsTest(first, second)
-- replaced with latitude, longitude, language code, GeoHack attribution parameters and full-page-name strings.
if type(first) ~= 'string' or type(second) ~= 'string' then
local SiteURL = {
return nil
GeoHack = '//tools.wmflabs.org/geohack/geohack.php?pagename=$page&params=$lat_N_$lon_E_$attr&language=$lang',
end
GoogleEarth = '{{fullurl:toollabs:geocommons/earth.kml|latdegdec=$lat&londegdec=$lon&scale=10000&commons=1}}',
local s = (first .. second):upper()
Proximityrama = '{{fullurl:toollabs:geocommons/proximityrama|latlon=$lat,$lon}}',
return s:find('^[NS][EW]$') or s:find('^[EW][NS]$')
OpenStreetMap = '{{fullurl:toollabs:wiwosm/osm-on-ol/commons-on-osm.php|zoom=16&lat=$lat&lon=$lon}}',
end
GoogleMaps = {
Mars = '//www.google.com/mars/#lat=$lat&lon=$lon&zoom=8',
Moon = '//www.google.com/moon/#lat=$lat&lon=$lon&zoom=8',
Earth = 'https://maps.google.com/maps?ll=$lat,$lon&spn=0.01,0.01&t=k&q=http://tools.wmflabs.org/geocommons/web.kml&hl=$lang'
}
}
 
-- Categories
local CoorCat = {
File = '[[Category:Media with locations]]',
Gallery = '[[Category:Galleries with coordinates]]',
Category = '[[Category:Categories with coordinates]]',
globe = '[[Category:Media with %s 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'
}
 
--[[ Wrapper function to grab args, see Module:Arguments for this function's documentation. ]]
local NoLatLonString = 'latitude, longitude'
local function makeInvokeFunc(funcName)
return function (frame)
local args = require('Module:Arguments').getArgs(frame, {
wrappers = 'Template:Coord'
})
return coordinates[funcName](args)
end
end
 
--[[ Helper function, handle optional args. ]]
-- =======================================
local function optionalArg(arg, supplement)
-- === Functions =========================
return arg and arg .. supplement or ''
-- =======================================
end
 
--[[
-- parse attribute variable returning desired field
Formats any error messages generated for display
function coordinates.parseAttribute(frame)
]]
return string.match(mw.text.decode(frame.args[1]), mw.text.decode(frame.args[2]) .. ':' .. '([^_]*)') or ''
local function errorPrinter(errors)
local result = ""
for i,v in ipairs(errors) do
local errorHTML = '<strong class="error">Coordinates: ' .. v[2] .. '</strong>'
result = result .. errorHTML .. "<br />"
end
return result
end
 
--[[
--[[============================================================================
Determine the required CSS class to display coordinates
Parse attribute variable returning heading field. If heading is a string than
 
try to convert it to an angle
Usually geo-nondefault is hidden by CSS, unless a user has overridden this for himself
==============================================================================]]
default is the mode as specificied by the user when calling the {{coord}} template
function coordinates.getHeading(frame)
mode is the display mode (dec or dms) that we will need to determine the css class for
local attributes
]]
if frame.args[1] then
local function displayDefault(default, mode)
attributes = frame.args[1]
elseifif frame.args.attributesdefault == "" then
default = "dec"
attributes = frame.args.attributes
end
if default == mode then
return "geo-default"
else
return ''"geo-nondefault"
end
local hNum = coordinates._getHeading(attributes)
if hNum == nil then
return ''
end
return tostring(hNum)
end
 
-- Helper core function for getHeading.
--[[
function coordinates._getHeading(attributes)
specPrinter
if attributes == nil then
 
return nil
Output formatter. Takes the structure generated by either parseDec
or parseDMS and formats it for inclusion on Wikipedia.
]]
local function specPrinter(args, coordinateSpec)
local uriComponents = coordinateSpec["param"]
if uriComponents == "" then
-- RETURN error, should never be empty or nil
return "ERROR param was empty"
end
if args["name"] then
local hStr = string.match(mw.text.decode(attributes), 'heading:([^_]*)')
uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec["name"])
if hStr == nil then
return nil
end
local hNum = tonumber( hStr )
local geodmshtml = '<span class="geo-dms" title="Maps, aerial photos, and other data for this location">'
if hNum == nil then
.. '<span class="latitude">' .. coordinateSpec["dms-lat"] .. '</span> '
hStr = string.upper (hStr)
.. '<span class="longitude">' ..coordinateSpec["dms-long"] .. '</span>'
hNum = compass_points[hStr]
.. '</span>'
 
local lat = tonumber( coordinateSpec["dec-lat"] ) or 0
local geodeclat
if lat < 0 then
-- FIXME this breaks the pre-existing precision
geodeclat = tostring(coordinateSpec["dec-lat"]):sub(2) .. "°S"
else
geodeclat = (coordinateSpec["dec-lat"] or 0) .. "°N"
end
if hNum ~= nil then
hNum = hNum%360
end
return hNum
end
 
local long = tonumber( coordinateSpec["dec-long"] ) or 0
--[[============================================================================
local geodeclong
Convert degrees to degrees/minutes/seconds notation comonly used when displaying
if long < 0 then
coordinates.
-- FIXME does not handle unicode minus
Inputs:
geodeclong = tostring(coordinateSpec["dec-long"]):sub(2) .. "°W"
1) latitude or longitude angle in degrees
else
2) georeference precission in degrees
geodeclong = (coordinateSpec["dec-long"] or 0) .. "°E"
3) language used in formating of the number
==============================================================================]]
function coordinates.deg2dms(frame)
local deg = tonumber(frame.args[1])
local degPrec = tonumber(frame.args[2]) or 0-- precision in degrees
local lang
if frame.args.lang and mw.language.isSupportedLanguage(frame.args.lang) then
lang = frame.args.lang
else -- get user's chosen language
lang = mw.message.new( "lang" ):plain()
end
if deg==nil then
local geodechtml = '<span class="geo-dec" title="Maps, aerial photos, and other data for this location">'
return frame.args[1];
.. geodeclat .. ' '
.. geodeclong
.. '</span>'
 
local geonumhtml = '<span class="geo">'
.. coordinateSpec["dec-lat"] .. '; '
.. coordinateSpec["dec-long"]
.. '</span>'
 
local inner = '<span class="' .. displayDefault(coordinateSpec["default"], "dms" ) .. '">' .. geodmshtml .. '</span>'
.. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'
.. '<span class="' .. displayDefault(coordinateSpec["default"], "dec" ) .. '">';
 
if not args["name"] then
inner = inner .. geodechtml
.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span></span>'
else
inner = inner .. '<span class="vcard">' .. geodechtml
return coordinates._deg2dms(deg, degPrec, lang)
.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span>'
.. '<span style="display:none">&#xfeff; (<span class="fn org">'
.. args["name"] .. '</span>)</span></span></span>'
end
 
return '<span class="plainlinks nourlexpansion">' ..
'[' .. coord_link .. uriComponents .. ' ' .. inner .. ']' .. '</span>'
end
 
--[[ Helper function, convert decimal to degrees ]]
--[[============================================================================
local function convert_dec2dms_d(coordinate)
Helper core function for deg2dms. deg2dms can be called by templates, while
local d = math_mod._round( coordinate, 0 ) .. "°"
_deg2dms should be called from Lua.
return d .. ""
Inputs:
end
* 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 coordinates._deg2dms(deg, degPrec, lang)
local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k
local Lang = mw.language.new(lang)
 
--[[ Helper function, convert decimal to degrees and minutes ]]
-- adjust number display based on precission
local function convert_dec2dms_dm(coordinate)
secPrec = degPrec*3600.0 -- coordinate precission in seconds
coordinate = math_mod._round( coordinate * 60, 0 );
if secPrec<0.05 then -- degPrec<1.3889e-05
local m = coordinate % 60;
formatStr = '%s°&nbsp;%s′&nbsp;%s″' -- use DD° MM′ SS.SS″ format
coordinate = math.floor( (coordinate - m) / 60 );
c = 360000
local d = coordinate % 360 .."°"
elseif secPrec<0.5 then -- 1.3889e-05<degPrec<1.3889e-04
formatStr = '%s°&nbsp;%s′&nbsp;%s″' -- use DD° MM′ SS.S″ format
c = 36000
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
return d .. string.format( "%02d′", m )
-- 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
 
--[[ Helper function, convert decimal to degrees, minutes, and seconds ]]
--[[============================================================================
local function convert_dec2dms_dms(coordinate)
Format coordinate location string, by creating and joining DMS strings for
coordinate = math_mod._round( coordinate * 60 * 60, 0 );
latutude and longitude. Also convert precission from meters to degrees.
local s = coordinate % 60
INPUTS:
coordinate = math.floor( (coordinate - s) / 60 );
* lat = latitude in degrees
local m = coordinate % 60
* lon = longitude in degrees
coordinate = math.floor( (coordinate - m) / 60 );
* lang = language code
local d = coordinate % 360 .."°"
* prec = geolocation precission in meters
 
==============================================================================]]
return d .. string.format( "%02d′", m ) .. string.format( "%02d″", s )
function coordinates.lat_lon(frame)
end
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
ifHelper lonfunction, thenconvert --decimal getlatitude or longitude t0 be in -180 to 180 range
degrees, minutes, and seconds format based on the specified precision.
lon=lon%360
]]
if lon>180 then
local function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)
lon = lon-360
local coord = tonumber(coordinate)
end
local postfix
end
if coord >= 0 then
local lang
postfix = firstPostfix
if frame.args.lang and mw.language.isSupportedLanguage(frame.args.lang) then
lang = frame.args.lang
else -- get user's chosen language
lang = mw.message.new( "lang" ):plain()
end
if lat==nil or lon==nil then
return NoLatLonString
else
postfix = secondPostfix
local nsew = Fallback._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 = coordinates._deg2dms(math.abs(lat), prec/lat2m, lang) -- Convert latitude degrees to degrees/minutes/seconds
lonStr = coordinates._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
 
precision = precision:lower();
--[[============================================================================
if precision == "dms" then
Create URL for different sites.
return convert_dec2dms_dms( math.abs( coord ) ) .. postfix;
INPUTS:
elseif precision == "dm" then
* site = Possinle sites: GeoHack, GoogleEarth, Proximityrama,
return convert_dec2dms_dm( math.abs( coord ) ) .. postfix;
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
elseif precision == "d" then
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
return convert_dec2dms_d( math.abs( coord ) ) .. postfix;
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 coordinates.externalLink(frame)
args = frame.args
if args.lang and mw.language.isSupportedLanguage(args.lang) then
lang = args.lang
else -- get user's chosen language
lang = mw.message.new( "lang" ):plain()
end
local str = coordinates._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, lang, args.attributes or '')
return frame:preprocess(str)
end
 
--[[
--[[============================================================================
Convert DMS format into a N or E decimal coordinate
Helper core function for externalLink. Create URL for different sites:
]]
INPUTS:
local function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str)
* site = Possinle sites: GeoHack, GoogleEarth, Proximityrama,
local degrees = tonumber(degrees_str)
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
local minutes = tonumber(minutes_str) or 0
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
local seconds = tonumber(seconds_str) or 0
Ganymede are also supported but are unused as of 2013.
* latStr = latitude string or number
local factor = 1
* lonStr = longitude string or number
if direction == "S" or direction == "W" then
* lang = language code
factor = -1
* attributes = attributes to be passed to GeoHack
==============================================================================]]
function coordinates._externalLink(site, globe, latStr, lonStr, lang, attributes)
local URLstr = SiteURL[site];
if site == 'GoogleMaps' then
URLstr = SiteURL.GoogleMaps[globe]
elseif site == 'GeoHack' then
attributes = string.format('globe:%s_%s', globe, attributes)
local pageName = mw.uri.encode( mw.title.getCurrentTitle().prefixedText, 'WIKI' )
pageName = mw.ustring.gsub( pageName, '%%', '%%%%')
URLstr = mw.ustring.gsub( URLstr, '$attr', attributes)
URLstr = mw.ustring.gsub( URLstr, '$page', pageName)
URLstr = mw.ustring.gsub( URLstr, ' ', '_')
end
URLstr = mw.ustring.gsub( URLstr, '$lat' , latStr)
local precision = 0
URLstr = mw.ustring.gsub( URLstr, '$lon' , lonStr)
if seconds_str then
URLstr = mw.ustring.gsub( URLstr, '$lang', lang)
precision = 5 + math.max( math_mod._precision(seconds_str), 0 );
return URLstr
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
local decimal = factor * (degrees+(minutes+seconds/60)/60)
return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based.
end
 
--[[
--[[============================================================================
Checks input values to for out of range errors.
Adjust GeoHack attributes depending on the template that calls it
]]
INPUTS:
local function validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, source, strong )
* attributes = attributes to be passed to GeoHack
local errors = {};
* mode = set by each calling template
lat_d = tonumber( lat_d ) or 0;
==============================================================================]]
lat_m = tonumber( lat_m ) or 0;
function coordinates.alterAttributes(attributes, mode)
lat_s = tonumber( lat_s ) or 0;
-- indicate which template called it
long_d = tonumber( long_d ) or 0;
if mode=='camera' then -- Used by {{Location}} and {{Location dec}}
long_m = tonumber( long_m ) or 0;
if string.find(attributes, 'type:camera')==nil then
long_s = tonumber( long_s ) or 0;
attributes = 'type:camera_' .. attributes
 
if strong then
if lat_d < 0 then
table.insert(errors, {source, "latitude degrees < 0 with hemisphere flag"})
end
if long_d < 0 then
elseif mode=='object'or mode =='globe' then -- Used by {{Object location}}
table.insert(errors, {source, "longitude degrees < 0 with hemisphere flag"})
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)
#coordinates is inconsistent about whether this is an error. If globe: is
elseif mode=='user' then -- Used by {{User location}}
specified, it won't error on this condition, but otherwise it will.
attributes = 'type:user_location'
elseif mode=='institution' then --Used by {{Institution/coordinates}} (categories only)
For not simply disable this check.
attributes = 'type:institution'
if long_d > 180 then
table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"})
end
]]
end
if lat_d > 90 then
table.insert(errors, {source, "latitude degrees > 90"})
end
if lat_d < -90 then
return attributes
table.insert(errors, {source, "latitude degrees < -90"})
end
if lat_m >= 60 then
table.insert(errors, {source, "latitude minutes >= 60"})
end
if lat_m < 0 then
table.insert(errors, {source, "latitude minutes < 0"})
end
if lat_s >= 60 then
table.insert(errors, {source, "latitude seconds >= 60"})
end
if lat_s < 0 then
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
if long_m < 0 then
table.insert(errors, {source, "longitude minutes < 0"})
end
if long_s >= 60 then
table.insert(errors, {source, "longitude seconds >= 60"})
end
if long_s < 0 then
table.insert(errors, {source, "longitude seconds < 0"})
end
return errors;
end
 
--[[
parseDec
 
Transforms decimal format latitude and longitude into the a
structure to be used in displaying coordinates
]]
local function parseDec( lat, long, format )
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
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}}}}}}}
coordinateSpec["dms-long"] = convert_dec2dms( long, "E", "W", mode) -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}
if format then
--[[============================================================================
coordinateSpec.default = format
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 coordinates.GeoHack_link(frame)
-- create link and coordintate string
local latlon = coordinates.lat_lon(frame)
if latlon==NoLatLonString then
return latlon
else
coordinateSpec.default = "dec"
frame.args.site = 'GeoHack'
local url = coordinates.externalLink(frame)
return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion">
end
 
return coordinateSpec, errors
end
 
--[[
--[[============================================================================
parseDMS
Create full external links section of {{Location}} or {{Object location}}
 
templates, based on:
Transforms degrees, minutes, seconds format latitude and longitude
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
into the a structure to be used in displaying coordinates
* mode = Possible options:
]]
- camera - call from {{location}}
local function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format )
- object - call from {{Object location}}
local coordinateSpec = {}
- globe - call from {{Globe location}}
local errors = {}
* lat = latitude in degrees
* lon = longitude in degrees
* lang = language code
* namespace = namespace name: File, Category, (Gallery)
==============================================================================]]
function coordinates.externalLinksSection(frame)
args = frame.args
if args.lang and mw.language.isSupportedLanguage(args.lang) then
lang = args.lang
else -- get user's chosen language
lang = mw.message.new( "lang" ):plain()
end
if not args.namespace then
args.namespace = mw.title.getCurrentTitle().namespace
end
lat_f = lat_f:upper();
local str
long_f = long_f:upper();
if args.globe=='Earth' then -- Earth locations will have 3 or 4 links
str = string.format('[%s %s] - [%s %s]', -- - [%s %s]',
-- Check if specified backward
coordinates._externalLink('OpenStreetMap', 'Earth', args.lat, args.lon, lang, ''),
if lat_f == 'E' or lat_f == 'W' then
Fallback._langSwitch(i18n.OpenStreetMaps, lang),
local t_d, t_m, t_s, t_f;
-- GoogleMap link no longer works due to changes in the website software
t_d = lat_d;
--coordinates._externalLink('GoogleMaps' , 'Earth', args.lat, args.lon, lang, ''),
t_m = lat_m;
--Fallback._langSwitch(i18n.GoogleMaps, lang),
t_s = lat_s;
coordinates._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, ''),
t_f = lat_f;
Fallback._langSwitch(i18n.GoogleEarth, lang))
lat_d = long_d;
if args.namespace=="Category" then
lat_m = long_m;
str = string.format('%s - [%s %s]', str,
lat_s = long_s;
coordinates._externalLink('Proximityrama', 'Earth', args.lat, args.lon, lang, ''),
lat_f = long_f;
Fallback._langSwitch(i18n.Proximityrama, lang))
long_d = t_d;
end
long_m = t_m;
elseif args.globe=='Mars' or args.globe=='Moon' then
long_s = t_s;
str = string.format('[%s %s]',
long_f = t_f;
coordinates._externalLink('GoogleMaps', args.globe, args.lat, args.lon, lang, ''),
end
Fallback._langSwitch(i18n.GoogleMaps, lang))
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
if not lat_m and not lat_s and not long_m and not long_s and #errors == 0 then
return frame:preprocess(str) -- use preprocess to expand {{#fullurl}}
if math_mod._precision( lat_d ) > 0 or math_mod._precision( long_d ) > 0 then
--return str
if lat_f:upper() == 'S' then
lat_d = '-' .. lat_d;
end
if long_f:upper() == 'W' then
long_d = '-' .. long_d;
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
else
coordinateSpec.default = "dms"
end
 
return coordinateSpec, errors
end
 
--[[
--[[============================================================================
Check the input arguments for coord to determine the kind of data being provided
Core section of template:Location, template:Object location and template:Globe location.
and then make the necessary processing.
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.
local function formatTest(args)
* mode = Possible options:
local result, errors
- camera - call from {{location}}
local primary = false
- object - call from {{Object location}}
 
- globe - call from {{Globe location}}
local function getParam(args, lim)
* lat = latitude in degrees
local ret = {}
* lon = longitude in degrees
for i = 1, lim do
* attributes = attributes
ret[i] = args[i] or ''
* lang = language code
end
* namespace = namespace: File, Category, Gallery
return table.concat(ret, '_')
* prec = geolocation precission in meters
==============================================================================]]
function coordinates.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
if not args[1] then
args.lang = mw.message.new( "lang" ):plain() -- get user's chosen language
-- no lat logic
end
return errorPrinter( {{"formatTest", "Missing latitude"}} )
if not (args.namespace) then -- if namespace not provided than look it up
elseif not tonumber(args[1]) then
args.namespace = mw.title.getCurrentTitle().namespace
-- 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
result.param = table.concat({args[1], 'N', args[2] or '', 'E', args[3] or ''}, '_')
elseif dmsTest(args[4], args[8]) then
-- dms logic
result, errors = 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 = 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
if not result then
return errorPrinter(errors)
end
result.param = getParam(args, 7)
elseif dmsTest(args[2], args[4]) then
-- d logic
result, errors = 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
result.param = getParam(args, 5)
else
-- Error
return errorPrinter({{"formatTest", "Unknown argument format"}})
end
result.name = args.name
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 = coordinates.alterAttributes(args.attributes or '', args.mode)
frame.args = args
local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type'}
-- check for errors and add Geo (microformat) code for machine readability.
for _, v in ipairs(extra_param) do
local lat = tonumber(args.lat)
if args[v] then
local lon = tonumber(args.lon)
table.insert(errors, {'formatTest', 'Parameter: "' .. v .. '=" should be "' .. v .. ':"' })
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 Categories, geoMicroFormat, coorTag = '', '', ''
local ret = specPrinter(args, result)
if #errors > 0 then
ret = ret .. ' ' .. errorPrinter(errors) .. '[[Category:Pages with malformed coordinate tags]]'
end
return ret
end
 
--[[
-- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages
Generate Wikidata tracking categories.
if (args.namespace == 'File' or args.namespace == 'Category' or args.namespace == 'Gallery') then
]]
if lat and lon then -- if lat and lon are numbers...
local function makeWikidataCategories()
if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources
local ret
Categories = CoorCat.default
if mw.wikibase and current_page.namespace == 0 then
end
local entity = mw.wikibase.getEntityObject()
if args.noError==0 or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors )
if entity and entity.claims and entity.claims.P625 and entity.claims.P625[1] then
Categories = Categories .. CoorCat.erroneous
local snaktype = entity.claims.P625[1].mainsnak.snaktype
end
if snaktype == 'value' then
local cat = CoorCat[args.namespace]
-- coordinates exist both here and on Wikidata, and can be compared.
if cat then -- add category based on namespace
Categoriesret = Categories'Coordinates ..on catWikidata'
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
-- if not earth than add a category for each globe
-- We have to either import the coordinates to Wikidata or remove them here.
if args.mode and args.globe and args.mode=='globe' and args.globe~='Earth' then
ret = 'Coordinates not on Wikidata'
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)
end
else -- if lat and lon are not numbers then add error category
Categories = Categories .. CoorCat.erroneous
end
 
end
if ret then
 
return string.format('[[Category:%s]]', ret)
-- Call helper functions to render different parts of the template
else
local str1, str2, str3, str4, inner_table, heading
return ''
str1 = coordinates.GeoHack_link(frame) -- the coordinates and link to GeoHack
heading = coordinates._getHeading(frame.args.attributes) -- get heading arrow section
if heading then
--str1 = string.format('%s&nbsp;&nbsp;<span style="{{Transform-rotate|%f}}">[[File:North Pointer.svg|20px|link=|alt=]]</span>', str1, 360-heading)
local fname = string.format('{{Compass rose file|%f|style=heading}}', heading)
str1 = string.format('%s&nbsp;&nbsp;<span title="%s°">[[%s|25px|link=|alt=%s°]]</span>', str1, heading, fname, heading)
end
end
str2 = Fallback._langSwitch(i18n.LocationTemplateLinkLabel, args.lang) -- header of the link section
 
str3 = coordinates.externalLinksSection(frame) or '' -- external link section
--[[
str4 = string.format('[[File:Circle-information.svg|18x18px|alt=info|link=%s]]', Fallback._langSwitch(i18n.COM_GEO, args.lang) )
link
inner_table = string.format('<td style="border:none;">%s</td><td style="border:none;">%s %s</td><td style="border:none;">%s%s</td>', str1, str2, str3, str4, geoMicroFormat)
 
Simple function to export the coordinates link for other uses.
 
Usage:
{{ Invoke:Coordinates | link }}
]]
-- combine strings into a table
function coordinates.link(frame)
local templateText
return coord_link;
if bare then
end
templateText = string.format('<table style="width:100%%"><tr>%s</tr></table>', inner_table)
 
--[[
dec2dms
 
Wrapper to allow templates to call dec2dms directly.
 
Usage:
{{ Invoke:Coordinates | dec2dms | decimal_coordinate | positive_suffix |
negative_suffix | precision }}
decimal_coordinate is converted to DMS format. If positive, the positive_suffix
is appended (typical N or E), if negative, the negative suffix is appended. The
specified precision is one of 'D', 'DM', or 'DMS' to specify the level of detail
to use.
]]
coordinates.dec2dms = makeInvokeFunc('_dec2dms')
function coordinates._dec2dms(args)
local coordinate = args[1]
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';
-- choose name of the field
end
local field_name = 'Location'
end
if args.mode=='camera' then
 
field_name = Fallback._langSwitch(i18n.CameraLocation, args.lang)
--[[
elseif args.mode=='object' then
dms2dec
field_name = Fallback._langSwitch(i18n.ObjectLocation, args.lang)
 
elseif args.mode=='globe' then
Wrapper to allow templates to call dms2dec directly.
field_list = Fallback._langSwitch(i18n.GlobeLocation, args.lang)
 
if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized
Usage:
field_name = field_list[args.globe]
{{ Invoke:Coordinates | dms2dec | direction_flag | degrees |
minutes | seconds }}
Converts DMS values specified as degrees, minutes, seconds too decimal format.
direction_flag is one of N, S, E, W, and determines whether the output is
positive (i.e. N and E) or negative (i.e. S and W).
]]
coordinates.dms2dec = makeInvokeFunc('_dms2dec')
function coordinates._dms2dec(args)
local direction = args[1]
local degrees = args[2]
local minutes = args[3]
local seconds = args[4]
 
return convert_dms2dec(direction, degrees, minutes, seconds)
end
 
--[[
coord
 
Main entry point for Lua function to replace {{coord}}
 
Usage:
{{ Invoke:Coordinates | coord }}
{{ Invoke:Coordinates | coord | lat | long }}
{{ Invoke:Coordinates | coord | lat | lat_flag | long | long_flag }}
...
Refer to {{coord}} documentation page for many additional parameters and
configuration options.
Note: This function provides the visual display elements of {{coord}}. In
order to load coordinates into the database, the {{#coordinates:}} parser
function must also be called, this is done automatically in the Lua
version of {{coord}}.
]]
coordinates.coord = makeInvokeFunc('_coord')
function coordinates._coord(args)
if not args[1] and not args[2] and mw.wikibase.getEntityObject() then
local entity = mw.wikibase.getEntityObject()
if entity
and entity.claims
and entity.claims.P625
and entity.claims.P625[1].mainsnak.snaktype == 'value'
then
local precision = entity.claims.P625[1].mainsnak.datavalue.value.precision
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
local style = frame:expandTemplate{ title="Infobar-Layout", args={ ["lang"] = lang, ["class"] = 'commons-file-information-table' } }
templateText = string.format('<table lang="%s" %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', lang, style, field_name, inner_table)
end
return frame:preprocess(templateText .. Categories .. coorTag)
local contents = formatTest(args)
local Notes = args.notes or ''
local Display = args.display and args.display:lower() or 'inline'
 
local function isInline(s)
-- Finds whether coordinates are displayed inline.
return s:find('inline') ~= nil or s == 'i' or s == 'it' or s == 'ti'
end
local function isInTitle(s)
-- Finds whether coordinates are displayed in the title.
return s:find('title') ~= nil or s == 't' or s == 'it' or s == 'ti'
end
local text = ''
if isInline(Display) then
text = text .. displayinline(contents, Notes)
end
if isInTitle(Display) then
text = text
.. displaytitle(contents, Notes)
.. makeWikidataCategories()
end
return text
end