Modul:GeoData
| 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 benutzt die Wikidata-Eigenschaften:
|
Dieses Modul enthält Funktionen zur Anzeige von geografischen Koordinaten für die Vorlagen {{Coord}} und {{GeoData}}.
Die Modulbeschreibung befindet sich in....
Lua-Fehler in Modul:Failsafe, Zeile 62: attempt to index field 'wikibase' (a nil value)
Benötigte weitere Module
Dieses Modul benötigt folgende weitere Module: Coordinates • CountryData • GeoData/i18n • GeoData/Params • Great circle distance • Wikidata utilities
-- Functions for the presentation of locations coordinate pairs
-- module variable and administration
local gd = {
moduleInterface = {
suite = 'GeoData',
serial = '2022-10-22',
item = 94472936
}
}
-- module import
-- require( 'strict' )
local cd = require( 'Module:Coordinates' )
local cm = require( 'Module:CountryData' )
local gc = require( 'Module:Great circle distance' )
local gi = require( 'Module:GeoData/i18n' )
local gp = require( 'Module:GeoData/Params' )
local lp = require( 'Module:LinkPhone' )
local wu = require( 'Module:Wikidata utilities' )
local possibleFormats = { 'f1', 'f2', 'f3', 'f4', 'dec' }
local lengthUnits = {
["1"] = { 'm', 1 },
Q11573 = { 'm', 1 },
Q828224 = { 'km', 1000 },
Q174789 = { 'mm', 0.001 },
Q218593 = { 'in', 0.0254 },
Q253276 = { 'mi', 1610 },
Q3710 = { 'ft', 0.3048 },
Q174728 = { 'cm', 0.01 },
Q848856 = { 'dam', 10 },
Q200323 = { 'dm', 0.1 }
}
local scalesByType = {
adm1st = 1000000,
adm2nd = 300000,
adm3rd = 100000,
airport = 30000,
city = 100000,
country = 10000000,
edu = 10000,
event = 50000,
forest = 50000,
glacier = 50000,
isle = 100000,
landmark = 10000,
mountain = 100000,
pass = 10000,
railwaystation = 10000,
river = 100000,
satellite = 10000000,
waterbody = 100000,
camera = 10000,
default = 300000
}
-- zoom level 19 -> 1:1000, 0 -> 500000000
local maxZoomLevel = 19
local scales = { 1000, 2000, 4000, 8000, 15000, 35000, 70000, 150000, 250000,
500000, 1000000, 2000000, 4000000, 10000000, 15000000, 35000000, 70000000,
150000000, 250000000, 500000000 }
-- Local helper functions
-- Helper function isSet
local function isSet( param )
return param and param ~= '';
end
local function setValue( value, default )
return isSet( value ) and value or default
end
-- Helper function roundScale
local function roundScale( scale )
if scale <= scales[ 1 ] then
return scales[ 1 ]
end
for i = 2, #scales, 1 do
if scale > scales[ i - 1 ] and scale <= scales[ i ] then
return scales[ i ]
end
end
return scales[ #scales ]
end
-- Helper function getZoomFromScale
local function getZoomFromScale( scale )
if scale <= scales[ 1 ] then
return maxZoomLevel
end
for i = 2, #scales, 1 do
if scale > scales[ i - 1 ] and scale <= scales[ i ] then
return maxZoomLevel + 2 - i -- because i starts from 2
end
end
return 0
end
-- helper function round
-- n: 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
local function checkFormat( f )
if type( f ) == 'table' then
return f
end
for i, fmt in ipairs( possibleFormats ) do
if fmt == f then
return f
end
end
return 'f1'
end
local function checkNumber( s )
return tonumber( s ) or ''
end
-- helper function getPrecision
-- returns integer precision number
-- possible values: numbers, D, DM, DMS
local function getPrecision( prec )
local p = tonumber( prec )
if p then
p = round( p, 0 )
if p < -1 then
p = -1
elseif p == 1 then
p = 2
elseif p == 3 then
p = 4
elseif p > 6 then
p = 6 -- maximum 6 decimals
end
return p
else
p = prec and prec:upper() or 'DMS'
if p == 'D' then
return 0
elseif p == 'DM' then
return 2
elseif p == 'DMS' then
return 4
else
return ''
end
end
end
-- getPrecisionFromSize gives precision number for toDMS function calculated
-- from the maximum of the dimension of a geographic object or the measurement
-- error of the coordinate (as a radius)
-- 1° equals about 1852 meters
-- 1852 meters give precision = 2 (1')
local function getPrecisionFromSize( dim, err )
local d = tonumber( dim ) or 4000
if err ~= '' then
local m = tonumber( err ) or 0
if m > d / 2 then
d = 2 * m
end
end
if d < 1 then
d = 1
end
-- 2 * 60 * 1852 = 222240
d = math.ceil ( math.log10 ( 222240 / d ) )
if d < 0 then
d = 0
elseif d > 5 then
d = 5
end
if d == 1 then
d = 2
elseif d == 3 then
d = 4
end
return d
end
-- getExtraParameters returns a string with extra Geohack parameters as it is
-- used by Special:Mapsources and {{#coordinates}}
local function getExtraParameters( args )
local s = 'scale:' .. args.scale
if args.type ~= '' then
s = s .. '_type:' .. args.type
end
if args.dim ~= '' then
s = s .. '_dim:' .. args.dim
end
s = s .. '_globe:' .. args.globe
if args.region ~= '' then
s = s .. '_region:' .. args.region
end
return s
end
-- Helper function for microformat creation
local function getMicroformat( aName, addClasses )
return '<span class="h-card'
.. ( isSet( addClasses ) and ( ' ' .. addClasses ) or '' )
.. '" style="display: none;">'
.. '<span class="p-geo geo">'
.. '<span class="p-latitude latitude">$5</span>, '
.. '<span class="p-longitude longitude">$6</span>'
.. '</span>'
.. '<span class="p-name">' ..aName .. '</span>'
.. '</span>'
end
-- Coordinates shown as article indicator
-- Helper function indicatorTable returns a table containing coordinate text inset
local function indicatorTable( inset, aName, zoom, lat, long, country )
local function data( s )
return isSet( s ) and s or nil
end
local c = mw.ustring.gsub( gi.titles.coordinates, '($1)', aName)
local m = mw.ustring.gsub( gi.titles.mapsources, '($1)', aName)
local adm1st
if country.id ~= '' then
adm1st = country.adm1st or cm.getAdm1st( country.id )
inset = inset:gsub( '_region%%3A%a+&',
'_region%%3A' .. ( adm1st or country.iso_3166 ) .. '&' )
end
local html = mw.html.create( 'div' )
:addClass( 'voy-coord-indicator' )
:attr( 'data-zoom', zoom )
:attr( 'data-lat', lat )
:attr( 'data-lon', long )
:node( mw.html.create( 'div' )
:addClass( 'voy-icon' )
:attr( 'title', c )
:node( mw.html.create( 'span' )
:addClass( 'voy-map-globe-default' )
:wikitext( gi.titles.globeDefault )
)
:node( mw.html.create( 'span' )
:addClass( 'voy-map-globe-js' )
:css( 'display', 'none' )
:wikitext( gi.titles.globeJS )
)
)
:node( mw.html.create( 'div' )
:attr( 'class', 'voy-coords printNoLink plainlinks' )
:attr( 'title', m )
:wikitext( inset )
)
-- adding country- and region-related data
if country.id ~= '' then
if country.cc ~= '' then
country.trunkPrefix = lp.getTrunkPrefix( country.cc )
end
html:attr( 'data-country', data( country.iso_3166 ) )
:attr( 'data-country-name', data( country.country ) )
:attr( 'data-adm1st', adm1st )
:attr( 'data-country-calling-code', data( country.cc ) )
:attr( 'data-trunk-prefix', data( country.trunkPrefix ) )
:attr( 'data-lang', data( country.lang ) )
:attr( 'data-lang-name', data( country.langName ) )
:attr( 'data-currency', data( country.addCurrency ) )
:attr( 'data-dir', data( country.isRTL and 'rtl' or 'ltr' ) )
end
return tostring( html )
end
-- getLengthFromWD returns a length by property from WD
local function getLengthFromWD( id, p )
local w = wu.getValue( id, p )
if w == '' then
return 0
end
local a = tonumber( w.amount ) or 0
local u = lengthUnits[ w.unit ]
if u then
u = u[ 2 ]
else
u = wu.getValue( w.unit, gi.properties.conversionToSI )
u = u == '' and 0 or tonumber( u.amount ) or 0
end
return a * u
end
-- Helper function to get data from Wikidata by id
-- gets name, dimension, latitude, and longitude
-- returns an error if no coordinates are available
local function getParamsFromWD( args )
local coordState = {
err = not isSet( args.lat ) or not isSet( args.long ),
faultyCoordinate = false,
distanceErr = 0,
fromWD = false
}
if not coordState.err then
local objLat = cd.toDec( args.lat, 'lat', 8 )
args.lat = objLat.dec
local objLong = cd.toDec( args.long, 'long', 8 )
args.long = objLong.dec
coordState.err = ( objLat.error + objLong.error ) > 0
coordState.faultyCoordinate = coordState.err
end
if not args.wikidata then
return args, coordState
end
if not isSet( args.name ) then
args.name = mw.wikibase.label( args.wikidata ) or ''
end
local length = math.abs( tonumber( args.dim ) or 0 )
if length < 1 then
length = getLengthFromWD( args.wikidata, gi.properties.length )
local width = getLengthFromWD( args.wikidata, gi.properties.width )
if width > length then
length = width
end
end
args.dim = length < 1 and '' or round( length )
-- getting coordinates in any case
local c = wu.getValue( args.wikidata, gi.properties.centerCoordinates )
if c == '' then
c = wu.getValue( args.wikidata, gi.properties.coordinates )
end
if c ~= '' then
c.latitude = tonumber( c.latitude )
c.longitude = tonumber( c.longitude )
-- 1° ~ 1842 m
c.precision = ( tonumber( c.precision ) or 0 ) * 1842
if not isSet( args.prec ) and c.precision >= 1 then
args.prec = round( c.precision )
end
if coordState.err then
args.lat = c.latitude
args.long = c.longitude
coordState.err = false
coordState.faultyCoordinate = false
coordState.fromWD = true
else
-- GeoData and Wikidata positions may differ
coordState.distanceErr = gc.getGcd( args.lat, args.long,
c.latitude, c.longitude )
end
end
return args, coordState
end
local function makeCoordinates( args, pattern )
local s, lat, long
if args.format == 'dec' then
s, lat, long = cd.getDecGeoLink( pattern, args.lat, args.long, args.precision )
else
s, lat, long = cd.getGeoLink( pattern, args.lat, args.long, '', '', '', '',
args.precision, args.format )
end
return s
end
-- Display the coordinates
local function displayCoords( args, frame, isNs0 )
args.lat = args[ 1 ] or args.lat or args.NS or '' -- NS/EW: fallback
args.long = args[ 2 ] or args.long or args.EW or ''
args.name = args.name or ''
args.display = args.display and args.display:lower() or 'inline'
args.format = checkFormat( args.format and args.format:lower() or 'f1' )
args.addMf = args.addMf and args.addMf:lower() or ''
-- if 'true' then add microformat and {{#coordinates}}
args.region = args.region and args.region:upper() or ''
args.dim = checkNumber( args.dim or '' )
args.globe = args.globe and args.globe:lower() or 'earth'
args.precision = getPrecision( args.precision or '' ) -- display precission
args.prec = checkNumber( args.prec or '' ) -- measurement error
args.radius = checkNumber( args.radius or '' )
args.scale = checkNumber( args.scale or '' )
args.zoom = checkNumber( args.zoom or '' )
args.type = args.type and args.type:lower() or ''
if not isSet( args.wikidata ) or not mw.wikibase.isValidEntityId( args.wikidata ) then
args.wikidata = nil
end
if not scalesByType[ args.type ] then
args.type = ''
end
local cat = '' -- tracking categories
-- Parameter check
local function fIsInline( s ) -- inline in any case
return s:find( 'inline' )
or s == 'i' or s == 'it' or s == 'ti'
end
local isInline = fIsInline( args.display )
local function fIsInTitle( s ) -- intitle in any case
return s:find( 'title' ) or s == 't' or s == 'it' or s == 'ti'
end
local isInTitle = fIsInTitle( args.display )
if not isInline and not isInTitle then
isInline = true
end
if isInTitle then
if not args.wikidata then
args.wikidata = mw.wikibase.getEntityIdForCurrentPage()
end
if not isSet( args.name ) then
args.name = mw.title.getCurrentTitle().subpageText
end
end
local coordState
args, coordState = getParamsFromWD( args )
if coordState.err then
local m = coordState.faultyCoordinate and gi.errorMsg.faultyCoordinate or ''
if isInTitle then
return m .. gi.categories.geoWithoutCoords
else
return m .. gi.categories.coordWithoutCoords
end
end
if coordState.distanceErr > 50 then
cat = cat .. gi.categories.differentPositions50
elseif coordState.distanceErr > 25 then
cat = cat .. gi.categories.differentPositions25
elseif coordState.distanceErr > 10 then
cat = cat .. gi.categories.differentPositions
end
if coordState.fromWD then
cat = cat .. gi.categories.fromWikidata
end
local country = cm.getCountryData( args.wikidata, nil )
if country.fromWD then
cat = cat .. gi.categories.countryFromWD
end
if not isSet( args.region ) then
args.region = country.iso_3166
end
if args.name == '' then
args.name = gi.errorMsg.missingName
cat = cat .. gi.categories.coordWithoutName
end
if args.scale == '' and args.type ~= '' then
args.scale = scalesByType[ args.type ]
end
if args.zoom ~= '' then
args.zoom = round( args.zoom )
if args.zoom < 0 and args.zoom > maxZoomLevel then
args.zoom = ''
end
end
if args.dim == '' and args.radius == '' and args.zoom == ''
and args.scale == '' then
args.dim = 4000
cat = cat .. gi.categories.withoutScale
end
if args.dim == '' and args.radius ~= '' then
args.dim = round( 2 * args.radius )
end
if args.scale == '' and args.zoom ~= '' then
args.scale = scales[ maxZoomLevel + 1 - args.zoom ]
end
if args.dim == '' and args.scale ~= '' then
args.dim = args.scale / 5
end
if args.scale == '' then
args.scale = 5 * args.dim
end
args.scale = roundScale( args.scale )
if args.precision == '' then -- mainly for geo/geoData
args.precision = getPrecisionFromSize( args.dim, args.prec )
end
local extra = getExtraParameters( args )
local mf = ''
if args.addMf == 'true' then -- adding microformat
mf = getMicroformat( args.name, 'listing-coordinates' )
end
-- inline coordinates
local v = ''
if isInline then
v = '<span class="printNoLink plainlinks'
.. '">' .. mf
.. '[' .. gi.coordURL .. '$1_$2_' .. mw.uri.encode( extra, 'QUERY' )
.. '&locname=' .. mw.uri.encode( args.name, 'QUERY' )
.. ' <span class="voy-coord-style" title="' .. gi.titles.latitude .. '">$3</span>'
.. ' <span class="voy-coord-style" title="' .. gi.titles.longitude .. '">$4</span>]'
.. '</span>'
v = makeCoordinates( args, v )
end
-- indicator/in title coordinates
local w = ''
if isInTitle then
w = mf
.. '[' .. gi.coordURL .. '$1_$2_' .. mw.uri.encode( extra, 'QUERY' )
.. '&locname=' .. mw.uri.encode( args.name, 'QUERY' ) .. ' $3<br />$4]'
w = makeCoordinates( args, w )
w = indicatorTable( w, args.name, getZoomFromScale( args.scale ),
args.lat, args.long, country )
end
local parserFc = ''
local parserFcArgs = { args.lat, args.long, extra, name = args.name }
if isInTitle then
table.insert( parserFcArgs, 1, 'primary' )
end
if frame and args.addMf == 'true' then -- adding {{#coordinates}}
parserFc = frame:callParserFunction{ name = '#coordinates',
args = parserFcArgs }
end
v = v .. w .. parserFc .. cat
.. ( isNs0 and wu.getCategories( gi.categories.properties ) or '' )
return v
end
-- [[template:Coord]] template
-- format: f1 ... f4, dec
function gd.coord( frame )
local args = frame:getParent().args
local ns = mw.title.getCurrentTitle().namespace
args.display = 'inline'
args.precision = setValue( args.precision, '4' )
args.addMf = 'true'
-- with {{#coordinates}} and microformat
return displayCoords( args, frame, ns == 0 ) .. gp.checkParams( args, gp.coord )
end
-- [[template:Geo]] template
-- if id is set then data are alternatively used from Wikidata
-- includes primary {{#coordinate}}
-- calculates precission from the size of the geographical object
function gd.geo( frame )
local args = frame:getParent().args
local title = mw.title.getCurrentTitle()
local ns = title.namespace
local categs = ''
args.display = 'title'
args.format = 'f2'
args.name = setValue( args.name, title.subpageText )
args.addMf = frame.args.minerva and 'false' or 'true'
-- prevent 2nd #coordinates call with Minerva skin
if ns == 0 then
categs = categs .. gi.categories.hasGeo
end
return displayCoords( args, frame, ns == 0 )
.. gp.checkParams( args, gp.geo ) .. categs
end
return gd
Kategorien:
- Seiten mit Skriptfehlern
- Modules for general use
- Module:Module, die Wikidata benutzen
- Modul benötigt das Modul Coordinates
- Modul benötigt das Modul CountryData
- Modul benötigt das Modul GeoData/i18n
- Modul benötigt das Modul GeoData/Params
- Modul benötigt das Modul Great circle distance
- Modul benötigt das Modul Wikidata utilities
- Vorlagen:Koordinaten