This is an old revision of this page, as edited by Dragons flight (talk | contribs) at 02:39, 5 March 2013. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Revision as of 02:39, 5 March 2013 by Dragons flight (talk | contribs)(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff) Module documentation[view] [edit] [history] [purge]This Lua module is used on approximately 1,370,000 pages, or roughly 2% of all pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
This module depends on the following other modules: |
This module uses TemplateStyles: |
Note: The code which this module's main function (coord
) outputs is directly parsed and/or manipulated by Module:Location map and other functions of this module itself (coord2text
and coordinsert
). If the structure of the output changes (for example, to use the <mapframe>
and <maplink>
tags), please update the aforementioned scripts as well.
Using the module with coordinsert
When using the {{Coord}} template inside another template, like an infobox, there may be parameters (like type:airport
) which should be added automatically. To do so, do something like this:
{{#if:{{{coordinates|}}}|{{#invoke:Coordinates|coordinsert|{{{coordinates|}}}|parameter1:value1|parameter2:value2|parameter3:value3…}}|
Do not add more vertical bars |
than necessary.
Using the module with coord2text to extract latitude or longitude
Developers maintaining legacy code may need to extract latitude or longitude to use a parameters in other code, or a mathematical expression. The module's "coord2text" function can be used to extract data from the {{Coord}} template. To extract the latitude from a Coord template, use:
{{#invoke:coordinates|coord2text|{{Coord|57|18|22|N|4|27|32|E}}|lat}}
→ Lua error in package.lua at line 80: module 'Module:Wikitext' not found.
To extract the longitude, use:
{{#invoke:coordinates|coord2text|{{Coord|57|18|22|N|4|27|32|E}}|long}}
→ Lua error in package.lua at line 80: module 'Module:Wikitext' not found.
Modules using this module directly
Tracking categories
- Category:Pages with malformed coordinate tags (38)
- Category:Coordinates not on Wikidata (14,472)
- Category:Coordinates on Wikidata (1,205,815)
- Category:Coordinates on Wikidata set to no value (9)
- Category:Coordinates on Wikidata set to unknown value (29)
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit | run) pages.
Subpages of this module.
-- A module that mimics the functionality of Template:Coord and its sub templates -- The attempt is to actually mimic a conversion of an often used en.wp template in the way -- that most templates will actually be converted by the wiki users. -- Attempt is not really to write a nice and proper module from scratch :D local math_mod = require "Module:Math"; local wikitext = require "Module:Wikitext" globalFrame = nil coordinates = {}; --- Replacement for {{coord/display/title}} function displaytitle (s) local l = "]: " .. s local co = mw.text.tag({name="span",contents=l,params={id="coordinates"}}) local p = {} p = "small" return mw.text.tag({name="span",contents=co,params=p}) end --- Replacement for {{coord/display/inline}} function displayinline (s) return s end --- Test if the arguments imply that DMS might be in use local dmsTest = function(first, second) local concatenated = first .. second; if concatenated == "NE" or concatenated == "NW" or concatenated == "SE" or concatenated == "SW" then return true; end return false; end --- Parse the frame assuming that it is in dec format. -- @frame -- @returns a table with all information needed to display coordinates function parseDec(args) local coordinateSpec = {} local errors = {} if args == "" or args == nil then return nil, {{"parseDec", "Missing longitude"}} end coordinateSpec = args coordinateSpec = args local precision = math.max( math_mod._precision(args), math_mod._precision(args) ); coordinateSpec = convert_dec2dms(args, "N", "S", precision) -- {{coord/dec2dms|{{{1}}}|N|S|{{coord/prec dec|{{{1}}}|{{{2}}}}}}} coordinateSpec = convert_dec2dms(args, "E", "W", precision) -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}} coordinateSpec.param = args .."_N_" .. args .. "_E_" .. args if args ~= "" then coordinateSpec.default = args else coordinateSpec.default = "dec" end coordinateSpec.name = args -- TODO refactor the validations into separate functions if (tonumber(args) or 0) > 90 then table.insert(errors, {"parseDec","latd>90"}) end if (tonumber(args) or 0) < -90 then table.insert(errors, {"parseDec", "latd<-90"}) end if (tonumber(args) or 0) >= 360 then table.insert(errors, {"parseDec", "longd>=360"}) end if (tonumber(args) or 0) <= -360 then table.insert(errors, {"parseDec", "longd<=-360"}) end return coordinateSpec, errors end function optionalArg(arg, suplement) if arg ~= nil and arg ~= "" then return arg .. suplement end return "" end function isEmpty(arg) if arg == nil or arg == "" then return true end return false end function parseDMS(args) local coordinateSpec = {} local errors = {} coordinateSpec = args.."°"..optionalArg(args,"′") .. optionalArg(args,"″") .. args coordinateSpec = args.."°"..optionalArg(args,"′") .. optionalArg(args,"″") .. args coordinateSpec = convert_dms2dec(args,args,args,args) -- {{coord/dms2dec|{{{4}}}|{{{1}}}|0{{{2}}}|0{{{3}}}}} coordinateSpec = convert_dms2dec(args,args,args,args) -- {{coord/dms2dec|{{{8}}}|{{{5}}}|0{{{6}}}|0{{{7}}}}} -- TODO Use loop when we know it won't break everything coordinateSpec.param = args .. "_" .. args .. "_" .. args .. "_" .. args .. "_".. args .. "_" .. args .. "_" .. args .. "_" .. args .. "_" .. args if args ~= "" then coordinateSpec.default = args else coordinateSpec.default = "dms" end coordinateSpec.name = args -- Error reporting if isEmpty(args) then table.insert(errors, {"parseDM", "Missing longitude" }) end if not isEmpty(args) then table.insert(errors, {"parseDM", "Unexpected extra parameters"}) end if (tonumber(args) or 0) > 90 then table.insert(errors, {"parseDMS", "latd>90"}) end if (tonumber(args) or 0) < -90 then table.insert(errors, {"parseDMS", "latd<-90"}) end if (tonumber(args) or 0) >= 60 then table.insert(errors, {"parseDMS", "latm>=60"}) end if (tonumber(args) or 0) < 0 then table.insert(errors, {"parseDMS", "latm<0"}) end if (tonumber(args) or 0) >= 60 then table.insert(errors, {"parseDMS", "lats>=60"}) end if (tonumber(args) or 0) < 0 then table.insert(errors, {"parseDMS", "lats<0"}) end if (tonumber(args) or 0) >= 360 then table.insert(errors, {"parseDMS", "longd>=360"}) end if (tonumber(args) or 0) <= -360 then table.insert(errors, {"parseDMS", "longd<=-360"}) end if (tonumber(args) or 0) >= 60 then table.insert(errors, {"parseDMS", "longm>=60"}) end if (tonumber(args) or 0) < 0 then table.insert(errors, {"parseDMS", "longm<0"}) end if (tonumber(args) or 0) >= 60 then table.insert(errors, {"parseDMS", "longs>=60"}) end if (tonumber(args) or 0) < 0 then table.insert(errors, {"parseDMS", "longs<0"}) end return coordinateSpec, errors end function parseDM(args) local coordinateSpec = {} local errors = {} coordinateSpec = args.."°"..optionalArg(args,"′") .. args coordinateSpec = args.."°"..optionalArg(args,"′") .. args coordinateSpec = convert_dms2dec(args,args,args) -- {{coord/dms2dec|{{{3}}}|{{{1}}}|0{{{2}}}}} coordinateSpec = convert_dms2dec(args,args,args) -- {{coord/dms2dec|{{{6}}}|{{{4}}}|0{{{5}}}}} -- TODO Use loop when we know it won't break everything coordinateSpec.param = args .. "_" .. args .. "_" .. args .. "_" .. args .. "_".. args .. "_" .. args .. "_" .. args if args ~= "" then coordinateSpec.default = args else coordinateSpec.default = "dms" end coordinateSpec.name = args -- Error reporting if isEmpty(args) then table.insert(errors, {"parseDM", "Missing longitude" }) end if not (isEmpty(args) and isEmpty(args) and isEmpty(args)) then table.insert(errors, {"parseDM", "Unexpected extra parameters"}) end if (tonumber(args) or 0) > 90 then table.insert(errors, {"parseDM", "latd>90"}) end if (tonumber(args) or 0) < -90 then table.insert(errors, {"parseDM", "latd<-90"}) end if (tonumber(args) or 0) >= 60 then table.insert(errors, {"parseDM", "latm>=60"}) end if (tonumber(args) or 0) < 0 then table.insert(errors, {"parseDM", "latm<0"}) end if (tonumber(args) or 0) >= 360 then table.insert(errors, {"parseDM", "longd>=360"}) end if (tonumber(args) or 0) <= -360 then table.insert(errors, {"parseDM", "longd<=-360"}) end if (tonumber(args) or 0) >= 60 then table.insert(errors, {"parseDM", "longm>=60"}) end if (tonumber(args) or 0) < 0 then table.insert(errors, {"parseDM", "longm<0"}) end return coordinateSpec, errors end function parseD(args) local coordinateSpec = {} local errors = {} coordinateSpec = args if args =="S" then coordinateSpec = "-" .. coordinateSpec end coordinateSpec = args if args =="W" then coordinateSpec = "-" .. coordinateSpec end coordinateSpec = args .. "°" .. args coordinateSpec = args .. "°" .. args local function postfixInverter(NE, latlong) if NE == "N" and latlong == "lat" then return "S" elseif NE == "E" and latlong == "long" then return "W" elseif latlong == "lat" then return "N" else return "E" end end local precision = math.max( math_mod._precision(args), math_mod._precision(args) ) coordinateSpec = convert_dec2dms(args, args, postfixInverter(args,"lat"), precision) -- {{coord/dec2dms|{{{1}}}|{{{2}}}|{{#ifeq:{{{2}}}|N|S|N}}|{{coord/prec dec|{{{1}}}|{{{3}}}}}}} coordinateSpec = convert_dec2dms(args, args, postfixInverter(args,"long"), precision) -- {{coord/dec2dms|{{{3}}}|{{{4}}}|{{#ifeq:{{{4}}}|E|W|E}}|{{coord/prec dec|{{{1}}}|{{{3}}}}}}} -- TODO Use loop when we know it won't break everything coordinateSpec.param = args .. "_" .. args .. "_" .. args .. "_" .. args .. "_".. args if args ~= "" then coordinateSpec.default = args else -- {{#ifeq:{{coord/prec dec|{{{1}}}|{{{3}}}}}|d|dms|dec}} if precision == "d" then coordinateSpec.default = "dms" else coordinateSpec.default = "dec" end end coordinateSpec.name = args -- Error reporting if isEmpty(args) then table.insert(errors, {"parseD", "Missing longitude" }) args = 0 -- to avoid error in tonumber() later on end if not (isEmpty(args) and isEmpty(args) and isEmpty(args) and isEmpty(args) and isEmpty(args)) then table.insert(errors, {"parseD", "Unexpected extra parameters"}) end if (tonumber(args) or 0) > 90 then table.insert(errors, {"parseD", "latd>90"}) end if (tonumber(args) or 0) < -90 then table.insert(errors, {"parseD", "latd<-90"}) end if (tonumber(args) or 0) >= 360 then table.insert(errors, {"parseD", "longd>=360"}) end if (tonumber(args) or 0) <= -360 then table.insert(errors, {"parseD", "longd<=-360"}) end return coordinateSpec, errors end --- BAD BAD URL escape -- replace this later with the actual helper template function urlEscape(arg) return arg:gsub("%s+", '%%20'):gsub("%<", "%%3C"):gsub("%>", "%%3E") end --- A function that prints a table of coordinate specifications to HTML function specPrinter(args, coordinateSpec) local uriComponents = coordinateSpec if uriComponents == "" then -- RETURN error, should never be empty or nil return "ERROR param was empty" end if args ~= "" and args ~= nil then uriComponents = uriComponents .. "&title=" .. urlEscape(coordinateSpec) end -- TODO i18n local geodmshtml = '<span class="geo-dms" title="Maps, aerial photos, and other data for this location">' .. '<span class="latitude">' .. coordinateSpec .. '</span> ' .. '<span class="longitude">' ..coordinateSpec .. '</span>' .. '</span>' local geodeclat = coordinateSpec if geodeclat == nil then local lat = tonumber( coordinateSpec ) or 0 if lat < 0 then -- FIXME this breaks the pre-existing precision geodeclat = coordinateSpec:sub(2) .. "°S" else geodeclat = (coordinateSpec or 0) .. "°N" end end -- FIXME ugly code duplication, but lazy :D local geodeclong = coordinateSpec if geodeclong == nil then local long = tonumber( coordinateSpec ) or 0 if long < 0 then -- FIXME does not handle unicode minus geodeclong = coordinateSpec:sub(2) .. "°W" else geodeclong = (coordinateSpec or 0) .. "°E" end end -- TODO requires DEC formatting -- TODO requires vcard local geodechtml = '<span class="geo-dec" title="Maps, aerial photos, and other data for this location">' .. '<span class="latitude">' .. geodeclat .. '</span> ' .. '<span class="longitude">' .. geodeclong .. '</span>' .. '</span>' local inner = '<span class="' .. displayDefault(coordinateSpec, "dms" ) .. '">' .. geodmshtml .. '</span>' .. '<span class="geo-multi-punct"> / </span>' .. '<span class="' .. displayDefault(coordinateSpec, "dec" ) .. '">' .. geodechtml .. '</span>' return '<span class="plainlinks nourlexpansion">' .. globalFrame:preprocess('') .. '</span>' end function errorPrinter(errors) local result = "" for i,v in ipairs(errors) do local errorHTML = '<strong class="error">' .. v .. ' in Module:Coordinates.' .. v .."()" .. '</strong>' result = result .. errorHTML .. "<br />" end return result end --- Determine the required CSS class to display coordinates -- 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 -- mode is the display mode (dec or dms) that we will need to determine the css class for function displayDefault(default, mode) if default == "" then default = "dec" end if default == mode then return "geo-default" else return "geo-nondefault" end end --- Check the arguments to determine what type of coordinates has been input function formatTest(args) if args == "" then -- no lat logic return errorPrinter( {{"formatTest", "Missing latitude"}} ) elseif args == "" and args == "" and args == "" then -- dec logic local decResult, errors = parseDec(args) return specPrinter(args,decResult) .. " " .. errorPrinter(errors) elseif dmsTest(args, args) then -- dms logic local dmsResult, errors = parseDMS(args) return specPrinter(args, dmsResult ) .. " " .. errorPrinter(errors) -- return "dms" elseif dmsTest(args, args) then -- dm logic local dmResult, errors = parseDM(args) return specPrinter(args, dmResult ) .. " " .. errorPrinter(errors) -- return "dm" elseif dmsTest(args, args) then -- d logic local dResult, errors = parseD(args) return specPrinter(args, dResult ) .. " " .. errorPrinter(errors) -- return "d" end -- Error return errorPrinter( {{"formatTest", "Unknown argument format"}} ) end function convert_dec2dms_d(coordinate) local d = coordinates.precision.round( coordinate, 0) .. "°" return d .. "" end function convert_dec2dms_dm(coordinate) -- {{#expr:{{{1}}} mod 360}}°{{padleft:{{#expr:({{{1}}} * 600 round 0) mod 600 / 10 round 0}}|2|0}}′ local d = math.floor(coordinate % 360) .."°" local m = string.format( "%02d′", math.round( math.round(coordinate * 600) % 600 / 10) ) return d .. m end function convert_dec2dms_dms(coordinate) --{{#expr:(((({{{1|0}}}) * 3600) round 0) / 3600) mod 360}}°{{padleft:{{#expr:(((3600 * ({{{1|0}}})) round 0) / 60) mod 60}}|2|0}}′{{padleft:{{#expr:((360000 * ({{{1|0}}})) round -2) mod 6000 div 100}}|2|0}}″ local d = math.floor(coordinate % 360) .. "°" local m = string.format( "%02d′", math.floor(60 * coordinate) % 60 ) local s = string.format( "%02d″", (coordinates.precision.round(360000 * coordinate, -2) % 6000) / 100 ) return d .. m .. s end --- Convert a latitude or longitude to the DMS format function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision) -- {{Coord/dec2dms/{{{4}}}|{{#ifexpr:{{{1}}} >= 0||-}}{{{1}}}}}{{#ifexpr:{{{1}}} >= 0|{{{2}}}|{{{3}}}}} local coord = tonumber(coordinate) or 0 local postfix if coord >= 0 then postfix = firstPostfix else postfix = secondPostfix end 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 -- return "" .. globalFrame:expandTemplate{ title = 'coord/dec2dms', args = {coordinate, firstPostfix, secondPostfix, precision}} end --- Convert DMS into a N or E decimal coordinate -- @param coordinate direction. either "N" "S" "E" or "W" -- @param degrees: string or number -- @param minutes: string or number -- @param seconds: string or number -- @return a number with the N or E normalized decimal coordinate of the input function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str) local degrees = tonumber(degrees_str) or 0 local minutes = tonumber(minutes_str) or 0 local seconds = tonumber(seconds_str) or 0 -- {{#expr:{{#switch:{{{1}}}|N|E=1|S|W=-1}}*({{{2|0}}}+({{{3|0}}}+{{{4|0}}}/60)/60) round {{{precdec|{{#if:{{{4|}}}|5|{{#if:{{{3|}}}|3|0}}}}+{{precision1|{{{4|{{{3|{{{2}}}}}}}}}}}}}}}} local factor if direction == "N" or direction == "E" then factor = 1 else factor = -1 end local precision = 0 if not isEmpty(seconds_str) then precision = 5 + math_mod._precision(seconds_str) elseif not isEmpty(minutes_str) then precision = 3 + math_mod._precision(minutes_str) else precision = math_mod._precision(degrees_str) end -- nil -> 0 local decimal = factor * (degrees+(minutes+seconds/60)/60) return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based. --return "" .. globalFrame:expandTemplate{ title = 'coord/dms2dec', args = {direction, degrees, minutes, seconds}} end --- TODO not yet in use function validateDegreesLatitude(degrees) if 0+tonumber(degrees) > 90 then return "latd>90" end if 0+tonumber(degrees) < -90 then return "latd<-90" end return true end --- TODO not yet in use function validateDegreesLongtitude(degrees) if 0+tonumber(degrees) >= 360 then return "longd>=360" end if 0+tonumber(degrees) <= -360 then return "longd<=-360" end return true end --- TODO not yet in use function validateMinutes(minutes) if 0+tonumber(minutes) >= 60 then return "m>=60" end if 0+tonumber(minutes) < 0 then return "m<0" end return true end --- TODO not yet in use function validateSeconds(seconds) if 0+tonumber(seconds) >= 60 then return "s>=60" end if 0+tonumber(seconds) < 0 then return "s<0" end return true end --- The display function we exposed to Module:Coordinates function coordinates.input(frame) globalFrame = frame; return formatTest(frame.args) end --- The dec2dms function exposed to Module:Coordinates function coordinates.dec2dms(frame) globalFrame = frame local coordinate = frame.args local firstPostfix = frame.args local secondPostfix = frame.args local precision = frame.args return convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision) end --- The dec2dms function exposed to Module:Coordinates function coordinates.dms2dec(frame) globalFrame = frame local direction = frame.args local degrees = frame.args local minutes = frame.args local seconds = frame.args return convert_dms2dec(direction, degrees, minutes, seconds) end --- This is used by {{coord}}. function coordinates.coord(frame) globalFrame = frame local args = frame.args for i=1,10 do if ( nil == args ) then args = "" end end local contents = formatTest(args) local Notes = args.notes or "" local Display = args.display or "inline" local text if ( "title" ~= Display ) then text = displayinline(contents) else text = displaytitle(contents) end return text .. Notes end return coordinatesCategories: