Module:Advanced map

From WIDEVERSE Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Advanced map/doc

-- <nowiki>
local maps = require('Module:Map')
local hc = require('Module:Paramtest').has_content
local icons

local p = {}
-- For color incrementing
local curcolor = 1
local keycolor = 1
local dotcolors = {'#ffff00', '#01ff7f', '#ff0101', '#ff7f01', '#ff017f', '#c0407f', '#ff01fc', '#a040c0',
	'#7f01ff', '#4040c0', '#017fff', '#40a0c0', '#01fffc', '#40c07f', '#61c040', '#84ff01', '#c05e40', '#c0be40', '#808080', '#ffffff'}
-- Possible style args
local styles = { 'title', 'description', 'label', 'class', 'stroke', 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity', 'radius', 'r', 'icon', 'iconWikiLink', 'direction' }
local pairstyles = { 'iconSize', 'iconAnchor', 'popupAnchor' }
-- Pin types
local pinTypes = {
	['greyPin'] = 'pin_grey.svg',
    ['redPin'] = 'pin_red.svg',
    ['greenPin'] = 'pin_green.svg',
    ['bluePin'] = 'pin_blue.svg',
    ['cyanPin'] = 'pin_cyan.svg',
    ['magentaPin'] = 'pin_magenta.svg',
    ['yellowPin'] = 'pin_yellow.svg',
}

-- Create JSON
function toJSON(j)
	local json_good, json = pcall(mw.text.jsonEncode, j)--, mw.text.JSON_PRETTY)
	if json_good then
		return json
	end
	return error('Error converting to JSON')
end
-- Parse coordinates
function parseCoords(str)
	local ret = {}
	for u in mw.text.gsplit(str, '%s*;%s*') do
		local xy = mw.text.split(u, '%s*,%s*')
		local x = tonumber(xy[1])
		local y = tonumber(xy[2])
		if x and y then
			table.insert(ret, {x=x, y=y})
		end
	end
	if #ret < 1 then error('Error the following is not a valid coordinate set: '..str) end
	return ret
end
function parseComplCoords(str)
	local ret,i = {},1
	for v in mw.text.gsplit(str, '%s*;%s*;%s*') do
		ret[i] = {}
		for u in mw.text.gsplit(v, '%s*;%s*') do
			local xy = mw.text.split(u, '%s*,%s*')
			local x = tonumber(xy[1])
			local y = tonumber(xy[2])
			if x and y then
				table.insert(ret[i], {x, y})
			end
		end
		if #ret[i] < 1 then error('Error the following is not a valid coordinate set: '..str) end
		i = i + 1
	end
	if #ret < 1 then error('Error the following is not a valid coordinate set: '..str) end
	return ret
end
-- Single point type features
function pointFeats(ftargs, opts, coords, typ)
	local ret = {}
	-- Force a new color if none given
	if typ ~= 'pin' and typ ~= 'circle' and typ ~= 'icon' and typ ~= 'text' then
		if not ftargs.fill then
			ftargs.fill = dotcolors[curcolor]
			curcolor = curcolor + 1
			if curcolor > #dotcolors then
				curcolor = 1
			end
		end
	end
	local mult = false
	if #coords > 1 then
		mult = true
	end
	for _,v in ipairs(coords) do
		v.desc = ftargs.desc or ''
		local args = ftargs
		if mult then
			args = mw.clone(ftargs)
		end
		if typ == 'circle' then
			-- Circle
			local cargs = args
			cargs.x = v.x
			cargs.y = v.y
			table.insert(ret, maps.featCircle(cargs, opts))
		elseif typ == 'text' then
			-- Text label
			table.insert(ret, maps.featText(args, opts, v))
		elseif typ == 'pin' then
			-- Pin
			table.insert(ret, maps.featPin(args, opts, v))
		elseif typ == 'icon' then
			-- Pin but use iconWikiLink with a predefine one
			args.icon = string.lower(args.icon)
			table.insert(ret, maps.featIcon(args, opts, v))
		elseif typ == 'square' or typ == 'sq' then
			-- Square dot
			table.insert(ret, maps.featSqDot(args, opts, v))
		else
			-- Dot is the default
			table.insert(ret, maps.featDot(args, opts, v))
		end
	end
	return ret
end

