Zum Inhalt springen

Modul:GeoData: Unterschied zwischen den Versionen

Aus Wikivoyage
Inhalt gelöscht Inhalt hinzugefügt
K unterschiedliche Abweichungen
K +maxZoomLevel (= 19)
Zeile 49: Zeile 49:


-- zoom level 19 -> 1:1000, 0 -> 500000000
-- zoom level 19 -> 1:1000, 0 -> 500000000
local maxZoomLevel = 19
local scales = { 1000, 2000, 4000, 8000, 15000, 35000, 70000, 150000, 250000,
local scales = { 1000, 2000, 4000, 8000, 15000, 35000, 70000, 150000, 250000,
500000, 1000000, 2000000, 4000000, 10000000, 15000000, 35000000, 70000000,
500000, 1000000, 2000000, 4000000, 10000000, 15000000, 35000000, 70000000,
Zeile 93: Zeile 94:
-- Helper function getZoomFromScale
-- Helper function getZoomFromScale
function gd.getZoomFromScale( scale )
function gd.getZoomFromScale( scale )
if scale <= scales[1] then return 19 end
if scale <= scales[1] then return maxZoomLevel end
for i = 2, #scales, 1 do
for i = 2, #scales, 1 do
if (scale > scales[i-1]) and (scale <= scales[i])
if (scale > scales[i-1]) and (scale <= scales[i])
then return 20-i end
then return maxZoomLevel+1-i end
end
end
return 0
return 0
Zeile 376: Zeile 377:
if args.zoom ~= '' then
if args.zoom ~= '' then
args.zoom = round( args.zoom )
args.zoom = round( args.zoom )
if (args.zoom < 0) and (args.zoom > 19) then args.zoom = '' end
if (args.zoom < 0) and (args.zoom > maxZoomLevel) then args.zoom = '' end
end
end
if (args.scale == '') and (args.zoom ~= '') then
if (args.scale == '') and (args.zoom ~= '') then
args.scale = scales[20-args.zoom]
args.scale = scales[maxZoomLevel+1-args.zoom]
end
end
if (args.dim == '') and (args.scale ~= '') then
if (args.dim == '') and (args.scale ~= '') then

Version vom 8. November 2018, 10:57 Uhr

Dokumentation für das Modul GeoData[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Dieses Modul enthält Funktionen zur Anzeige von geografischen Koordinaten für die Vorlagen {{Coord}} und {{GeoData}}.

Die Modulbeschreibung befindet sich in Wikivoyage:GeoData.

Versionsbezeichnung auf Wikidata: keine Version verfügbar

Benötigte weitere Module

Dieses Modul benötigt folgende weitere Module: Coordinates • CountryData • GeoData/i18n • GeoData/Params • Great circle distance • Wikidata utilities
Hinweise
-- Functions for the presentation of locations coordinate pairs

-- module import
local cd = require( 'Module:Coordinates' )
local pm = require( 'Module:GeoData/Params' )
local gi = require( 'Module:GeoData/i18n' )
local fw = require( 'Module:FastWikidata' )
local gc = require( 'Module:Great circle distance' )

-- module variable
local gd = {}

local possibleFormats = {'f1', 'f2', 'f3', 'f4', 'dec'}

local lengthUnits = {
	['http://www.wikidata.org/entity/Q11573']  = {  'm', 1        },
	['http://www.wikidata.org/entity/Q828224'] = { 'km', 1000     },
	['http://www.wikidata.org/entity/Q174789'] = { 'mm', 0.001    },
	['http://www.wikidata.org/entity/Q218593'] = { 'in', 39.370   },
	['http://www.wikidata.org/entity/Q253276'] = { 'mi', 0.000621 },
	['http://www.wikidata.org/entity/Q3710']   = { 'ft', 3.2808   },
	['http://www.wikidata.org/entity/Q174728'] = { 'cm', 0.01     },
	['http://www.wikidata.org/entity/Q848856'] = {'dam', 10       },
	['http://www.wikidata.org/entity/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 }

-- getIso3166_1 returns ISO 3166-1 country code from WD
function gd.getIso3166_1( id )
	-- in case of the country itself P17 is set, too

	local country = fw.getId( id, 'P17' )
	if country ~= '' then return fw.getValue( country, 'P297' ) end
	return ''
end

-- getLengthFromWD returns a length by property from WD
function gd.getLengthFromWD( id, p )
	local w = fw.getValue( id, p )
	local r = 0, a, u, t

	if w ~= '' then
		a = tonumber( w.amount or '' ) or 0
		if a ~= 0 then
			u = w.unit or ''
			if u ~= '' then
				t = lengthUnits[u]
				if t then r = a * t[2] end
			end
		end
	end
	
	return r
end

-- Helper function roundScale
function gd.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
function gd.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+1-i end
	end
	return 0
end

-- Local helper functions

-- 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

	local r = false
	for i = 1, #possibleFormats, 1 do
		if possibleFormats[i] == f then r = true end
	end
	if r then return f else return 'f1' end
end

local function checkNumber( s )
	if s == '' then return s end

	local n = tonumber( s )
	if n == nil then return '' else return n end
end

-- helper function getPrecision
-- returns integer precision number
-- possible values: numbers, D, DM, DMS
local function getPrecision( prec )
	local p = tonumber( prec )
	if p ~= nil then
		p = round( p, 0 )
		if p < -1 then p = -1 end
		if p > 8 then p = 8 end -- maximum 8 decimals
		if p == 1 then p = 2 end
		if p == 3 then p = 4 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° correlates to 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 end
	if d > 5 then d = 5 end
	if d == 1 then d = 2 end
	if 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 )
	local s = '<span class="h-card'
	if (addClasses ~= nil) and (addClasses ~= '') then
		s = s .. ' ' .. addClasses
	end

	s = s .. '"><span class="p-geo geo" style="display: none;">'
			.. '<span class="p-latitude latitude">$5</span>, '
			.. '<span class="p-longitude longitude">$6</span>'
		.. '</span>'
		.. '<span class="p-name" style="display: none;">' ..aName .. '</span>'
		.. '</span>'
	
	return s
end

-- Coordinates shown as article indicator

-- Helper function indicatorTable returns a table containing coordinate text inset
local function indicatorTable( inset, aName, zoom, lat, long )
	local c = mw.ustring.gsub( gi.titles.coordinates, '($1)', aName)
	local m = mw.ustring.gsub( gi.titles.mapsources, '($1)', aName)

	return '<table class="wv-coord-indicator" cellspacing="0" data-zoom="' .. zoom
		.. '" data-lat="' .. lat .. '" data-lon="' .. long .. '"><tr>'
		.. '<td class="wv-icon" title="' .. c .. '">'
		.. '<span class="map-globe-default">' .. gi.titles.globeDefault .. '</span>'
		.. '<span class="map-globe-js" style="display: none;">' .. gi.titles.globeJS .. '</span>'
		.. '</td><td class="wv-coords printNoLink plainlinks" title="' .. m .. '">'
		.. inset .. '</td>'
		.. '</tr></table>'
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 = false,
		distanceErr = 0,
		fromWD = false
	}
	local d = 0, c

	c = math.abs( tonumber( args.dim ) or 0 )
	if c >= 1 then d = c end

	if ((args.lat == '') or (args.lat == 0)) and
		((args.long == '') or (args.long == 0)) then coordState.err = true end
	
	if args.id ~= '' then
		if args.name == '' then
			args.name = mw.wikibase.label( args.id ) or ''
		end

		c = fw.getValue( args.id, 'P625' ) -- coordinates from Wikidata
		if ((args.lat == '') or (args.lat == 0)) and
			((args.long == '') or (args.long == 0)) then
			if c == '' then coordState.err = true
			else
				args.lat = tonumber( c.latitude )
				args.long = tonumber( c.longitude )
				coordState.err = false
				coordState.fromWD = true
				if args.prec == '' then
					c.precision = (tonumber( c.precision ) or 0) * 1842
					-- 1° ~ 1842 m
					if c.precision >= 1 then args.prec = round( c.precision ) end
				end
			end
		else
			if c ~= '' then
				-- GeoData and Wikidata positions may differ
				coordState.distanceErr = gc.getGcd( cd.toDec( args.lat, 'lat', 8 ).dec,
					cd.toDec( args.long, 'long', 8 ).dec,
					tonumber( c.latitude ), tonumber( c.longitude ) )
			end
		end

		if d < 1 then
			d = gd.getLengthFromWD( args.id, 'P2043' ) -- length
			local width = gd.getLengthFromWD( args.id, 'P2049' )
			if width > d then d = width end
		end
	end
	if d < 1 then args.dim = '' else args.dim = round( d ) end

	return args, coordState
end

-- Display the coordinates

function gd.displayCoords( args, frame )
	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.id        = fw.checkId( args.id )
	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 scalesByType[args.type] then args.type = '' end
	local cat      = '' -- tracking categories

	-- Parameter check

	local function isBox( s )
		return s:find('box') ~= nil
	end

	local function fIsInline( s ) -- inline in any case
		return s:find('inline') ~= nil or s:find('box') ~= nil
			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') ~= nil or s == 't' or s == 'it' or s == 'ti'
	end

	local isInTitle = fIsInTitle( args.display )
	local articleId = mw.wikibase.getEntityIdForCurrentPage() or ''

	if isInTitle then
		if args.id == '' then args.id = articleId end
	end

	if args.region == '' then
		if args.id == '' then
			if articleId ~= '' then args.region = gd.getIso3166_1( articleId ) end
		else
			args.region = gd.getIso3166_1( args.id )
		end
	end
	-- in future add the region (P300 like EG-LX), too

	if (args.dim == '') and (args.radius ~= '') then
		args.dim = round( 2 * args.radius )
	end
	if (args.scale == '') and (args.type ~= '') then
		args.scale = scalesByType[args.type]
	end

	local coordState
	args, coordState = getParamsFromWD( args )
	if coordState.err then
		if isInTitle then
			return gi.categories.geoWithoutCoords
		else
			return 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 withoutScale = true
	if ( args.dim ~= '' ) or ( args.radius ~= '' ) or ( args.zoom ~= '' ) or
		( args.scale ~= '' ) then
		withoutScale = false
	end
	if withoutScale then
		cat = cat .. gi.categories.withoutScale
	end
	if args.name == '' then
		args.name = gi.errorMsg.missingName
		cat = cat .. gi.categories.coordWithoutName
	end

	-- for compatibility with WV/en: args.zoom
	if args.zoom ~= '' then
		args.zoom = round( args.zoom )
		if (args.zoom < 0) and (args.zoom > maxZoomLevel) then args.zoom = '' end
	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.dim == '') and (args.scale == '') then args.dim = 4000 end
	if (args.scale == '') then args.scale = 5 * args.dim end
	args.scale = gd.roundScale( args.scale )

	if args.precision == '' then -- mainly for geo/geoData
		args.precision = getPrecisionFromSize( args.dim, args.prec )
	end

	local extra = getExtraParameters( args )
	local pattern
	local parserFc
	local mf
	local aLat
	local aLong

	-- inline coordinates

	local v = ''
	if isInline then
		pattern = '<span class="printNoLink plainlinks'
		if isBox( args.display ) then
			pattern = pattern .. ' coordBox">'
		else pattern = pattern .. '">' end

		mf = ''
		if (args.addMf == 'true') and not isInTitle then -- adding microformat
			mf = getMicroformat( args.name, 'listing-coordinates' )
		end

		pattern = mf .. pattern
			.. '[' .. gi.coordURL .. '$1_$2_' .. mw.uri.encode( extra, 'QUERY' )
			.. '&locname=' .. mw.uri.encode( args.name, 'QUERY' )
			.. ' <span class="coordStyle" title="' .. gi.titles.latitude .. '">$3</span>'
			.. ' <span class="coordStyle" title="' .. gi.titles.longitude .. '">$4</span>]'
			.. '</span>'

		if args.format == 'dec' then
			v, aLat, aLong =
				cd.getDecGeoLink( pattern, args.lat, args.long, args.precision )
		else
			v, aLat, aLong =
				cd.getGeoLink( pattern, args.lat, args.long, '', '', '', '',
				args.precision, args.format )
		end

		-- adding {{#coordinates}}
		parserFc = ''
		if (frame ~= nil) and (args.addMf == 'true') and not isInTitle then
			parserFc = frame:callParserFunction{ name = '#coordinates',
			args = { aLat, aLong, extra, name = args.name } }
		end

		v = v .. parserFc .. cat
	end

	-- indicator/in title coordinates

	local w = ''
	if isInTitle then

		mf = ''
		if args.addMf == 'true' then -- adding microformat
			mf = getMicroformat( args.name, '' )
		end

		pattern = mf
			.. '[' .. gi.coordURL .. '$1_$2_' .. mw.uri.encode( extra, 'QUERY' )
			.. '&locname=' .. mw.uri.encode( args.name, 'QUERY' ) .. ' $3<br />$4]'
	
		if args.format == 'dec' then
			w, aLat, aLong =
				cd.getDecGeoLink( pattern, args.lat, args.long, args.precision )
		else
			w, aLat, aLong =
				cd.getGeoLink( pattern, args.lat, args.long, '', '', '', '',
				args.precision, args.format )
		end

		parserFc = ''
		if (frame ~= nil) and (args.addMf == 'true') then -- adding {{#coordinates}}
			parserFc = frame:callParserFunction{ name = '#coordinates',
			args = { 'primary', aLat, aLong, extra, name = args.name } }
		end
		
		w = indicatorTable( w, args.name, gd.getZoomFromScale( args.scale ), args.lat, args.long )
			.. parserFc .. cat
	end

	return v .. w
end

-- [[template:Coord]] template
-- format: f1 ... f4, dec
function gd.coord( frame )
	local args     = frame:getParent().args

	args.display   = args.display or 'inline'
	args.precision = args.precision or '4'
	args.addMf     = args.addMf or 'true' -- with {{#coordinates}} and microformat
	
	return gd.displayCoords( args, frame ) .. pm._checkParams( args, pm._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()

	args.display   = args.display or 'title'
	args.format    = args.format or 'f2'
	args.addMf     = 'true'
	args.name      = args.name or title.subpageText
	if frame.args.minerva then args.addMf = 'false' end
	
	categs         = ''
	args.elev      = args.elev or ''
	args.elevMin   = args.elevMin or ''
	args.elevMax   = args.elevMax or ''
	if args.elev .. args.elevMin .. args.elevMax ~= '' then
		categs = gi.categories.elevationUsed
	end
	if title.namespace == 0 then
		categs = categs .. gi.categories.hasGeo
	end

	return gd.displayCoords( args, frame ) .. pm._checkParams( args, pm._geo )
		.. categs
end

return gd