Módulo:Coordinates
Apariencia
Version check
Designación de la versión en Wikidata: 2022-10-22
Uso
Esta documentación está transcluida desde Módulo:Coordinates/doc.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.
--[[
Coordinate conversion procedures
This module is intended to replace the functionality of MapSources extension
Redesign of my own MapSources_math.php
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 )
Additional functions in Module:GeoData
]]--
-- module import
-- require( 'strict' )
local ci = require( 'Module:Coordinates/i18n' )
-- module variable and administration
local cd = {
moduleInterface = {
suite = 'Coordinates',
serial = '2022-10-22',
item = 7348344
}
}
-- 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 mx = dir == 'lat' and 90 or 180
local r = tonumber( s )
if r then
if r < -mx or r > mx 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 < -mx or v > mx 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 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 ) )
if result.dec < -mx or result.dec > mx or result.dec <= -180 then
result.error = 5
return result
end
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
result = result .. del .. dms.min .. '′'
end
if dms.prec > 2 and dms.prec < 5 then
result = result .. del .. dms.sec .. '″'
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.precision or ''
local r, dec, absol = cd.getDMSString( args.coord, args.precision, '',
args.plus, args.minus, args.format )
return r
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.precision or ''
local r = cd.toDec( args.coord, '', args.precision )
local s = r.dec
if r.error ~= 0 then
s = '<span class="error" title="' .. getErrorMsg( r.error ) ..'">'
.. ci.errorMsg.faulty .. '</span>' .. ci.categories.faulty
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