-- Generate the map key
function genKey(args)
	local ret = mw.html.create('ul')
	
	-- Look for up to 125 feature groups, defined in order
	for i=1, 125 do
		if args['key'..i] or args['coords'..i] and hc(args['coords'..i]) then
			local typ = string.lower(args['type'..i] or 'dot')
			local show = false
			if args['key'..i] and args['title'..i] then
				show = true
			elseif typ ~= 'text' and args['coords'..i] and hc(args['coords'..i]) then
				show = true
			end
			if args['nokey'..i] then
				show = false
			end
			if show then
				local val = args['title'..i] or args['label'..i] or 'Missing title'
				local autocolor
				if typ ~= 'pin' and typ ~= 'circle' and typ ~= 'icon' and typ ~= 'polygon' and typ ~= 'complex-polygon' and typ ~= 'complpoly' and typ ~= 'line' and typ ~= 'lines' and typ ~= 'text' and typ ~= 'key' then
					if not args['fill'..i] then
						autocolor = dotcolors[keycolor]
						keycolor = keycolor + 1
						if keycolor > #dotcolors then
							keycolor = 1
						end
					end
				end
	
				local symb = mw.html.create('div')
				symb:addClass('amap-key-symb')
    
				if typ == 'key' then
					-- Only title text, no symbol
					symb:addClass('amap-key-nosymb')
				elseif typ == 'text' then
					local cl = 'leaflet-vis-tooltip'
					if args['class'..i] then
						cl = cl .. ' ' .. args['class'..i]
					end
					symb:addClass('map-style-preview')
						:tag('div'):addClass(cl):wikitext('XX'):done()
				elseif typ == 'polygon' or typ == 'circle' or typ == 'complex-polygon' or typ == 'complpoly' then
					local cl = 'amap-key-poly'
					if typ == 'circle' then cl = 'amap-key-circ' end
					
					local brdCol = args['stroke'..i] or '#3388ff'
					local brdWi = args['stroke-width'..i] or 3
					symb:tag('div'):addClass(cl):css({
						border = brdWi..'px solid '..brdCol,
						opacity = args['stroke-opacity'..i] or 1
					}) 
					:tag('div'):css({
						background = args['fill'..i] or '#3388ff',
						opacity = args['fill-opacity'..i] or 0.2
					}):done():done()
				elseif typ == 'line' or typ == 'lines' then
					local brdCol = args['stroke'..i] or '#3388ff'
					local brdWi = tonumber(args['stroke-width'..i]) or 3
					brdWi = brdWi / 2 -- div height 0 so border renders double height
					symb:tag('div'):addClass('amap-key-line'):css({
						border = brdWi..'px solid '..brdCol,
						opacity = args['stroke-opacity'..i] or 1
					}):done()
				elseif typ == 'pin' then
					local iconSize = '26x42px'
					local pimg
					if args['iconWikiLink'..i] then
						pimg = args['iconWikiLink'..i]
					else
						pimg = args['icon'..i] or 'greenPin'
						if pinTypes[pimg] then
							pimg = pinTypes[pimg]
						else
							pimg = 'pin_green.svg'
						end
					end
					if args['iconSize'..i] then
						iconSize = string.gsub(args['iconSize'..i], '%s*,%s*', 'x')..'px'
					end
					symb:wikitext( string.format('[[File:%s|%s|link=]]', pimg, iconSize) )
				elseif typ == 'icon' then
					if not icons then
						icons = mw.loadData('Module:Map/icons')
					end
					local ic = string.lower(args['icon'..i])
					ic = icons[ic]
					if not ic then error('Invalid icon name, see [[Module:Map/icons]] for available icons and aliases') end
					symb:wikitext( string.format('[[File:%s|%sx%spx|link=]]', ic.icon, math.ceil(ic.iconSize[1]), math.ceil(ic.iconSize[2])) )
				else
					-- Dot or square dot
					local cl = 'leaflet-dot'
					if typ == 'square' or typ == 'sq' then cl = 'leaflet-sqdot' end
					symb:tag('div'):addClass('amap-key-dots')
						:tag('div'):addClass(cl):css({ background = autocolor or args['fill'..i] or '#3388ff'})
						:done()
					:done()
				end
				
				ret:tag('li')
					:node(symb)
					:tag('div')
						:addClass('amap-key-text')
						:wikitext(val)
						:done()
					:done()
			end
		else
			break
		end
	end
	
	if args.compass then
		ret:tag('li')
			:tag('div'):addClass('amap-key-symb amap-key-nosymb'):done()
			:tag('div')
				:addClass('amap-key-text amap-key-compass')
				:wikitext('[[File:Compass.png|35x35px|link=]]')
			:done()
		:done()
	end
	
	return tostring(ret)
