|
|
Line 1: |
Line 1: |
| --[[ | | --[[ |
| __ __ _ _ ____ _ _ _
| | This module is intended to replace the functionality of {{Coord}} and related |
| | \/ | ___ __| |_ _| | ___ _ / ___|___ ___ _ __ __| (_)_ __ __ _| |_ ___ ___
| | templates. It provides several methods, including |
| | |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \ / _ \| '__/ _` | | '_ \ / _` | __/ _ \/ __|
| |
| | | | | (_) | (_| | |_| | | __/_| |__| (_) | (_) | | | (_| | | | | | (_| | || __/\__ \
| |
| |_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/ \___/|_| \__,_|_|_| |_|\__,_|\__\___||___/ | |
|
| |
|
| |
|
| This module is intended to provide functionality of {{location}} and related
| | {{#invoke:Coordinates | coord }} : General function formatting and displaying |
| templates. It was developed on Wikimedia Commons, so if you find this code on
| | coordinate values. |
| other sites, check there for updates and discussions.
| |
|
| |
|
| Please do not modify this code without applying the changes first at Module:Coordinates/sandbox and testing
| | {{#invoke:Coordinates | dec2dms }} : Simple function for converting decimal |
| at Module:Coordinates/sandbox/testcases and Module talk:Coordinates/sandbox/testcases.
| | degree values to DMS format. |
|
| |
|
| Authors and maintainers:
| | {{#invoke:Coordinates | dms2dec }} : Simple function for converting DMS format |
| * User:Jarekt
| | to decimal degree format. |
| * User:Ebraminio
| |
|
| |
|
| Functions:
| | {{#invoke:Coordinates | link }} : Export the link used to reach the tools |
| *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('strict') |
| -- === 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 core = require('Module:Core')
| |
|
| |
|
| -- =======================================
| | local math_mod = require("Module:Math") |
| -- === Hardwired parameters ==============
| | local coordinates = {}; |
| -- =======================================
| |
|
| |
|
| -- ===========================================================
| | 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 = '//geohack.toolforge.org/geohack.php?pagename=' .. page_name .. '¶ms=' |
| N = 0,
| | local templatestyles = 'Module:Coordinates/styles.css' |
| 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 = '//geohack.toolforge.org/geohack.php?pagename=$page¶ms=$lat_N_$lon_E_$attr&language=$lang',
| |
| --GoogleEarth = '//geocommons.toolforge.org/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 = '//wiwosm.toolforge.org/osm-on-ol/commons-on-osm.php?zoom=16&lat=$lat&lon=$lon',
| |
| OpenStreetMap1 = '//wikimap.toolforge.org/?wp=false&basemap=2&cluster=false&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() |
| strucData0 = '[[Category:Pages with %s coordinates from %s]]',
| | return s:find('^[NS][EW]$') or s:find('^[EW][NS]$') |
| strucData1 = '[[Category:Pages with local %s coordinates and matching %s coordinates]]', | | end |
| strucData2 = '[[Category:Pages with local %s coordinates and similar %s coordinates]]',
| |
| strucData3 = '[[Category:Pages with local %s coordinates and mismatching %s coordinates]]',
| |
| strucData4 = '[[Category:Pages with local %s coordinates and missing %s coordinates]]', | |
| sHeading3 = '[[Category:Pages with local %s heading and mismatching %s heading]]', | |
| sHeading4 = '[[Category:Pages with local %s heading and missing %s heading]]',
| |
| sHeading5 = '[[Category:Pages with local %s heading:0 and missing %s heading]]', | |
| 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]]',
| |
| dms = '[[Category:Media with coordinates in DMS format]]'
| |
| }
| |
|
| |
|
| local globeLUT = { Q2='Earth', Q111='Mars', Q405='Moon'}
| |
| local NoLatLonString = 'latitude, longitude'
| |
|
| |
|
| -- ======================================= | | --[[ Wrapper function to grab args, see Module:Arguments for this function's documentation. ]] |
| -- === Local Functions ===================
| | local function makeInvokeFunc(funcName) |
| -- =======================================
| | return function (frame) |
| | | local args = require('Module:Arguments').getArgs(frame, { |
| -- ===========================================================
| | wrappers = 'Template:Coord' |
| local function add_maplink(lat, lon, marker, text) | | }) |
| local tstr = '' | | return coordinates[funcName](args, frame) |
| if text then
| |
| tstr = string.format('text="%s" ', text) | |
| 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 |
|
| |
|
| -- =========================================================== | | --[[ Helper function, handle optional args. ]] |
| local function add_maplink2(lat1, lon1, lat2, lon2) | | local function optionalArg(arg, supplement) |
| return string.format('<maplink zoom="13" latitude="%f" longitude="%f" class="no-icon">[{'.. | | return arg and arg .. supplement or '' |
| ' "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 | | end |
|
| |
|
| -- =========================================================== | | --[[ |
| local function info_box(text) | | Formats any error messages generated for display |
| 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 errorPrinter(errors) |
| '<tr>'..
| | local result = "" |
| '<td class="mbox-image" style="padding-left:.9em;">'.. | | for i,v in ipairs(errors) do |
| ' [[File:Commons-emblem-issue.svg|class=noviewer|45px]]</td>'..
| | local errorHTML = '<strong class="error">Coordinates: ' .. v[2] .. '</strong>' |
| '<td class="mbox-text" style="">%s</td>'.. | | result = result .. errorHTML .. "<br />" |
| '</tr></table>', text)
| | end |
| | return result |
| end | | end |
|
| |
|
| -- =========================================================== | | --[[ |
| local function distance(lat1, lon1, lat2, lon2)
| | Determine the required CSS class to display coordinates |
| -- calculate distance
| |
| local dLat = math.rad(lat1-lat2)
| |
| local dLon = math.rad(lon1-lon2)
| |
| local d = math.pow(math.sin(dLat/2),2) + math.pow(math.sin(dLon/2),2) * math.cos(math.rad(lat1)) * math.cos(math.rad(lat2))
| |
| d = 2 * math.atan2(math.sqrt(d), math.sqrt(1-d)) -- angular distance in radians
| |
| d = 6371000 * d -- radians to meters conversion
| |
| d = math.floor(d+0.5) -- round it to even meters
| |
| return d
| |
| end
| |
|
| |
|
| -- =========================================================== | | Usually geo-nondefault is hidden by CSS, unless a user has overridden this for himself |
| local function getSDCoords(entity, prop)
| | default is the mode as specificied by the user when calling the {{coord}} template |
| -- get coordinates from structured data (either wikidata or SDC)
| | mode is the display mode (dec or dms) that we will need to determine the css class for |
| local coords = {id=entity.id, source=prop}
| | ]] |
| if not entity or not entity.claims or not entity.claims[prop]then | | local function displayDefault(default, mode) |
| return coords | | if default == "" then |
| | default = "dec" |
| end | | end |
| for _, statement in pairs( entity:getBestStatements( prop )) do | | |
| local v = statement.mainsnak.datavalue.value -- get coordinates
| | if default == mode then |
| if v.latitude then
| | return "geo-default" |
| coords.lat = v.latitude
| | else |
| coords.lon = v.longitude
| | return "geo-nondefault" |
| coords.prec = v.precision or 1e-4
| |
| coords.prec = math.floor(coords.prec*111000) -- convert precision from degrees to meters and round
| |
| coords.prec = math.max(math.min(coords.prec,111000),5) -- bound precision to a number between 5 meters and 1 degree
| |
| coords.globe = string.gsub(v.globe, 'http://www.wikidata.org/entity/','')
| |
| coords.globe = globeLUT[coords.globe]
| |
| if statement.qualifiers and statement.qualifiers.P7787 then
| |
| v = statement.qualifiers.P7787[1].datavalue.value
| |
| if v.unit == "http://www.wikidata.org/entity/Q28390" then -- in degrees
| |
| coords.heading = v.amount
| |
| elseif v.unit == "http://www.wikidata.org/entity/Q33680" then -- in radians
| |
| coords.heading = v.amount*57.2957795131
| |
| end
| |
| end
| |
| return coords
| |
| end
| |
| end | | end |
| return coords
| |
| end | | end |
|
| |
|
| -- =========================================================== | | --[[ |
| local function compareCoords(loc, sd, mode, source) | | specPrinter |
| -- compare coordinates
| | |
| --INPUTS:
| | Output formatter. Takes the structure generated by either parseDec |
| -- * loc - local coordinates | | or parseDMS and formats it for inclusion on Wikipedia. |
| -- * sd - structured data coords
| | ]] |
| local coord = loc | | local function specPrinter(args, coordinateSpec) |
| local cat, dist_str = '', '' | | local uriComponents = coordinateSpec["param"] |
| local case, dist, qs, mapLink, message
| | if uriComponents == "" then |
| dist=0 | | -- RETURN error, should never be empty or nil |
| | return "ERROR param was empty" |
| | end |
| | if args["name"] then |
| | uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec["name"]) |
| | end |
|
| |
|
| if not loc.lat or not loc.lon then -- structured data/wikidata coordinates only | | local geodmshtml = '<span class="geo-dms" title="Maps, aerial photos, and other data for this location">' |
| coord = sd
| | .. '<span class="latitude">' .. coordinateSpec["dms-lat"] .. '</span> ' |
| cat = string.format(CoorCat.strucData0, mode, source)
| | .. '<span class="longitude">' ..coordinateSpec["dms-long"] .. '</span>' |
| case = 0
| | .. '</span>' |
| elseif loc.lat and loc.lon and not sd.lat and not sd.lon then
| |
| cat = string.format(CoorCat.strucData4, mode, source)
| |
| case = 4 -- local coordinates only
| |
| elseif loc.lat and loc.lon and sd.lat and sd.lon then
| |
| dist = distance(loc.lat, loc.lon, sd.lat, sd.lon) -- calculate distance
| |
| dist_str = string.format(' (discrepancy of %i meters between the above coordinates and the ones stored on Wikidata)', dist) -- will be displayed when hovering a mouse above wikidata icon
| |
|
| |
|
| if dist<20 or dist<sd.prec then -- will consider location within 20 meters or precision distance as the same
| | local lat = tonumber( coordinateSpec["dec-lat"] ) or 0 |
| if source=='Wikidata' then
| | local geodeclat |
| cat = string.format(CoorCat.strucData1, mode, source)
| | if lat < 0 then |
| end
| | -- FIXME this breaks the pre-existing precision |
| case = 1
| | geodeclat = tostring(coordinateSpec["dec-lat"]):sub(2) .. "°S" |
| elseif (dist<1000 or dist<5*sd.prec) and mode=='object' then
| | else |
| --cat = string.format(CoorCat.strucData2, mode, source)
| | geodeclat = (coordinateSpec["dec-lat"] or 0) .. "°N" |
| case = 2
| |
| else -- locations 1 km off and 5 precision distances away are likely wrong. The issue might be with wrong precission
| |
| mapLink = mw.getCurrentFrame():preprocess(add_maplink2(loc.lat, loc.lon, sd.lat, sd.lon)) -- fancy link to OSM
| |
| message = string.format("There is a discrepancy of %i meters between the above coordinates and the ones stored at %s (%s, precision: %i m). Please [[Commons:Structured data/Reconciliation|reconcile them]]. ",
| |
| dist, source, mapLink, sd.prec)
| |
| cat = string.format(CoorCat.strucData3, mode, source) .. info_box(message)
| |
| case = 3
| |
| end
| |
| end | | end |
| if not loc.heading and sd.heading then -- structured data/wikidata heading only
| | |
| coord.heading = sd.heading
| | local long = tonumber( coordinateSpec["dec-long"] ) or 0 |
| elseif loc.heading==0 and not sd.heading and sd.lat and sd.lon then -- local heading only | | local geodeclong |
| cat = cat .. string.format(CoorCat.sHeading5, mode, source)
| | if long < 0 then |
| elseif loc.heading and not sd.heading and sd.lat and sd.lon then -- local heading only | | -- FIXME does not handle unicode minus |
| cat = cat .. string.format(CoorCat.sHeading4, mode, source)
| | geodeclong = tostring(coordinateSpec["dec-long"]):sub(2) .. "°W" |
| elseif loc.heading and sd.heading then | | else |
| local dh = math.abs(math.fmod(loc.heading,360) - math.fmod(sd.heading,360)) | | geodeclong = (coordinateSpec["dec-long"] or 0) .. "°E" |
| if dh>1 and dh<359 then | |
| message = string.format("There is a discrepancy of %i degrees between the above camera heading (set to %i) and the ones stored at %s (set to %i). Please [[Commons:Structured data/Reconciliation|reconcile them]]. ", dh, loc.heading, source, sd.heading)
| |
| cat = cat .. string.format(CoorCat.sHeading3, mode, source) .. info_box(message)
| |
| end
| |
| end | | end |
| if source=='Wikidata' and case>=3 then | | |
| local url = mw.title.getCurrentTitle():canonicalUrl()
| | local geodechtml = '<span class="geo-dec" title="Maps, aerial photos, and other data for this location">' |
| local today = '+' .. os.date('!%F') .. 'T00:00:00Z/11' -- today's date in QS format
| | .. geodeclat .. ' ' |
| qs = string.format('%s|P625|@%09.5f/%09.5f|S143|Q565|S813|%s|S4656|"%s"', sd.wID, loc.lat, loc.lon, today, url)
| | .. geodeclong |
| qs = string.gsub (mw.uri.encode(qs),'%%2520','%%20') | | .. '</span>' |
| qs = 'https://quickstatements.toolforge.org/#/v1=' .. qs -- create full URL link | | |
| qs = string.format("[[File:Commons_to_Wikidata_QuickStatements.svg|15px|link=%s|Copy geo coordinates to Wikidata]]", qs)
| | 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"> / </span>' |
| | .. '<span class="' .. displayDefault(coordinateSpec["default"], "dec" ) .. '">'; |
| | |
| | if not args["name"] then |
| | inner = inner .. geodechtml |
| | .. '<span style="display:none"> / ' .. geonumhtml .. '</span></span>' |
| | else |
| | inner = inner .. '<span class="vcard">' .. geodechtml |
| | .. '<span style="display:none"> / ' .. geonumhtml .. '</span>' |
| | .. '<span style="display:none"> (<span class="fn org">' |
| | .. args["name"] .. '</span>)</span></span></span>' |
| end | | end |
| local ret = {dist_str=dist_str, case=case, qs=qs } | | |
| return coord, cat, ret
| | return mw.getCurrentFrame():extensionTag{ name = 'templatestyles', args = { src = templatestyles} } |
| | .. '<span class="plainlinks nourlexpansion">' |
| | .. '[' .. coord_link .. uriComponents .. ' ' .. inner .. ']' |
| | .. '</span>' |
| end | | end |
|
| |
|
| -- =========================================================== | | --[[ Helper function, convert decimal to degrees ]] |
| local function dms2deg_ ( d, m, s, h ) | | local function convert_dec2dms_d(coordinate) |
| d,m,s = tonumber(d), tonumber(m), tonumber(s)
| | local d = math_mod._round( coordinate, 0 ) .. "°" |
| if not (d and m and s and h) then
| | return d .. "" |
| return nil
| |
| end
| |
| local LUT = {N=1, S=-1, E=1, W=-1} -- look up table
| |
| h = LUT[mw.ustring.upper( h )]
| |
| if not h then
| |
| return nil
| |
| end
| |
| return h * (d + m/60.0 + s/3600.0) | |
| end | | end |
|
| |
|
| -- =========================================================== | | --[[ Helper function, convert decimal to degrees and minutes ]] |
| local function dms2deg ( dms ) | | local function convert_dec2dms_dm(coordinate) |
| local ltab = mw.text.split(dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", ""), "/")
| | coordinate = math_mod._round( coordinate * 60, 0 ); |
| local degre = dms2deg_ (ltab[1], ltab[2], ltab[3], ltab[4])
| | local m = coordinate % 60; |
| --return dms .. '->' .. dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", "") .. '->' .. (degre or 'nil') | | coordinate = math.floor( (coordinate - m) / 60 ); |
| return degre or dms
| | local d = coordinate % 360 .."°" |
| | |
| | return d .. string.format( "%02d′", m ) |
| end | | end |
|
| |
|
| -- ======================================= | | --[[ Helper function, convert decimal to degrees, minutes, and seconds ]] |
| -- === External Functions ================
| | local function convert_dec2dms_dms(coordinate) |
| -- ======================================= | | coordinate = math_mod._round( coordinate * 60 * 60, 0 ); |
| local p = {} | | local s = coordinate % 60 |
| p.debug = 'nothing'
| | coordinate = math.floor( (coordinate - s) / 60 ); |
| | local m = coordinate % 60 |
| | coordinate = math.floor( (coordinate - m) / 60 ); |
| | local d = coordinate % 360 .."°" |
|
| |
|
| -- parse attribute variable returning desired field (used for debugging)
| | return d .. string.format( "%02d′", m ) .. string.format( "%02d″", s ) |
| 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) |
| | local coord = tonumber(coordinate) |
| | local postfix |
| | if coord >= 0 then |
| | postfix = firstPostfix |
| | else |
| | postfix = secondPostfix |
| end | | end |
| local hStr = string.match(mw.text.decode(attributes), 'heading:([^_]*)') | | |
| if hStr == nil then | | precision = precision:lower(); |
| return nil | | 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 |
| local hNum = tonumber( hStr )
| |
| if hNum == nil then
| |
| hStr = string.upper (hStr)
| |
| hNum = compass_points[hStr]
| |
| end
| |
| if hNum then
| |
| hNum = hNum%360
| |
| end
| |
| return hNum
| |
| end | | end |
|
| |
|
| --[[============================================================================ | | --[[ |
| Parse attribute variable returning heading field. If heading is a string than
| | Convert DMS format into a N or E decimal coordinate |
| 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 |
| | local seconds = tonumber(seconds_str) or 0 |
|
| |
|
| function p.getHeading(frame)
| | local factor = 1 |
| local attributes | | if direction == "S" or direction == "W" then |
| if frame.args[1] then | | factor = -1 |
| attributes = frame.args[1] | | end |
| elseif frame.args.attributes then | | |
| attributes = frame.args.attributes | | local precision = 0 |
| | if seconds_str then |
| | 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 | | else |
| return '' | | precision = math.max( math_mod._precision(degrees_str), 0 ); |
| end | | end |
| local hNum = p._getHeading(attributes) | | |
| if hNum == nil then | | local decimal = factor * (degrees+(minutes+seconds/60)/60) |
| return ''
| | return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based. |
| end
| |
| return tostring(hNum)
| |
| 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 |
| Helper core function for deg2dms. deg2dms can be called by templates, while
| | if lat_d < 0 then |
| _deg2dms should be called from Lua.
| | table.insert(errors, {source, "latitude degrees < 0 with hemisphere flag"}) |
| Inputs:
| | end |
| * degree - positive coordinate in degrees
| | if long_d < 0 then |
| * degPrec - coordinate precision in degrees will result in different angle format
| | table.insert(errors, {source, "longitude degrees < 0 with hemisphere flag"}) |
| * lang - language to used when formatting the number
| | end |
| ==============================================================================]]
| | --[[ |
| function p._deg2dms(degree, degPrec, lang)
| | #coordinates is inconsistent about whether this is an error. If globe: is |
| local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k, d, zero
| | specified, it won't error on this condition, but otherwise it will. |
| local Lang = mw.language.new(lang)
| |
|
| |
|
| -- adjust number display based on precision | | For not simply disable this check. |
| secPrec = degPrec*3600.0 -- coordinate precision in seconds | | |
| if secPrec<0.05 then -- degPrec<1.3889e-05 | | if long_d > 180 then |
| formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.SS″ format
| | table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"}) |
| c = 360000 | | end |
| elseif secPrec<0.5 then -- 1.3889e-05<degPrec<1.3889e-04 | | ]] |
| formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.S″ format
| | end |
| c = 36000 | | |
| elseif degPrec*60.0<0.5 then -- 1.3889e-04<degPrec<0.0083 | | if lat_d > 90 then |
| formatStr = '%s° %s′ %s″' -- use DD° MM′ SS″ format
| | table.insert(errors, {source, "latitude degrees > 90"}) |
| c = 3600 | | end |
| elseif degPrec<0.5 then -- 0.0083<degPrec<0.5 | | if lat_d < -90 then |
| formatStr = '%s° %s′' -- use DD° MM′ format
| | table.insert(errors, {source, "latitude degrees < -90"}) |
| c = 60 | | end |
| else -- if degPrec>0.5 then | | if lat_m >= 60 then |
| formatStr = '%s°' -- use DD° format | | table.insert(errors, {source, "latitude minutes >= 60"}) |
| c = 1
| | 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 | | end |
| | | if long_s >= 60 then |
| -- create degree, minute and seconds numbers and string
| | table.insert(errors, {source, "longitude seconds >= 60"}) |
| d = c/60
| |
| k = math.floor(c*(degree%360)+0.49) -- convert float to an integer. This step HAS to be identical for all conversions to avoid incorrect results due to different rounding
| |
| dNum = math.floor(k/c) % 360 -- degree number (integer in 0-360 range)
| |
| mNum = math.floor(k/d) % 60 -- minute number (integer in 0-60 range)
| |
| sNum = 3600*(k%d) / c -- seconds number (float in 0-60 range with 0, 1 or 2 decimal digits)
| |
| dStr = Lang:formatNum(dNum) -- degree string
| |
| mStr = Lang:formatNum(mNum) -- minute string
| |
| sStr = Lang:formatNum(sNum) -- second string
| |
| zero = Lang:formatNum(0) -- zero string in local language
| |
| if mNum<10 then
| |
| mStr = zero .. mStr -- pad with zero if a single digit
| |
| end | | end |
| if sNum<10 then | | if long_s < 0 then |
| sStr = zero .. sStr -- pad with zero if less than ten | | table.insert(errors, {source, "longitude seconds < 0"}) |
| end | | end |
| return string.format(formatStr, dStr, mStr, sStr); | | |
| | return errors; |
| end | | end |
|
| |
|
| --[[============================================================================ | | --[[ |
| Convert degrees to degrees/minutes/seconds notation commonly used when displaying
| | parseDec |
| coordinates.
| | |
| Inputs:
| | Transforms decimal format latitude and longitude into the |
| 1) latitude or longitude angle in degrees
| | structure to be used in displaying coordinates |
| 2) georeference precision in degrees
| | ]] |
| 3) language used in formatting of the number
| | local function parseDec( lat, long, format ) |
| ==============================================================================]]
| | local coordinateSpec = {} |
| function p.deg2dms(frame) | | local errors = {} |
| local args = core.getArgs(frame) | | |
| local degree = tonumber(args[1]) | | if not long then |
| local degPrec = tonumber(args[2]) or 0-- precision in degrees | | return nil, {{"parseDec", "Missing longitude"}} |
| | elseif not tonumber(long) then |
| | return nil, {{"parseDec", "Longitude could not be parsed as a number: " .. long}} |
| | end |
|
| |
|
| if degree==nil then | | errors = validate( lat, nil, nil, long, nil, nil, 'parseDec', false ); |
| return args[1];
| | 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 |
| else | | else |
| return p._deg2dms(degree, degPrec, args.lang) | | coordinateSpec.default = "dec" |
| end | | end |
| | |
| | return coordinateSpec, errors |
| end | | end |
|
| |
|
| function p.dms2deg(frame) | | --[[ |
| return dms2deg(frame.args[1]) | | parseDMS |
| end | | |
| | Transforms degrees, minutes, seconds format latitude and longitude |
| | into the a structure to be used in displaying coordinates |
| | ]] |
| | local function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format ) |
| | local coordinateSpec, errors, backward = {}, {} |
| | |
| | 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 |
| | |
| | if not lat_m and not lat_s and not long_m and not long_s and #errors == 0 then |
| | if math_mod._precision( lat_d ) > 0 or math_mod._precision( long_d ) > 0 then |
| | 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 ); |
| Format coordinate location string, by creating and joining DMS strings for
| |
| latitude and longitude. Also convert precision from meters to degrees.
| |
| INPUTS:
| |
| * lat = latitude in degrees
| |
| * lon = longitude in degrees
| |
| * lang = language code
| |
| * prec = geolocation precision in meters
| |
| ==============================================================================]]
| |
| function p._lat_lon(lat, lon, prec, lang)
| |
| lat = tonumber(lat)
| |
| lon = tonumber(lon)
| |
| prec = math.abs(tonumber(prec) or 0)
| |
| if lon then -- get longitude to be in -180 to 180 range
| |
| lon=lon%360
| |
| if lon>180 then
| |
| lon = lon-360
| |
| end | | end |
| end | | end |
| if lat==nil or lon==nil then | | |
| return NoLatLonString | | 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 | | else |
| local nsew = core.langSwitch(i18n.NSEW, lang) -- find set of localized translation of N, S, W and E in the desired language | | coordinateSpec.default = "dms" |
| local SN, EW, latStr, lonStr, lon2m, lat2m, phi
| |
| if lat<0 then SN = nsew.S else SN = nsew.N end -- choose S or N depending on latitude degree sign
| |
| if lon<0 then EW = nsew.W else EW = nsew.E end -- choose W or E depending on longitude degree sign
| |
| lat2m=1
| |
| lon2m=1
| |
| if prec>0 then -- if user specified the precision of the geo location...
| |
| phi = math.abs(lat)*math.pi/180 -- latitude in radiants
| |
| lon2m = 6378137*math.cos(phi)*math.pi/180 -- see https://en.wikipedia.org/wiki/Longitude
| |
| lat2m = 111000 -- average latitude degree size in meters
| |
| end
| |
| latStr = p._deg2dms(math.abs(lat), prec/lat2m, lang) -- Convert latitude degrees to degrees/minutes/seconds
| |
| lonStr = p._deg2dms(math.abs(lon), prec/lon2m, lang) -- Convert longitude degrees to degrees/minutes/seconds
| |
| return string.format('%s %s, %s %s', latStr, SN, lonStr, EW)
| |
| --return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW)
| |
| end | | end |
| end
| |
|
| |
|
| function p.lat_lon(frame)
| | return coordinateSpec, errors, backward |
| local args = core.getArgs(frame)
| |
| return p._lat_lon(args.lat, args.lon, args.prec, args.lang) | |
| end | | end |
|
| |
|
| --[[============================================================================ | | --[[ |
| Helper core function for externalLink. Create URL for different sites:
| | Check the input arguments for coord to determine the kind of data being provided |
| INPUTS:
| | and then make the necessary processing. |
| * site = Possible sites: GeoHack, GoogleEarth, Proximityrama,
| | ]] |
| OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
| | local function formatTest(args) |
| * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
| | local result, errors |
| Ganymede are also supported but are unused as of 2013.
| | local backward, primary = false, false |
| * latStr = latitude string or number
| |
| * lonStr = longitude string or number
| |
| * lang = language code
| |
| * attributes = attributes to be passed to GeoHack
| |
| ==============================================================================]]
| |
| function p._externalLink(site, globe, latStr, lonStr, lang, attributes, level) | |
| local URLstr = SiteURL[site]; | |
| level = level or 1
| |
| local pageName = mw.uri.encode( mw.title.getCurrentTitle().prefixedText, 'WIKI' ) | |
| pageName = mw.ustring.gsub( pageName, '%%', '%%%%')
| |
|
| |
|
| if site == 'GoogleMaps' then | | local function getParam(args, lim) |
| URLstr = SiteURL.GoogleMaps[globe] | | local ret = {} |
| elseif site == 'GeoHack' then
| | for i = 1, lim do |
| attributes = string.format('globe:%s_%s', globe, attributes) | | ret[i] = args[i] or '' |
| URLstr = mw.ustring.gsub( URLstr, '$attr', attributes) | | end |
| | return table.concat(ret, '_') |
| end | | end |
| URLstr = mw.ustring.gsub( URLstr, '$lat' , latStr)
| |
| URLstr = mw.ustring.gsub( URLstr, '$lon' , lonStr)
| |
| URLstr = mw.ustring.gsub( URLstr, '$lang' , lang)
| |
| URLstr = mw.ustring.gsub( URLstr, '$level', level)
| |
| URLstr = mw.ustring.gsub( URLstr, '$page' , pageName)
| |
| URLstr = mw.ustring.gsub( URLstr, '+', '')
| |
| URLstr = mw.ustring.gsub( URLstr, ' ', '_')
| |
| return URLstr
| |
| end
| |
|
| |
|
| --[[============================================================================ | | if not args[1] then |
| Create URL for different sites.
| | -- no lat logic |
| INPUTS:
| | return errorPrinter( {{"formatTest", "Missing latitude"}} ) |
| * site = Possible sites: GeoHack, GoogleEarth, Proximityrama,
| | elseif not tonumber(args[1]) then |
| OpenStreetMap, GoogleMaps (for Earth, Mars and Moon)
| | -- bad lat logic |
| * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan,
| | return errorPrinter( {{"formatTest", "Unable to parse latitude as a number:" .. args[1]}} ) |
| Ganymede are also supported but are unused as of 2013.
| | elseif not args[4] and not args[5] and not args[6] then |
| * lat = latitude string or number
| | -- dec logic |
| * lon = longitude string or number
| | result, errors = parseDec(args[1], args[2], args.format) |
| * lang = language code
| | if not result then |
| * attributes = attributes to be passed to GeoHack
| | return errorPrinter(errors); |
| ==============================================================================]] | | end |
| function p.externalLink(frame)
| | -- formatting for geohack: geohack expects D_N_D_E notation or D;D notation |
| local args = core.getArgs(frame)
| | -- wikiminiatlas doesn't support D;D notation |
| return p._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '')
| | -- #coordinates parserfunction doesn't support negative decimals with NSWE |
| end | | result.param = table.concat({ |
| | | math.abs(tonumber(args[1])), |
| --[[============================================================================
| | ((tonumber(args[1]) or 0) < 0) and 'S' or 'N', |
| Adjust GeoHack attributes depending on the template that calls it
| | math.abs(tonumber(args[2])), |
| INPUTS:
| | ((tonumber(args[2]) or 0) < 0) and 'W' or 'E', |
| * attributes = attributes to be passed to GeoHack
| | args[3] or ''}, '_') |
| * mode = set by each calling template
| | elseif dmsTest(args[4], args[8]) then |
| ==============================================================================]]
| | -- dms logic |
| function p.alterAttributes(attributes, mode, heading)
| | result, errors, backward = parseDMS(args[1], args[2], args[3], args[4], |
| -- indicate which template called it
| | args[5], args[6], args[7], args[8], args.format) |
| if mode=='camera' then -- Used by {{Location}} and {{Location dec}}
| | if args[10] then |
| if not string.find(attributes, 'type:camera') then | | table.insert(errors, {'formatTest', 'Extra unexpected parameters'}) |
| attributes = 'type:camera_' .. attributes
| | 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 |
| | if not result then |
| | return errorPrinter(errors) |
| | end |
| | result.param = getParam(args, 7) |
| | elseif dmsTest(args[2], args[4]) then |
| | -- 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 | | end |
| elseif mode=='object' then -- Used by {{Object location}}
| | if not result then |
| if mode=='object' and not string.find(attributes, 'type:') then | | return errorPrinter(errors) |
| attributes = 'type:object_' .. attributes | |
| end | | end |
| if not string.find(attributes, 'class:object') then | | result.param = getParam(args, 5) |
| attributes = 'class:object_' .. attributes | | else |
| | -- Error |
| | return errorPrinter({{"formatTest", "Unknown argument format"}}) .. '[[Category:Pages with malformed coordinate tags]]' |
| | end |
| | result.name = args.name |
| | |
| | local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type'} |
| | for _, v in ipairs(extra_param) do |
| | if args[v] then |
| | table.insert(errors, {'formatTest', 'Parameter: "' .. v .. '=" should be "' .. v .. ':"' }) |
| end | | end |
| elseif mode=='inline' then -- Used by {{Inline coordinates}} (actually that template does not set any attributes at the moment)
| |
| elseif mode=='user' then -- Used by {{User location}}
| |
| attributes = 'type:user_location'
| |
| elseif mode=='institution' then --Used by {{Institution/coordinates}} (categories only)
| |
| attributes = 'type:institution'
| |
| end | | end |
| local hStr = '' | | |
| if heading then -- if heading is a number | | local ret = specPrinter(args, result) |
| hStr = string.format('heading:%6.2f', heading) | | if #errors > 0 then |
| | ret = ret .. ' ' .. errorPrinter(errors) .. '[[Category:Pages with malformed coordinate tags]]' |
| | end |
| | return ret, backward |
| | end |
| | |
| | --[[ |
| | Generate Wikidata tracking categories. |
| | ]] |
| | local function makeWikidataCategories(qid) |
| | local ret |
| | local qid = qid or mw.wikibase.getEntityIdForCurrentPage() |
| | if mw.wikibase and current_page.namespace == 0 then |
| | if qid and mw.wikibase.entityExists(qid) and mw.wikibase.getBestStatements(qid, "P625") and mw.wikibase.getBestStatements(qid, "P625")[1] then |
| | local snaktype = mw.wikibase.getBestStatements(qid, "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 | | end |
| if not string.find(attributes, 'heading:') then | | if ret then |
| attributes = attributes .. '_' .. hStr
| | return string.format('[[Category:%s]]', ret) |
| else | | else |
| attributes = string.gsub(attributes,'heading:[^_]*', hStr) -- replace heading in form heading:N with heading=0 | | return '' |
| attributes = string.gsub(attributes,'__', '_')
| |
| end | | end |
| | end |
| | |
| | --[[ |
| | link |
|
| |
|
| return string.gsub(attributes,' ', '') | | Simple function to export the coordinates link for other uses. |
| | |
| | Usage: |
| | {{#invoke:Coordinates | link }} |
| | |
| | ]] |
| | function coordinates.link(frame) |
| | return coord_link; |
| end | | end |
|
| | |
| --[[============================================================================ | | --[[ |
| Create link to GeoHack tool which displays latitude and longitude coordinates
| | dec2dms |
| in DMS format
| | |
| INPUTS:
| | Wrapper to allow templates to call dec2dms directly. |
| * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | | |
| Ganymede are also supported but are unused as of 2013.
| | Usage: |
| * lat = latitude in degrees
| | {{#invoke:Coordinates | dec2dms | decimal_coordinate | positive_suffix | |
| * lon = longitude in degrees
| | negative_suffix | precision }} |
| * lang = language code
| | |
| * prec = geolocation precision in meters
| | decimal_coordinate is converted to DMS format. If positive, the positive_suffix |
| * attributes = attributes to be passed to GeoHack
| | 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 |
| function p._GeoHack_link(args) | | to use. |
| -- create link and coordintate string
| | ]] |
| local latlon = p._lat_lon(args.lat, args.lon, args.prec, args.lang) | | coordinates.dec2dms = makeInvokeFunc('_dec2dms') |
| if latlon==NoLatLonString then | | function coordinates._dec2dms(args) |
| return latlon | | 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 | | else |
| local url = p._externalLink('GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '')
| | return 'dms'; |
| return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion"> | |
| end | | end |
| end | | end |
|
| |
|
| function p.GeoHack_link(frame)
| | --[[ |
| return p._GeoHack_link(core.getArgs(frame))
| | 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. |
| | 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 | | 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 |
| Create full external links section of {{Location}} or {{Object location}}
| | order to load coordinates into the database, the {{#coordinates:}} parser |
| templates, based on: | | function must also be called, this is done automatically in the Lua |
| * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
| | version of {{coord}}. |
| * mode = Possible options:
| | ]] |
| - camera - call from {{location}}
| | coordinates.coord = makeInvokeFunc('_coord') |
| - object - call from {{Object location}}
| | function coordinates._coord(args) |
| - globe - call from {{Globe location}}
| | if not tonumber(args[1]) and not args[2] then |
| * lat = latitude in degrees
| | args[3] = args[1]; args[1] = nil |
| * lon = longitude in degrees
| | local entity = mw.wikibase.getEntityObject(args.qid) |
| * lang = language code
| | if entity |
| * namespace = namespace name: File, Category, (Gallery)
| | and entity.claims |
| ==============================================================================]] | | and entity.claims.P625 |
| function p._externalLinksSection(args)
| | and entity.claims.P625[1].mainsnak.snaktype == 'value' |
| local lang = args.lang
| | then |
| if not args.namespace then
| | local precision = entity.claims.P625[1].mainsnak.datavalue.value.precision |
| args.namespace = mw.title.getCurrentTitle().nsText
| | 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 |
| end | | 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]', link1, core.langSwitch(i18n.OpenStreetMaps, lang))
| |
| --link2, core.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]', link1, core.langSwitch(i18n.OpenStreetMaps, lang))
| |
| --link2, core.langSwitch(i18n.GoogleMaps, lang),
| |
| --link3, core.langSwitch(i18n.GoogleEarth, lang),
| |
| --link4, core.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, core.langSwitch(i18n.GoogleMaps, lang))
| |
| end
| |
|
| |
| return str
| |
| end
| |
|
| |
|
| function p.externalLinksSection(frame)
| | local contents, backward = formatTest(args) |
| return p._externalLinksSection(core.getArgs(frame)) | | local Notes = args.notes or '' |
| end
| | local Display = args.display and args.display:lower() or 'inline' |
|
| |
|
| --[[============================================================================ | | local function isInline(s) |
| Core section of template:Location, template:Object location and template:Globe location.
| | -- Finds whether coordinates are displayed inline. |
| This method requires several arguments to be passed to it or it's parent method/template:
| | return s:find('inline') ~= nil or s == 'i' or s == 'it' or s == 'ti' |
| * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
| |
| * mode = Possible options:
| |
| - camera - call from {{location}}
| |
| - object - call from {{Object location}}
| |
| - globe - call from {{Globe location}}
| |
| * lat = latitude in degrees
| |
| * lon = longitude in degrees
| |
| * attributes = attributes
| |
| * lang = language code
| |
| * namespace = namespace: File, Category, Gallery
| |
| * prec = geolocation precision in meters
| |
| ==============================================================================]]
| |
| function p._LocationTemplateCore(args)
| |
| -- prepare arguments
| |
| if not (args.namespace) then -- if namespace not provided than look it up
| |
| args.namespace = mw.title.getCurrentTitle().nsText
| |
| end | | end |
| if args.namespace=='' then -- if empty than it is a gallery | | local function isInTitle(s) |
| args.namespace = 'Gallery'
| | -- Finds whether coordinates are displayed in the title. |
| | return s:find('title') ~= nil or s == 't' or s == 'it' or s == 'ti' |
| end | | end |
| local bare = core.yesno(args.bare,false) | | |
| local Status = 'primary' -- used by {{#coordinates:}}
| | local function coord_wrapper(in_args) |
| if core.yesno(args.secondary,false) then
| | -- Calls the parser function {{#coordinates:}}. |
| Status = 'secondary'
| | return mw.getCurrentFrame():callParserFunction('#coordinates', in_args) or '' |
| end | | end |
| args.globe = mw.language.new('en'):ucfirst(args.globe or 'Earth') | | |
| | | local text = '' |
| -- Convert coordinates from string to numbers
| | if isInline(Display) then |
| local lat = tonumber(args.lat)
| | text = text .. displayinline(contents, Notes) |
| local lon = tonumber(args.lon)
| |
| local precission = tonumber(args.prec or '0')
| |
| local heading = p._getHeading(args.attributes) -- get heading arrow section
| |
| if lon then -- get longitude to be in -180 to 180 range
| |
| lon=lon%360
| |
| if lon>180 then
| |
| lon = lon-360
| |
| end
| |
| end | | end |
|
| | if isInTitle(Display) then |
| -- If wikidata link provided than compare coordinates
| | text = text |
| local Categories, geoMicroFormat, coorTag, edit_icon, wikidata_link = '', '', '', '', '', '', ''
| | .. displaytitle(contents, Notes) |
| local entity, coord, sd, cmp
| | .. makeWikidataCategories(args.qid) |
| local loc = {lat=lat, lon=lon, heading=heading, source='loc'}
| |
| local ID = args.wikidata
| |
| if (ID==nil) then | |
| entity = mw.wikibase.getEntity() | |
| elseif type(ID)=='string' and ID:match( '^[QqMm]%d+$' ) then
| |
| entity = mw.wikibase.getEntity(ID)
| |
| elseif type(ID)~='string' and ID.id then
| |
| entity = ID -- entities can be passed from outside
| |
| end | | end |
|
| | if not args.nosave then |
| if entity then | | local page_title, count = mw.title.getCurrentTitle(), 1 |
| if (args.mode=='object' or args.mode=='globe') then | | if backward then |
| sd = getSDCoords(entity,'P9149') -- fetch coordinates of depicted place | | local tmp = {} |
| if not sd.lat then | | while not string.find((args[count-1] or ''), '[EW]') do tmp[count] = (args[count] or ''); count = count+1 end |
| sd = getSDCoords(entity,'P625') -- fallback to coordinate location
| | tmp.count = count; count = 2*(count-1) |
| end
| | while count >= tmp.count do table.insert(tmp, 1, (args[count] or '')); count = count-1 end |
| elseif (args.mode=='camera') then
| | for i, v in ipairs(tmp) do args[i] = v end |
| sd = getSDCoords(entity,'P1259') -- fetch camera coordinates or coordinates of the point of view
| | else |
| end
| | while count <= 9 do args[count] = (args[count] or ''); count = count+1 end |
| if (args.namespace=='File') then -- look up lat/lon on SDC
| |
| coord, Categories, cmp = compareCoords(loc, sd, args.mode, 'SDC')
| |
| if coord.source~='loc' then | |
| edit_icon = core.editAtSDC(coord.source, args.lang)
| |
| lat, lon, heading, precission = coord.lat, coord.lon, coord.heading, coord.prec
| |
| end
| |
| elseif (args.namespace == 'Category') then -- look up lat/lon on wikidata
| |
| sd.wID = entity.id | |
| coord, Categories, cmp = compareCoords(loc, sd, args.mode, 'Wikidata')
| |
| if coord.source~='loc' then | |
| local str = "\n[[File:Wikidata-logo.svg|20px|Field with data from Wikidata's %s property<br/>%s|link=wikidata:%s#%s]]"
| |
| edit_icon = core.editAtWikidata(entity.id, coord.source, args.lang)
| |
| lat, lon, heading, precission = coord.lat, coord.lon, coord.heading, coord.prec
| |
| end
| |
| if cmp.qs then
| |
| wikidata_link = cmp.qs
| |
| end
| |
| end | | end |
| elseif (args.namespace=='File') then
| | if isInTitle(Display) and not page_title.isTalkPage and page_title.subpageText ~= 'doc' and page_title.subpageText ~= 'testcases' then args[10] = 'primary' end |
| Categories = string.format(CoorCat.strucData4, args.mode, 'SDC') | | args.notes, args.format, args.display = nil |
| | text = text .. coord_wrapper(args) |
| end | | end |
| | return text |
| | end |
| | |
| | --[[ |
| | coord2text |
| | |
| | Extracts a single value from a transclusion of {{Coord}}. |
| | IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED. |
| | |
| | Usage: |
|
| |
|
| args.lat = string.format('%010.6f', lat or 0)
| | {{#invoke:Coordinates | coord2text | {{Coord}} | parameter }} |
| args.lon = string.format('%011.6f', lon or 0)
| |
| args.prec = precission
| |
| args.attributes = p.alterAttributes(args.attributes or '', args.mode, heading)
| |
| local frame = mw.getCurrentFrame()
| |
|
| |
|
| -- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages
| | Valid values for the second parameter are: lat (signed integer), long (signed integer), type, scale, dim, region, globe, source |
| if (args.namespace == 'File' or args.namespace == 'Category' or args.namespace == 'Gallery') then
| |
| if lat and lon then -- if lat and lon are numbers...
| |
| if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources
| |
| Categories = Categories .. CoorCat.default
| |
| end
| |
| if args.attributes and string.find(args.attributes, '=') then
| |
| Categories = Categories .. CoorCat.attribute
| |
| end
| |
| if (math.abs(lon)>180) or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors )
| |
| Categories = Categories .. '<span style="color:red;font-weight:bold">Error: Invalid parameters! (coordinates are outside allowed range)</span>\n' .. CoorCat.erroneous
| |
| end
| |
| -- local cat = CoorCat[args.namespace]
| |
| -- if cat then -- add category based on namespace
| |
| -- Categories = Categories .. cat
| |
| -- end
| |
| -- if not earth than add a category for each globe
| |
| if args.mode and args.globe and args.mode=='globe' and args.globe~='Earth' then
| |
| Categories = Categories .. string.format(CoorCat[args.mode], args.globe)
| |
| end
| |
| -- add <span class="geo"> Geo (microformat) code: it is included for machine readability
| |
| geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon)
| |
| -- add {{#coordinates}} tag, see https://www.mediawiki.org/wiki/Extension:GeoData
| |
| if args.namespace == 'File' and Status == 'primary' and args.mode=='camera' then
| |
| coorTag = frame:callParserFunction( '#coordinates', { 'primary', lat, lon, args.attributes } )
| |
| elseif args.namespace == 'File' and args.mode=='object' then
| |
| coorTag = frame:callParserFunction( '#coordinates', { lat, lon, args.attributes } )
| |
| end
| |
| else -- if lat and lon are not numbers then add error category
| |
| Categories = Categories .. '<span style="color:red;font-weight:bold">Error: Invalid parameters! (coordinates are missing or not numeric)</span>\n' .. CoorCat.erroneous
| |
| end
| |
| end
| |
|
| |
|
| -- Call helper functions to render different parts of the template
| | ]] |
| local coor, info_link, inner_table, OSM = '','','','','','' | | function coordinates.coord2text(frame) |
| coor = p._GeoHack_link(args) -- the p and link to GeoHack | | if frame.args[1] == '' or frame.args[2] == '' or not frame.args[2] then return nil end |
| coor = string.format('<span class=plainlinks>%s</span>%s', coor, edit_icon) | | frame.args[2] = mw.text.trim(frame.args[2]) |
| if heading then
| | if frame.args[2] == 'lat' or frame.args[2] == 'long' then |
| local k = math.fmod(math.floor(0.5+math.fmod(heading+360,360)/11.25),32)+1 | | local result, negative = mw.text.split((mw.ustring.match(frame.args[1],'[%.%d]+°[NS] [%.%d]+°[EW]') or ''), ' ') |
| local fname = heading_icon[k]
| | if frame.args[2] == 'lat' then |
| coor = string.format('%s <span title="%s°">[[%s|25px|link=|alt=Heading=%s°]]</span>', coor, heading, fname, heading)
| | result, negative = result[1], 'S' |
| end
| | else |
| if args.globe=='Earth' then
| | result, negative = result[2], 'W' |
| local icon = 'marker'
| |
| if args.mode=='camera' then | |
| icon = 'camera' | |
| end | | 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 | | result = mw.text.split(result, '°') |
| end
| | if result[2] == negative then result[1] = '-'..result[1] end |
| local external_link = p._externalLinksSection(args) -- external link section
| | return result[1] |
| if external_link and args.namespace == 'File' then
| |
| external_link = core.langSwitch(i18n.LocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{location}} template
| |
| elseif external_link then
| |
| external_link = core.langSwitch(i18n.ObjectLocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{Object location}} template
| |
| end
| |
| info_link = string.format('[[File:OOjs UI icon help.svg|18x18px|alt=info|link=%s]]', core.langSwitch(i18n.COM_GEO, args.lang) )
| |
| inner_table = string.format('<td style="border:none;">%s %s</td><td style="border:none;">%s</td><td style="border:none;">%s%s%s</td>',
| |
| coor, OSM, external_link or '', wikidata_link, info_link, geoMicroFormat)
| |
|
| |
| -- combine strings into a table
| |
| local templateText
| |
| if bare then
| |
| templateText = string.format('<table style="width:100%%"><tr>%s</tr></table>', inner_table)
| |
| else | | else |
| -- choose name of the field and create row | | return mw.ustring.match(frame.args[1], 'params=.-_'..frame.args[2]..':(.-)[ _]') |
| local field_name = 'Location'
| |
| if args.mode=='camera' then
| |
| field_name = core.langSwitch(i18n.CameraLocation, args.lang)
| |
| elseif args.mode=='object' then
| |
| field_name = core.langSwitch(i18n.ObjectLocation, args.lang)
| |
| elseif args.mode=='globe' then
| |
| local field_list = core.langSwitch(i18n.GlobeLocation, args.lang)
| |
| if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized
| |
| field_name = field_list[args.globe]
| |
| end
| |
| end
| |
| templateText = string.format('<tr><th class="type fileinfo-paramfield">%s</th>%s</tr>', field_name, inner_table)
| |
| --Create HTML text
| |
| local dir = mw.language.new( args.lang ):getDir() -- get text direction
| |
| local style = 'class="toccolours mw-content-%s layouttemplate commons-file-information-table" style="width: 100%%;" dir="%s" lang="%s"'
| |
| style = string.format(style, dir, dir, args.lang)
| |
| templateText = string.format('<table %s>\n%s\n</table>', style, templateText)
| |
| end | | end |
| return templateText, Categories, coorTag
| |
| end | | end |
|
| |
|
| function p.LocationTemplateCore(frame)
| | --[[ |
| local args = core.getArgs(frame)
| | coordinsert |
| args.namespace = mw.title.getCurrentTitle().nsText
| | |
| if not args.lat and not args.lon then -- if no lat and lon but numbered arguments present
| | 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}}. |
| if args[4] then -- DMS with pipes format, ex. "34|5|32.36|N|116|9|24|55|W"
| | IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED. |
| args.lat = dms2deg_ ( args[1], args[2], args[3], args[4] )
| | |
| args.lon = dms2deg_ ( args[5], args[6], args[7], args[8] )
| | Usage: |
| args.attributes = args.attributes or args[9] | | |
| elseif args[2] and not (type(args[2])=='string' and args[2]:find(":")) then -- decimal format or DMS with one pipe, ex. "34° 05′ 32.36″ N| 116° 09′ 24.55″ W"
| | {{#invoke:Coordinates | coordinsert | {{Coord}} | parameter:value | parameter:value | … }} |
| args.lat = args[1]
| | |
| args.lon = args[2]
| | Do not make Geohack unhappy by inserting something which isn't mentioned in the {{Coord}} documentation. |
| args.attributes = args.attributes or args[3]
| | |
| elseif args[1] then -- detect a single argument in the form "34° 05′ 32.36″ N, 116° 09′ 24.55″ W" or similar
| | ]] |
| local v = mw.text.split(args[1]:gsub("([NnSs])", "%1/" ), "/") -- split into lat and lon using splitting point after any letter
| | function coordinates.coordinsert(frame) |
| args.lat, args.lon = v[1], v[2]
| | for i, v in ipairs(frame.args) do |
| args.attributes = args.attributes or args[2] | | 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 | | end |
| local cat = ''
| | if frame.args.name then |
| if args.lat and args.lon then | | if not mw.ustring.find(frame.args[1], '<span class="vcard">') then |
| local lat = tonumber(args.lat) | | local namestr = frame.args.name |
| local lon = tonumber(args.lon)
| | frame.args[1] = mw.ustring.gsub(frame.args[1], |
| if not lat or not lon then
| | '(<span class="geo%-default">)(<span[^<>]*>[^<>]*</span><span[^<>]*>[^<>]*<span[^<>]*>[^<>]*</span></span>)(</span>)', |
| args.lat = dms2deg(args.lat or '') | | '%1<span class="vcard">%2<span style="display:none"> (<span class="fn org">' .. namestr .. '</span>)</span></span>%3') |
| args.lon = dms2deg(args.lon or '')
| | frame.args[1] = mw.ustring.gsub(frame.args[1], '(¶ms=[^&"<>%[%] ]*) ', '%1&title=' .. mw.uri.encode(namestr) .. ' ') |
| if (args.namespace == 'File' or args.namespace == 'Category') then | |
| cat = CoorCat.dms
| |
| end
| |
| end | | end |
| end | | end |
| local templateText, Categories, coorTag = p._LocationTemplateCore(args) | | return frame.args[1] |
| return templateText .. Categories .. cat .. coorTag
| |
| end | | end |
|
| |
|
| return p | | return coordinates |