Modul:Location map

Aus Wikivoyage
Template-info.png Dokumentation für das Modul Location map[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Das Modul Location map stellt Funktionen zur Erstellung von Positionskarten bereit. Technische Hintergrundinformationen befinden sich in Wikivoyage:Location map.

Funktionen

Lagekarte von Ägypten
Golf von
Suez
Ägypten
Lagekarte der Welt
Kairo
Kairo
Orte in Ägypten

Kartenfunktionen

function locMap.locationMap(frame)

Stellt eine Karte mit einem Ort dar. Es können weitere Orte und Objekte eingefügt werden.

function locMap.addLocation(frame)

Ergänzt einen weiteren Ort auf der Karte.

function locMap.addObject(frame)

Fügt ein beliebiges Objekt in die Karte ein.

Dokumentation regionaler Kartendaten

function locMap.getMapValue(frame)

Gibt den Wert eines Parameters einer Kartendefinition aus.

function locMap.getMapValueSet(frame)

Erstellt eine Tabelle mit den Parametern einer Kartendefinition.

Benötigte weitere Module

Dieses Modul benötigt folgende weitere Module: Coordinates • Location map/i18n • Location map/Params • Location map/styles.css

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:

Hinweise
-- documentation
local LocationMap = {
	suite  = 'Location map',
	serial = '2022-03-10',
	item   = 15934920
}

-- module import
require( 'Module:No globals' )
local cd = require( 'Module:Coordinates' )
local li = require( 'Module:Location map/i18n' )
local lp = require( 'Module:Location map/Params' )

-- module variable
local locMap = {
	maintenance = {}
}

-- Local functions, please do not call them directly

local function split( s )
	local tb = mw.text.split( s, ';' )
	for i = #tb, 1, -1 do
		tb[ i ] = mw.text.trim( tb[ i ] )
		if tb[ i ] == '' then
			table.remove( tb, i )
		end
	end
	return tb
end

local function getClass( style, list )
    local tb = split( style )
    local class, rule
    for i = #tb, 1, -1 do
        rule = tb[ i ]
        if list[ rule ] then
            class = list[ rule ]
            table.remove( tb, i )
        end
    end
    return class, table.concat( tb, '; ' )
end

local function addMaintenance( s )
	if s and s ~= '' then
		table.insert( locMap.maintenance, s )
	end
end

local function getMaintenance()
	local m = table.concat( locMap.maintenance, ' ' )
	if m ~= '' then
		m = '<span class="error">' .. m .. '</span>'
	end
	return m
end

local function isSet( s )
	if not s then
		return s
	end
	s = mw.text.trim( s )
	if s ~= '' then
		return s
	else
		return nil
	end
end

local function round( n )
	return math.floor( n * 100 + 0.5 ) / 100
end

local function setLocation( args )
	local lmarksize = math.floor( args.marksize + 0.5 )
	local msize = math.floor( ( args.marksize - 1 ) / 2 + 0.5 )
	local msize3 = math.floor( ( args.marksize + 2 ) / 2 + 0.5 )
	local msize5 = math.floor( ( args.marksize + 4 ) / 2 + 0.5 )
	local centerPosition = -msize .. 'px'

	-- create marker box
	local markerBox = mw.html.create( 'div' )
		:addClass( 'voy-locmap-marker-box' )
		:css( { top = round( args.y * 100 ) .. '%',
			left = round( args.x * 100 ) .. '%' } )

	-- add marker symbol
	if args.mark ~= 'none' then
		markerBox:node( mw.html.create( 'div' )
			:addClass( 'voy-locmap-marker-symbol' )
			:css( {
				top = centerPosition,
				left = centerPosition,
				[ 'min-width' ] = lmarksize .. 'px',
				[ 'min-height' ] = lmarksize .. 'px'
			} )
			:wikitext( mw.ustring.format( '[[File:%s|%sx%spx|top|class=noviewer notpageimage|link=%s|%s]]',
				args.mark, lmarksize, lmarksize, args.name, args.name) )
		)
	end

	-- add label
	local wrapClass, labelClass
	if args.label ~= '' and args.label ~= 'none' then
		if args.labelWrap == 'manual' then
			wrapClass = 'voy-locmap-marker-label-nowrap'
		end

		local styles = {}
		local pos = li.labelPositions[ args.labelPosition ]
		if pos then
			pos = pos:gsub( 'msize_', msize )
				:gsub( 'msize3_', msize3 )
				:gsub( 'msize5_', msize5 )
			table.insert( styles, pos )
		else
			-- estimation of posititon
			if args.y <= 0.5 then
				table.insert( styles, li.labelPositions.yTop:format( msize3 ) )
			else
				table.insert( styles, li.labelPositions.yBottom:format( msize3 ) )
			end
			if args.x < 0.25 then
				table.insert( styles,
					li.labelPositions.xLeft:format( math.floor( 3 - 60 * args.x ) / 10 ) )
			elseif args.x <= 0.75 then
				table.insert( styles, li.labelPositions.xCenter )
			else
				table.insert( styles,
					li.labelPositions.xRight:format( math.floor( 3 - 60 * ( 1 - args.x ) ) / 10 ) )
			end
		end
		labelClass, args.labelStyle = getClass( args.labelStyle, li.labelClasses )
		table.insert( styles, args.labelStyle )

		markerBox:node( mw.html.create( 'div' )
			:addClass( 'voy-locmap-marker-label' )
			:addClass( wrapClass )
			:addClass( labelClass )
			:cssText( isSet( table.concat( styles, ' ' ) ) )
			:node( mw.html.create( 'span' )
				:addClass( 'voy-locmap-marker-label-text' )
				:wikitext( args.label )
			)
		)
	end
	return tostring( markerBox )
end

local function baseMap( args )
	-- map and map container
	local map = mw.ustring.format( '[[File:%s|%spx|center|class=noviewer notpageimage|link=|%s]]',
		 args.mapImage, args.width, args.description )

	-- add marker
	if args.x < 0 or args.x > 1 or args.y < 0 or args.y > 1 then
		map = map .. tostring( mw.html.create( 'div' )
			:addClass( 'voy-locmap-error' )
			:wikitext( mw.ustring.format( li.errMsgs.coordError, args.name ) )
		)
	else
		map = map .. setLocation( args )
	end

	local style = ( args.caption ~= '' and args.captionInnerBorder ~= '' )
		and ( 'border:' .. args.captionInnerBorder ) or ''
	map = mw.html.create( 'div' )
		:addClass( 'voy-locmap-map-box' )
		:cssText( isSet( style ) )
		:wikitext( map .. args.places ) -- adding places to map

	-- add map caption
	local caption, class
	if args.caption ~= '' then
		caption = mw.html.create( 'div' )
			:addClass( 'thumbcaption voy-locmap-caption' )
			:cssText( isSet( args.captionStyle ) )
			:wikitext( args.caption )
	end

	-- create outer box
	class, style = getClass( args.mapStyle, li.mapClasses )
	class = isSet( class ) or 'voy-locmap-center'
	if args.caption ~= '' and args.captionOuterBorder ~= '' then
		style = style .. '; border:' .. args.captionOuterBorder
	end

	return tostring( mw.html.create( 'div' )
		:addClass( 'voy-locmap' )
		:addClass( class )
		:addClass( args.caption ~= '' and 'voy-locmap-with-caption' or nil )
		:cssText( isSet( style ) )
		:node( map )
		:node( caption )
	)
end

-- Handling regional map data
-- This function is never to be called directly but with a pcall()
-- to handle exceptions in case of missing map modules
local function getMapData( id )
	local region = require( li.modulePrefix .. id )
	if region then
		region.id = id
	end
	return region
end

local function linearX( mapData, long )
	local left = mapData.left
	local right = mapData.right
	if not mapData or not left or not right or left == right then
		return -1 -- error
	elseif left < right then
		return ( long - left ) / ( right - left )
	elseif long < 0 then
		return ( 360 + long - left ) / ( 360 + right - left )
	else
		return ( long - left ) / ( 360 + right - left )
	end
end

local function linearY( mapData, lat )
	local top = mapData.top
	local bottom = mapData.bottom
	if not mapData or not top or not bottom or top == bottom then
		return -1 -- error
	end
	return ( lat - top ) / ( bottom - top )
end

local function getX( mapData, long, lat )
	if mapData.x then
		return mapData.x( lat, long )
	else
		return linearX( mapData, long )
	end
end

local function getY( mapData, long, lat )
	if mapData.y then
		return mapData.y( lat, long )
	else
		return linearY( mapData, lat )
	end
end

local function getMapImage( mapData, which )
	local image = mapData.default
	if which == 'quickbar' then
		which = li.defaults.quickbarMapType
		if ( mapData.quickbar or '' ) ~= '' then
			which = mapData.quickbar
		end
	end
	if which ~= '' and ( mapData[ which ] or '' ) ~= '' then
		image = mapData[ which ]
	end
	return image
end

-- parameters handling
local function argCheck( param, altValue )
	if not param or param == '' then
		return altValue
	end
	param = mw.text.trim( param )
	if param == '' then
		param = altValue
	end
	return param
end

-- checking a set of arguments
local function argsCheck( args, keys )
	for _, key in ipairs( keys ) do
		args[ key ] = argCheck( args[ key ], '' )
	end
end

local function checkMarkerProperties( args, mapData )
	args.mark = argCheck( args.mark, mapData.mark or li.defaults.markerImg )
	args.marksize = argCheck( args.marksize, mapData.marksize or li.defaults.markerSize )

	argsCheck( args, { 'name', 'label', 'labelWrap', 'labelPosition', 'labelStyle',
		'labelBackground' } )

	if args.labelBackground ~= '' then
		args.labelBackground = 'background: ' .. args.labelBackground
		if args.labelStyle ~= '' then
			args.labelStyle = args.labelStyle .. '; ' .. args.labelBackground
		else
			args.labelStyle = args.labelBackground
		end
	end

	return args
end

local function checkCoordinate( args, mapData )
	local success = true
	local t
	args.lat = argCheck( tostring( args.lat ), '' )
	args.long = argCheck( tostring( args.long ), '' )
	if args.lat ~= '' and args.long ~= '' then
		t = tonumber( args.lat )
		if t then
			args.lat = math.abs( t ) <= 90 and t or ''
		else
			t = cd.toDec( args.lat, 'lat', 6 )
			args.lat = t.error == 0 and t.dec or ''
		end

		t = tonumber( args.long )
		if t then
			args.long = ( t > -180 and t <= 180 ) and t or ''
		else
			t = cd.toDec( args.long, 'long', 6 )
			args.long = t.error == 0 and t.dec or ''
		end
	end
	if args.lat == '' or args.long == '' then
		return -1, -1, false
	end

	local x = getX( mapData, args.long, args.lat )
	if x < 0 or x > 1 then
		success = false
		if x == -1 then
			addMaintenance( li.errMsgs.wrongXBorders )
		else
			addMaintenance( mw.ustring.format( li.errMsgs.wrongLong,
				tonumber( args.long ) or 0 ) ) 
		end
	end

	local y = getY( mapData, args.long, args.lat )
	if y < 0 or y > 1 then
		success = false
		if y == -1 then
			addMaintenance( li.errMsgs.wrongYBorders )
		else
			addMaintenance( mw.ustring.format( li.errMsgs.wrongLat,
				tonumber( args.lat ) or 0 ) )
		end
	end

	return x, y, success
end

local function checkParameters( args, list )
	local unknown = {}
	for key, value in pairs( args ) do
		if not list[ key ] then
			table.insert( unknown, "''" .. key .. "''" )
		end
	end
	local category = li.errMsgs.wrongParam
	if #unknown == 1 then
		addMaintenance( category
			.. mw.ustring.format( li.errMsgs.unknownParam, unknown[ 1 ] ) )
	elseif #unknown > 1 then
		addMaintenance( category .. mw.ustring.format( li.errMsgs.unknownParams,
			table.concat( unknown, ', ' ) ) )
	end
end

-- Map functions

local function apiLocationMap( args )
	local map = argCheck( args.map, 'missing' )
	local success, mapData = pcall( getMapData, map )
	if not success then
		return mw.ustring.format( li.errMsgs.unknownMap, map )
	end
	
	-- Parameters check
	addMaintenance( checkParameters( args, lp.locationMap ) )
	if not args.lat or not args.long then
		addMaintenance( li.errMsgs.notANumber )
		return getMaintenance()
	end
	args.x, args.y, success = checkCoordinate( args, mapData )

	args.maptype = argCheck( args.maptype, 'default' )
	args.mapImage = argCheck( args.alternativeMap, getMapImage( mapData, args.maptype ) )
	if ( args.mapImage or '' ) == '' then
		success = false
		addMaintenance( li.errMsgs.noMapImage )
	end
	if not success then
		return getMaintenance()
	end

	argsCheck( args, { 'caption', 'captionStyle', 'captionInnerBorder',
		'captionOuterBorder', 'places', 'mapStyle' } )

	-- Image size and description
	args.width = argCheck( tostring( args.width ), '' )
	if not args.width:match( '^%d+$' ) and not args.width:match( '^%d*x%d+$' ) then
		args.width = li.defaults.mapSize
	end
	args.description = mapData.description or ''

	args = checkMarkerProperties( args, mapData )
	return baseMap( args ) .. getMaintenance()
end

local function apiAddLocation( args )
	local map = argCheck( args.map, 'missing' )
	local success, mapData = pcall( getMapData, map )
	if not success then
		return mw.ustring.format( li.errMsgs.unknownMap, map )
	end

	-- Parameters check
	addMaintenance( checkParameters( args, lp.locationMapLocation ) )
	if not args.lat or not args.long then
		addMaintenance( li.errMsgs.notANumber )
		return getMaintenance()
	end
	args.x, args.y, success = checkCoordinate( args, mapData )
	if not success then
		return getMaintenance()
	end

	args = checkMarkerProperties( args, mapData )
	return setLocation( args ) .. getMaintenance()
end

local function apiAddObject( args )
	argsCheck( args, { 'object', 'right', 'left', 'top', 'bottom', 'objectStyle',
		'objectBackground' } )

	if args.object == '' then
		return li.errMsgs.noObject
	end
	
	local success = true
	addMaintenance( checkParameters( args, lp.locationMapObject ) )

	if args.right == '' and args.left == '' then
		success = false
		addMaintenance( li.errMsgs.noXPos )
	end
	if args.top == '' and args.bottom == '' then
		success = false
		addMaintenance( li.errMsgs.noYPos )
	end
	if not success then
		return getMaintenance()
	end

	if args.objectBackground ~='' then
		args.objectBackground = 'background: ' .. args.objectBackground
		if args.objectStyle ~='' then
			args.objectStyle = args.objectStyle .. '; ' .. args.objectBackground
		else
			args.objectStyle = args.objectBackground
		end
	end

	local style, labelClass
	if args.left ~= '' then
		style = 'left: ' .. args.left .. ';'
	else
		style = 'right: ' .. args.right .. ';'
	end
	if args.top ~= '' then
		style = style .. 'top: ' .. args.top .. ';'
	else
		style = style .. 'bottom: ' .. args.bottom .. ';'
	end
	labelClass, args.objectStyle = getClass( args.objectStyle, li.labelClasses )
	style = style .. args.objectStyle
	return tostring( mw.html.create( 'div' )
		:addClass( 'voy-locmap-object' )
		:addClass( labelClass )
		:cssText( isSet( style ) )
		:wikitext( args.object )
	) .. getMaintenance()
end

-- Documentation of map data

local function apiGetMapValue( args )
	local map = argCheck( args.map, 'missing' )
	local success, mapData = pcall( getMapData, map )
	if not success then
		return mw.ustring.format( li.errMsgs.unknownMap, map )
	end
	
	args.param = argCheck( args.param, '' )
	if args.param == '' then
		return li.errMsgs.noParam
	else
		return mapData[ args.param ] or li.errMsgs.anError
	end
end

local function apiGetMapValueSet( args )
	local map = argCheck( args.map, 'missing' )
	local success, mapData = pcall( getMapData, map )
	if not success then
		return mw.ustring.format( li.errMsgs.unknownMap, map )
	end
	
	local row, v
	local list = mw.html.create( 'table' )
		:addClass( li.mapDocs.tableClass )
	for i, j in ipairs( li.paramList ) do
		v = mapData[ j ]
		if not v then
			v = li.errMsgs.notDefined
		else
			if j == 'default' or j == 'relief' then
				v = mw.ustring.format( '[[c:File:%s|%s]]', v, v )
			elseif li.mapDocs[ v ] then
				v = li.mapDocs[ v ]
			end
		end
		row = mw.html.create( 'tr' )
			:node(
				mw.html.create( 'th' )
					:css( 'text-align', 'left' )
					:wikitext( li.mapDocs[ j ] )
			)
			:node(
				mw.html.create( 'td' )
					:wikitext( v )
			)
		list:node( row )
	end
	local titleObj = mw.title.getCurrentTitle()
	if titleObj.text == titleObj.baseText then
		-- not a subpage
		if not mapData.relief then
			addMaintenance( li.errMsgs.noReliefMap )
		end
	end

	return tostring( list ) .. getMaintenance()
end

-- API function calls

local function templateStyles()
	local frame = mw.getCurrentFrame()
	return frame:extensionTag( 'templatestyles', '', { src = 'Module:Location map/styles.css' } );
end

locMap[ li.api.apiLocationMap ] = function( frame )
	return templateStyles() .. apiLocationMap( frame.args )
end

locMap[ li.api.apiAddLocation ] = function( frame )
	return apiAddLocation( frame.args )
end

locMap[ li.api.apiAddObject ] = function( frame )
	return apiAddObject( frame.args )
end

locMap[ li.api.apiGetMapValue ] = function( frame )
	return apiGetMapValue( frame.args )
end

locMap[ li.api.apiGetMapValueSet ] = function( frame )
	return apiGetMapValueSet(frame.args)
end

-- example for usage in a Lua script

function locMap.exampleLuaCall()
	local frame = {}
	frame.args = {
		map   = 'de',
		lat   = 52.51789,
		long  = 13.38873,
		name  = 'Berlin',
		label = '[[Berlin]]',
	}
	return locMap.locationMap( frame )
end

return locMap