end

-- Category args
function catArg(arg)
	local ret = ''
	for u in mw.text.gsplit(arg, '%s*,%s*') do
		if u and u ~= '' then
			ret = ret..string.format('[[Category:%s]]', u)
		end
	end
	return ret
end

-- Edit buttons
function navBar(name)
	local viewSpan = mw.html.create( 'span' )
		:attr( 'title', 'View this map' )
		:wikitext( 'v' )

	local talkSpan = mw.html.create( 'span' )
		:attr( 'title', 'Discussion about this map' )
		:wikitext( 'd' )

	local editSpan = mw.html.create( 'span' )
		:attr( 'title', 'You can edit this map. Please use the preview button before saving.' )
		:wikitext( 'e' )
	
	local pgtitle = mw.title.getCurrentTitle()
		
	local ret = mw.html.create('div')
		:addClass('amap-nav plainlinks noprint')
	if pgtitle.prefixedText ~= 'Map:'..name then
		ret	:wikitext( '[[Map:'..name..'|'..tostring(viewSpan)..']]' )
			:wikitext( '&nbsp;' )
			:tag( 'span' ):css( 'font-size', '80%' ):wikitext( '&bull;' ):done()
			:wikitext( '&nbsp;' )
	end
	ret	:wikitext( '['..tostring( mw.uri.fullUrl( 'Map talk:'..name ) )..' '..tostring(talkSpan)..']' )
		:wikitext( '&nbsp;' )
		:tag( 'span' ):css( 'font-size', '80%' ):wikitext( '&bull;' ):done()
		:wikitext( '&nbsp;' )	
		:wikitext( '['..tostring( mw.uri.fullUrl( 'Map:'..name, 'action=edit' ) )..' '..tostring(editSpan)..']' )
			
    return ret
end

function p.main(frame)
	local args = frame:getParent().args
	
	-- Combination parameters
	if args.center and hc(args.center) then
		local xy = mw.text.split(args.center, '%s*,%s*')
		local x = tonumber(xy[1])
		local y = tonumber(xy[2])
		if x then args.x = x end
		if y then args.y = y end
	end
	if args.dimensions then
		local xy = mw.text.split(args.dimensions, '%s*,%s*')
		local x = tonumber(xy[1])
		local y = tonumber(xy[2])
		if x then args.width = x end
		if y then args.height = y end
	elseif args.size then
		local xy = mw.text.split(args.size, '%s*,%s*')
		local x = tonumber(xy[1])
		local y = tonumber(xy[2])
		if x then args.width = x end
		if y then args.height = y end
	end
	
	-- for norpreprocess just return mapframe
	if args.nopreprocess then
		return p.genMap(args)
	elseif args.maponly then
		return p.genMap(args)
	end
	
	local title = args.title or 'Add a title!'
	local width = args.width or 600
	local key = genKey(args)
	local intmap = p.genMap(args)
	local class = 'advanced-map'
	local nsp = mw.title.getCurrentTitle().namespace
	
	-- translate "centre" spelling for align, set alignment, all centered in map namespace
	if nsp ~= 118 and args.align then
		local align = string.lower(args.align)
		if align == 'left' then
			class = class..' amap-left'
		elseif align == 'right' then
			class = class..' amap-right'
		--elseif align == 'centre' or align == 'center' then
		end
	end
	
	-- optional drop shadows for pins/imgs
	if args.shadows then
		local shadow = string.lower(args.shadows)
		if shadow == 'dark' then
			class = class..' amap-dropdark'
		elseif shadow == 'light' then
			class = class..' amap-droplight'
		end
	end
	
	local ret = mw.html.create('div')
	ret	:addClass(class)
		:css({ width = width..'px' })
	
	-- Add navigaion buttons to top left like navbox	
	if args.name then
		ret:node( navBar(args.name) )
	end
	
	if string.find(title, '%[%[') then
		ret:tag('div'):addClass('amap-title')
			:wikitext(title)
			:done()
	else
		ret:tag('div'):addClass('amap-title')
			:tag('span'):wikitext(title):done()
			:done()
	end
	if args.keyonly then
		ret:tag('div')
			:addClass('amap-key')
			:wikitext(key)
			:done()
	elseif args.nokey then
		ret:tag('div')
			:addClass('amap-map')
			:wikitext(intmap)
			:done()
	else
		ret:tag('div')
			:addClass('amap-map')
			:wikitext(intmap)
			:done()
		:tag('div')
			:addClass('amap-key')
			:wikitext(key)
			:done()
	end
	
	ret = tostring(ret)
	if nsp == 0 then
		-- On main
		ret = ret .. '[[Category:Pages using advanced maps]]'
		if args.pgcats then
			ret = ret .. catArg(args.pgcats)
		end
	elseif nsp == 118 then
		-- Map namespace
		ret = ret .. '[[Category:Advanced maps]]'
		if args.mapcats then
			ret = ret .. catArg(args.mapcats)
		end
	end
	
	return ret
