Modul:Coordinates
This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
Dieses Modul enthält Funktionen zur Konvertierung von geografischen Koordinaten.
Lua-Fehler in Modul:Failsafe, Zeile 62: attempt to index field 'wikibase' (a nil value)
Funktionen
Siehe Quelltext oder die Dokumentation.
Benötigte weitere Module
Dieses Modul benötigt folgende weitere Module: Coordinates/i18n
Verwendung in anderen Modulen
Dieses Modul ist notwendig für die Ausführung folgender Module. Bei Anpassungen sollte die Funktionstüchtigkeit der folgenden Module geprüft werden. Benutze dazu auch diese Tracking-Kategorie um Fehler zu finden, die sich dann auf Artikel auswirken:
- GeoData • Location map • Mapframe • Marker utilities • Quickbar Position
- Modul benötigt das Modul Coordinates – Wartungskategorie, in der nochmals alle Module gelistet sind, die von diesem Modul abhängig sind.
--[[ 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