Toggle search
Search
Toggle menu
notifications
Toggle personal menu
Editing
Module:Coordinates
From Dive Atlas
Views
Read
Edit
View history
associated-pages
Module
Discussion
More actions
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
-- Coordinate conversion procedures -- This module is intended to replace the functionality of MapSources extension -- designed for use both in modules and for direct invoking -- functions for use in modules -- toDec( coord, aDir, prec ) -- returns a decimal coordinate from decimal or deg-min-sec-letter strings -- getDMSString( coord, prec, aDir, plus, minus, aFormat ) -- formats a decimal/dms coordinate to a deg-min-sec-letter string -- getGeoLink( pattern, lat, long, plusLat, plusLong, minusLat, minusLong, prec, aFormat ) -- converts a complete dms geographic coordinate without reapplying the toDec function -- getDecGeoLink( pattern, lat, long, prec ) -- converts a complete decimal geographic coordinate without reapplying the toDec function -- Invokable functions -- dec2dms( frame ) -- dms2dec( frame ) -- geoLink( frame ) -- documentation local Coordinates = { suite = 'Coordinates', serial = '2020-08-18', item = 7348344 } -- module import local ci = require( 'Module:Coordinates/i18n' ) -- module variable local cd = {} -- helper function getErrorMsg -- returns error message by error number which local function getErrorMsg( which ) if which == 'noError' or which == 0 then return ci.errorMsg.noError elseif which > #ci.errorMsg then return ci.errorMsg.unknown else return ci.errorMsg[ which ] end end -- helper function round -- num: value to round -- idp: number of digits after the decimal point local function round( n, idp ) local m = 10^( idp or 0 ) if n >= 0 then return math.floor( n * m + 0.5 ) / m else return math.ceil( n * m - 0.5 ) / m end end -- helper function getPrecision -- returns integer precision number -- possible values: numbers, D, DM, DMS -- default result: 4 local function getPrecision( prec ) local p = tonumber( prec ) if p then p = round( p, 0 ) if p < -1 then p = -1 elseif p > 8 then -- maximum 8 decimals p = 8 end return p else p = prec and prec:upper() or 'DMS' if p == 'D' then return 0 elseif p == 'DM' then return 2 else return 4 -- DMS = default end end end -- helper function toDMS -- splits a decimal coordinate dec to degree, minute and second depending on the -- precision. prec <= 0 means only degree, prec < 3 degree and minute, and so on -- returns a result array local function toDMS( dec, prec ) local result = { dec = 0, deg = 0, min = 0, sec = 0, sign = 1, NS = 'N', EW = 'E', prec = getPrecision( prec ) } local p = result.prec result.dec = round( dec, 8 ) if result.dec < 0 then result.sign = -1 result.NS = 'S' result.EW = 'W' end local angle = math.abs( round( result.dec, p ) ) result.deg = math.floor( angle ) result.min = ( angle - result.deg ) * 60 if p > 4 then result.sec = round( ( result.min - math.floor( result.min ) ) * 60, p - 4 ) else result.sec = round( ( result.min - math.floor( result.min ) ) * 60 ) end result.min = math.floor( result.min ) if result.sec >= 60 then result.sec = result.sec - 60 result.min = result.min + 1 end if p < 3 and result.sec >= 30 then result.min = result.min + 1 end if p < 3 then result.sec = 0 end if result.min >= 60 then result.min = result.min - 60 result.deg = result.deg + 1 end if p < 1 and result.min >= 30 then result.deg = result.deg + 1 end if p < 1 then result.min = 0 end return result end -- toDec converts decimal and hexagesimal DMS formatted coordinates to decimal -- coordinates -- input -- dec: coordinate -- prec: number of digits after the decimal point -- aDir: lat/long directions -- returns a result array -- output -- dec: decimal value -- error: error number -- parts: number of DMS parts, usually 1 (already decimal) ... 4 function cd.toDec( coord, aDir, prec ) local result = { dec = 0, error = 0, parts = 1 } local s = mw.text.trim( coord ) if s == '' then result.error = 1 return result end -- pretest if already a decimal local dir = aDir or '' local r = tonumber( s ) if r then if dir == 'lat' and ( r < -90 or r > 90 ) then result.error = 13 return result elseif r <= -180 or r > 180 then result.error = 5 return result end result.dec = round( r, getPrecision ( prec ) ) return result end s = mw.ustring.gsub( s, '[βββ²Β΄`]', "'" ) s = s:gsub( "''", '"' ) s = mw.ustring.gsub( s, '[βββ³]', '"' ) s = mw.ustring.gsub( s, '[βββ]', '-' ) s = mw.ustring.upper( mw.ustring.gsub( s, '[_/%c%s%z]', ' ' ) ) local mStr = '^[ %.%-Β°\'"0-9' -- string to match, illegal characters? for key, value in pairs( ci.inputLetters ) do mStr = mStr .. key end mStr = mStr .. ']+$' if not mw.ustring.match( s, mStr ) then result.error = 3 return result end s = mw.ustring.gsub( s, '(%u)', ' %1' ) s = mw.ustring.gsub( s, '%s*([Β°"\'])', '%1 ' ) s = mw.text.split( s, '%s' ) for i = #s, 1, -1 do if mw.text.trim( s[ i ] ) == '' then table.remove( s, i ) end end result.parts = #s if #s < 1 or #s > 4 then result.error = 2 return result end local units = { 'Β°', "'", '"', ' ' } local res = { 0, 0, 0, 1 } -- 1 = positive direction local v local l for i = 1, #s, 1 do v = mw.ustring.gsub( s[ i ], units[ i ], '' ) if tonumber( v ) then if i > 3 then -- this position is for direction letter, not for number result.error = 4 return result end v = tonumber( v ) if i == 1 then if v <= -180 or v > 180 then result.error = 5 return result end res[ 1 ] = v elseif i == 2 or i == 3 then if v < 0 or v >= 60 then result.error = 2 + 2 * i return result end if res[ i - 1 ] ~= round( res[ i - 1 ], 0 ) then result.error = 3 + 2 * i return result end res[ i ] = v end else -- no number if i ~= #s then -- allowed only at the last position result.error = 10 return result end if res[ 1 ] < 0 then result.error = 11 return result end l = ci.inputLetters[ v ] if mw.ustring.len( v ) ~= 1 or not l then result.error = 3 return result end -- l[1]: factor -- l[2]: lat/long if ( dir == 'long' and l[ 2 ] ~= 'long' ) or ( dir == 'lat' and l[ 2 ] ~= 'lat' ) then result.error = 12 return result else dir = l[ 2 ] end res[ 4 ] = l[ 1 ] end end if dir == 'lat' and ( res[ 1 ] < -90 or res[ 1 ] > 90 ) then result.error = 13 return result end if res[ 1 ] >= 0 then result.dec = ( res[ 1 ] + res[ 2 ] / 60 + res[ 3 ] / 3600 ) * res[ 4 ] else result.dec = ( res[ 1 ] - res[ 2 ] / 60 - res[ 3 ] / 3600 ) * res[ 4 ] end result.dec = round( result.dec, getPrecision ( prec ) ) return result end -- getDMSString formats a degree-minute-second string for output in accordance -- to a given format specification -- input -- coord: decimal or hexagesimal DMS coordinate -- prec: precion of the coorninate string: D, DM, DMS -- aDir: lat/long direction to add correct direction letters -- plus: alternative direction string for positive directions -- minus: alternative direction string for negative directions -- aFormat: format array with delimiter and leadZeros values or a predefined -- dmsFormats key. Default format key is f1. -- outputs 3 results -- 1: formatted string or error message for display -- 2: decimal coordinate -- 3: absolute decimal coordinate including the direction letter like 51.2323_N function cd.getDMSString( coord, prec, aDir, aPlus, aMinus, aFormat ) local d = aDir or '' local p = aPlus or '' local m = aMinus or '' -- format local f = aFormat or 'f1' if type( f ) ~= 'table' then f = ci.dmsFormats[ f ] end local del = f.delimiter or ' ' local lz = f.leadZeros or false local c = { dec = tonumber( coord ), error = 0, parts = 1 } if not c.dec then c = cd.toDec( coord, d, 8 ) elseif c.dec <= -180 or c.dec > 180 then c.error = 5 elseif d == 'lat' and ( c.dec < -90 or c.dec > 90 ) then c.error = 5 end local l = '' local wp = '' local result = '' if c.error == 0 then local dms = toDMS( c.dec, prec ) if dms.dec < 0 and d == '' and m == '' then dms.deg = -dms.deg end if lz and dms.min < 10 then dms.min = '0' .. dms.min end if lz and dms.sec < 10 then dms.sec = '0' .. dms.sec end result = dms.deg .. 'Β°' if dms.prec > 0 then if ((dms.sec ~= '00') and (dms.sec ~= '0') and (dms.sec ~= 0)) or ((dms.min ~= '00') and (dms.min ~= '0') and (dms.min ~= 0)) then result = result .. del .. dms.min .. 'β²' end end if dms.prec > 2 and dms.prec < 5 then if (dms.sec ~= '00') and (dms.sec ~= '0') and (dms.sec ~= 0) then result = result .. del .. dms.sec .. 'β³' end end if dms.prec > 4 then -- enforce sec decimal digits even if zero local s = string.format( "%." .. dms.prec - 4 .. "fβ³", dms.sec ) if ci.decimalPoint ~= '.' then s = mw.ustring.gsub( s, '%.', ci.decimalPoint ) end result = result .. del .. s end if d == 'lat' then wp = dms.NS elseif d == 'long' then wp = dms.EW end if dms.dec >= 0 and p ~= '' then l = p elseif dms.dec < 0 and m ~= '' then l = m else l = ci.outputLetters[ wp ] end if l and l ~= '' then result = result .. del .. l end if c.parts > 1 then result = result .. ci.categories.dms end return result--, dms.dec, math.abs( dms.dec ) .. '_' .. wp else if d == 'lat' then wp = 'N' elseif d == 'long' then wp = 'E' end result = '<span class="error" title="' .. getErrorMsg( c.error ) ..'">' .. ci.errorMsg.faulty .. '</span>' .. ci.categories.faulty return result, '0', '0_' .. wp end return result end -- getGeoLink returns complete dms geographic coordinate without reapplying the toDec -- and toDMS functions. Pattern can contain placeholders $1 ... $6 -- $1: latitude in Wikipedia syntax including the direction letter like 51.2323_N -- $2: longitude in Wikipedia syntax including the direction letter like 51.2323_E -- $3: latitude in degree, minute and second format considering the strings for -- the cardinal directions and the precision -- $4: longitude in degree, minute and second format considering the strings -- for the cardinal directions and the precision -- $5: latitude -- $6: longitude -- aFormat: format array with delimiter and leadZeros values or a predefined -- dmsFormats key. Default format key is f1. -- outputs 3 results -- 1: formatted string or error message for display -- 2: decimal latitude -- 3: decimal longitude function cd.getGeoLink( pattern, lat, long, plusLat, plusLong, minusLat, minusLong, prec, aFormat ) local lat_s, lat_dec, lat_wp = cd.getDMSString( lat, prec, 'lat', plusLat, minusLat, aFormat ) local long_s, long_dec, long_wp = cd.getDMSString( long, prec, 'long', plusLong, minusLong, aFormat ) local s = pattern s = mw.ustring.gsub( s, '($1)', lat_wp ) s = mw.ustring.gsub( s, '($2)', long_wp ) s = mw.ustring.gsub( s, '($3)', lat_s ) s = mw.ustring.gsub( s, '($4)', long_s ) s = mw.ustring.gsub( s, '($5)', lat_dec ) s = mw.ustring.gsub( s, '($6)', long_dec ) return s, lat_dec, long_dec end -- getDecGeoLink returns complete decimal geographic coordinate without reapplying -- the toDec function. Pattern can contain placeholders $1 ... $4 function cd.getDecGeoLink( pattern, lat, long, prec ) local function getDec( coord, prec, aDir, aPlus, aMinus ) local l = aPlus local c = cd.toDec( coord, aDir, 8 ) if c.error == 0 then if c.dec < 0 then l = aMinus end local d = round( c.dec, prec ) .. '' if ci.decimalPoint ~= '.' then d = mw.ustring.gsub( d, '%.', ci.decimalPoint ) end return d, math.abs( c.dec ) .. '_' .. l else c.dec = '<span class="error" title="' .. getErrorMsg( c.error ) ..'">' .. ci.errorMsg.faulty .. '</span>' .. ci.categories.faulty return c.dec, '0_' .. l end end local lat_dec, lat_wp = getDec( lat, prec, 'lat', 'N', 'S' ) local long_dec, long_wp = getDec( long, prec, 'long', 'E', 'W' ) local s = pattern s = mw.ustring.gsub( s, '($1)', lat_wp) s = mw.ustring.gsub( s, '($2)', long_wp) s = mw.ustring.gsub( s, '($3)', lat_dec) s = mw.ustring.gsub( s, '($4)', long_dec) return s, lat_dec, long_dec end -- Invokable functions -- identical to MapSources #dd2dms tag -- frame input -- 1 or coord: decimal or hexagesimal coordinate -- precision: precion of the coorninate string: D, DM, DMS -- plus: alternative direction string for positive directions -- minus: alternative direction string for negative directions -- format: Predefined dmsFormats key. Default format key is f1. function cd.dec2dms( frame ) local args = frame:getParent().args args.coord = args[ 1 ] or args.coord or '' args.precision = args[ 2 ] or args.precision or '' return cd.getDMSString( args.coord, args.precision, '', args.plus, args.minus, args.format ) end -- identical to MapSources #deg2dd tag function cd.dms2dec( frame ) local args = frame:getParent().args args.coord = args[ 1 ] or args.coord or '' args.precision = args[ 2 ] or args.precision or '' local r = cd.toDec( args.coord, '', args.precision ) local s = r.dec if r.error ~= 0 then if args.coord == '' then s = ci.categories.faulty else s = '<span class="error" title="' .. getErrorMsg( r.error ) ..'">' .. ci.errorMsg.faulty .. '</span>' .. ci.categories.faulty end end return s end -- identical to MapSources #geoLink tag -- This function can be extended to add Extension:GeoData #coordinates because -- cd.getGeoLink returns lat and long, too function cd.geoLink( frame ) local args = frame:getParent().args args.pattern = args[ 1 ] or args.pattern or '' if args.pattern == '' then return errorMsg[ 14 ] end return cd.getGeoLink( args.pattern, args.lat, args.long, args.plusLat, args.plusLong, args.minusLat, args.minusLong, args.precision, args.format ) end return cd
Summary:
Please note that all contributions to Dive Atlas may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Meta:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Template used on this page:
Module:Coordinates/doc
(
edit
)