end

function p.genMap(args)
	-- Shared options
	local opts = {
		mapID = args.mapID or 28, -- RuneScape Surface
		plane = tonumber(args.plane) or 0,
	}
	
	local featCol = {}
	-- Look for up to 125 feature groups, defined in order
	for i=1, 125 do
		if args['coords'..i] and hc(args['coords'..i]) then
			local typ = string.lower(args['type'..i] or 'dot')
			local coords = parseCoords(  args['coords'..i] )
			local ftargs = {}
			
			-- Complex coords
			local complcoords
			if typ == 'complex-polygon' or typ == 'complpoly' or typ == 'lines' then
				complcoords = parseComplCoords( args['coords'..i] )
			end	
			
			-- Get styles for map feature group
			for _,s in ipairs(styles) do
				if args[s..i] and hc(args[s..i]) then
					ftargs[s] = args[s..i]
				end
			end
			if ftargs.radius then
				ftargs.r = ftargs.radius
			end
			for _,s in ipairs(pairstyles) do
				if args[s..i] and hc(args[s..i]) then
					local xy = mw.text.split(args[s..i], '%s*,%s*')
					local x = tonumber(xy[1])
					local y = tonumber(xy[2])
					if x and y then
						ftargs[s] = {x, y}
					end
				end
			end
			
			if typ == 'complex-polygon' or typ == 'complpoly' then
				if #complcoords > 1 then
					table.insert(featCol, maps.featComplPolygon(ftargs, opts, complcoords))
				else
					ftargs.pins = complcoords[1]
					table.insert(featCol, maps.featPolygon(ftargs, opts))
				end
			elseif typ == 'lines' then
				for _,v in ipairs(complcoords) do
					local lcoords = {}
					for _,k in ipairs(v) do
						table.insert(lcoords, {x=k[1], y=k[2]})
					end	
					ftargs.pins = lcoords
					table.insert(featCol, maps.featLine(ftargs, opts))
				end
			elseif typ == 'polygon' then
				ftargs.pins = coords
				table.insert(featCol, maps.featPolygon(ftargs, opts))
			elseif typ == 'line' then
				ftargs.pins = coords
				table.insert(featCol, maps.featLine(ftargs, opts))
			else
				-- Single point features
				local subfts = pointFeats(ftargs, opts, coords, typ)
				for _,f in ipairs(subfts) do
					table.insert(featCol, f)
				end
			end
		elseif args['key'..i] and hc(args['key'..i]) then
		else
			break
		end
	end
	
	local ftjson = {
		type = 'FeatureCollection',
		features = featCol
	}
	local mapopts = {
		x = args.x,
		y = args.y,
		width = args.width or 600,
		height = args.height or 600,
		mapID = opts.mapID,
		plane = opts.plane,
		zoom = args.zoom or 2,
		align = 'center',
		plainTiles = 'true'
	}
	if hc(args.group) then
		mapopts.group = args.group
	end
	if args.maponly and args.align then
		mapopts.align = args.align
	end
	-- translate "centre" spelling for align
	if mapopts.align == 'centre' then
		mapopts.align = 'center'
	end
	if hc(args.caption) then
		mapopts.text = args.caption
	else
		mapopts.frameless = ''
	end
	if hc(args.showicons) then
		mapopts.plainTiles = 'false'
	end
	if hc(args.mapVersion) then
		mapopts.mapVersion = args.mapVersion
	end
	
	-- mw.logObject(ftjson)
	
	-- Create map element
	local mapelem = mw.html.create('mapframe')
	mapelem:attr(mapopts):newline():wikitext(toJSON(ftjson)):newline()
	
	if args.nopreprocess then
		return tostring(mapelem)
	end
	return mw.getCurrentFrame():preprocess(tostring(mapelem))
end

return p