Módulo:Map

De RuneScape Wiki
Ir para: navegação, pesquisa

A documentação para este módulo pode ser criada em Módulo:Map/doc

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

local p = {}

local zoomSizes = {
	{ 3, 8 },
	{ 2, 4 },
	{ 1, 2 },
	{ 0, 1 },
	{ -1, 1/2 },
	{ -2, 1/4 },
	{ -3, 1/8 }
}
-- Default size of maps (to calc zoom)
local default_size = 800 -- 800px for full screen
-- Map feature (overlay) types
local featureMap = {
	none = {},
	square = { square=true },
	rectangle = { square=true },
	polygon = { polygon=true },
	line = { line=true },
	lines = { line=true },
	circle = { circle=true },
	pin = { pins=true },
	pins = { pins=true },
	['pin-polygon'] = { polygon=true, pins=true },
	['pins-polygon'] = { polygon=true, pins=true },
	['pin-line'] = { line=true, pins=true },
	['pins-line'] = { line=true, pins=true },
	['pin-circle'] = { circle=true, pins=true },
	['pins-circle'] = { circle=true, pins=true }
}
-- Possible properties
local simplestyle = {'title', 'description', 'marker-size', 'marker-symbol', 'marker-color',
	'stroke', 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity'}
local properties = {
	polygon = { title=true, description=true, stroke=true, ['stroke-opacity']=true, ['stroke-width']=true, fill=true, ['fill-opacity']=true },
	line = { title=true, description=true, stroke=true, ['stroke-opacity']=true, ['stroke-width']=true },
	circle = { title=true, description=true, stroke=true, ['stroke-opacity']=true, ['stroke-width']=true, fill=true, ['fill-opacity']=true },
	pin = { title=true, description=true, icon=true, iconWikiLink=true, iconSize=true, iconAnchor=true, popupAnchor=true}
}

-- 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
-- Create map html element
function createMapElement(elem, args, json)
	local mapelem = mw.html.create(elem)
	mapelem:attr(args):newline():wikitext(toJSON(json)):newline()
	return mapelem
end
-- Create pin description
function parseDesc(args, pin, pgname, ptype)
	local desc = {}
	if ptype == 'item' then
		desc = {
			"'''Item''': ".. (args.item or pgname),
			"'''Quantity''': ".. (pin.qty or 1)
		}
		if pin.respawn then
			table.insert(desc, "'''Respawn time''': "..pin.respawn)
		elseif args.respawn then
			table.insert(desc, "'''Respawn time''': "..args.respawn)
		end
		if pin.notes then
			table.insert(desc, "'''Notes''': "..pin.notes)
		elseif args.notes then
			table.insert(desc, "'''Notes''': "..args.notes)
		end
	elseif ptype == 'npc' then
		table.insert(desc, "'''NPC''': "..(args.npcname or pgname))
		if pin.version then
			table.insert(desc, "'''Version''': "..pin.version)
		elseif args.version then
			table.insert(desc, "'''Version''': "..args.version)
		end
		if pin.npcid then
			table.insert(desc, "'''NPC ID''': "..pin.npcid)
		elseif args.npcid then
			table.insert(desc, "'''NPC ID''': "..args.npcid)
		end
		if pin.objectid then
			table.insert(desc, "'''Object ID''': "..pin.objectid)
		elseif args.objectid then
			table.insert(desc, "'''Object ID''': "..args.objectid)
		end
		if pin.respawn then
			table.insert(desc, "'''Respawn time''': "..pin.respawn)
		elseif args.respawn then
			table.insert(desc, "'''Respawn time''': "..args.respawn)
		end
		if pin.notes then
			table.insert(desc, "'''Notes''': "..pin.notes)
		elseif args.notes then
			table.insert(desc, "'''Notes''': "..args.notes)
		end
	elseif ptype == 'object' then
		table.insert(desc, "'''Object''': "..(args.objectname or pgname))
		if pin.version then
			table.insert(desc, "'''Version''': "..pin.version)
		elseif args.version then
			table.insert(desc, "'''Version''': "..args.version)
		end
		if pin.objectid then
			table.insert(desc, "'''Object ID''': "..pin.objectid)
		elseif args.objectid then
			table.insert(desc, "'''Object ID''': "..args.objectid)
		end
		if pin.npcid then
			table.insert(desc, "'''NPC ID''': "..pin.npcid)
		elseif args.npcid then
			table.insert(desc, "'''NPC ID''': "..args.npcid)
		end
		if pin.respawn then
			table.insert(desc, "'''Respawn time''': "..pin.respawn)
		elseif args.respawn then
			table.insert(desc, "'''Respawn time''': "..args.respawn)
		end
		if pin.notes then
			table.insert(desc, "'''Notes''': "..pin.notes)
		elseif args.notes then
			table.insert(desc, "'''Notes''': "..args.notes)
		end
	else
		if args.desc then
			table.insert(desc, args.desc)
		end
		if pin.desc then
			table.insert(desc, pin.desc)
		elseif pin.x and pin.y then
			table.insert(desc, 'X,Y: '..pin.x..','..pin.y)
		end
	end

	return table.concat(desc, '<br>')
end
-- Parse unnamed arguments (arg = pin)
function p.parseArgs(args, ptype)
	args.pins = {}
	local sep = args.sep or '%s*,%s*'
	local pgname = mw.title.getCurrentTitle().text
	local rng = {
		xmin = 10000000,
		xmax = -10000000,
		ymin = 10000000,
		ymax = -10000000
	}

	local i,cnt = 1,0
	while (args[i]) do
		local v = mw.text.trim(args[i])
		if hc(v) then
			local pin = {}
			for u in mw.text.gsplit(v, sep) do
				local _u = mw.text.split(u, '%s*:%s*')
				if _u[2] then
					local k = mw.text.trim(_u[1])
					if k == 'x' or k == 'y' then
						pin[k] = tonumber(mw.text.trim(_u[2]))
					else
						pin[k] = mw.text.trim(_u[2])
					end
				else
					if pin.x then
						pin.y = tonumber(_u[1])
					else
						pin.x = tonumber(_u[1])
					end
				end
			end

			if pin.x > rng.xmax then
				rng.xmax = pin.x
			end
			if pin.x < rng.xmin then
				rng.xmin = pin.x
			end
			if pin.y > rng.ymax then
				rng.ymax = pin.y
			end
			if pin.y <  rng.ymin then
				rng.ymin = pin.y
			end

			-- Pin size/location args
			if pin.iconSizeX and pin.iconSizeY then
				pin.iconSize = {pin.iconSizeX, pin.iconSizeY }
			elseif pin.iconSize then
				pin.iconSize = {pin.iconSize, pin.iconSize}
			end
			if pin.iconAnchorX and pin.iconAnchorY then
				pin.iconAnchor = {pin.iconAnchorX, pin.iconAnchorY }
			elseif pin.iconAnchor then
				pin.iconAnchor = {pin.iconAnchor, pin.iconAnchor}
			end
			if pin.popupAnchorX and pin.popupAnchorY then
				pin.popupAnchor = {pin.popupAnchorX, pin.popupAnchorY }
			elseif pin.popupAnchor then
				pin.popupAnchor = {pin.popupAnchor, pin.popupAnchor}
			end

			pin.desc = parseDesc(args, pin, pgname, ptype)
			
			table.insert( args.pins, pin)
			cnt =  cnt + 1
		end
		i =  i + 1
	end

	-- In no anonymous args then x,y are pin
	if cnt == 0 then
		local x = tonumber(args.x) or 3233 -- Default is Lumbridge loadstone
		local y = tonumber(args.y) or 3222
		rng.xmax = x
		rng.xmin = x
		rng.ymax = y
		rng.ymin = y
		local desc = parseDesc(args, {}, pgname, ptype)
		table.insert( args.pins, {x = x, y = y, desc = desc} )
		cnt = cnt + 1
	end

	local xrange = rng.xmax - rng.xmin
	local yrange = rng.ymax - rng.ymin

	if not tonumber(args.x) then
		args.x = math.floor(rng.xmin + xrange/2)
	end
	if not tonumber(args.y) then
		args.y = math.floor(rng.ymin + yrange/2)
	end
	-- Default range (1 pin) is 40
	if not tonumber(args.x_range) then
		if xrange > 0 then
			args.x_range = xrange
		else
			args.x_range = 40
		end
	end
	if not tonumber(args.y_range) then
		if yrange > 0 then
			args.y_range = yrange
		else
			args.y_range = 40
		end
	end
	-- Default square (1 pin) is 20
	if not tonumber(args.squareX) then
		if xrange > 0 then
			args.squareX = xrange
		else
			args.squareX = 20
		end
	end
	if not tonumber(args.squareY) then
		if yrange > 0 then
			args.squareY = yrange
		else
			args.squareY = 20
		end
	end

	args.pin_count = cnt

	return args
end
-- Add styles
function styles(ftjson, args, this, ptype)
	local props = properties[ptype]
	for i,v in pairs(args) do
		if props[i] then
			ftjson.properties[i] = v
		end
	end
	for i,v in pairs(this) do
		if props[i] then
			ftjson.properties[i] = v
		end
	end

	return ftjson
end

-- Functions for templates --
function p.npcmap(frame)
	local args = {}
	for i,v in pairs(frame:getParent().args) do
		args[i] = v
	end
	--[[ Each unnamed arg is 1 pin in format:
		x,y
		or
		x:#,y:#,version:#,npcid:#,notes:#
	]]
	args = p.parseArgs(args, 'npc')

	-- Min square size
	if args.squareX < 20 then
		args.squareX = 20
	end
	if args.squareY < 20 then
		args.squareY = 20
	end

	-- Default green pins
	if not hc(args.icon) then
		args.icon = 'greenPin'
	end
	-- Pin group
	if not hc(args.group) then
		args.group = 'npcspawns'
	end
	-- Map type
	local mtype = 'normal'
	if hc(args.type) then
		mtype = string.lower(args.type)
	end

	if mtype == 'maplink' then
		-- Link to map with pins
		args.etype = 'maplink'
		args.features = 'pins'
		if not hc(args.text) then
			args.text = 'Spawn location'
		end
		return p.createMap(args)
	elseif mtype == 'pin' or mtype == 'pins' then
		-- Map with pins
		args.etype = 'mapframe'
		args.features = 'pins'
		return p.createMap(args)
	else
		-- Standard map (square with link to all)
		if args.pin_count > 1 then
			if not hc(args.text) then
				args.text = 'show exact spawns'
			end
			local capt = args.pin_count .. ' spawns '
			if hc(args.caption) then
				capt = args.caption
			end

			args.etype = 'maplink'
			args.features = 'pins'
			local link = tostring(p.createMap(args))
			capt = capt .. link

			args.etype = 'mapframe'
			args.caption = ''
			args.features = 'square'
			args.group = args.group..'area'
			local map = tostring(p.createMap(args))

			local classes = 'mw-kartographer-container thumb'
			if hc(args.align) then
				local align = string.lower(args.align)
				if align == 'left' then
					classes = classes..' tleft'
				elseif align == 'right' then
					classes = classes..' tright'
				else
					classes = classes..' center'
				end
			else
				classes = classes..' center'
			end

			local width = args.width or 300
			local ret = mw.html.create('div')
			ret:addClass(classes)
				:tag('div')
					:addClass('thumbinner')
					:css('width', width..'px')
					:node(map)
					:tag('div')
						:addClass('thumbcaption')
						:node(capt)

			return ret
		end

		args.etype = 'mapframe'
		args.group = args.group..'area'
		args.features = 'square'
		return p.createMap(args)
	end
end
function p.monstermap(frame)
	local args = {}
	for i,v in pairs(frame:getParent().args) do
		args[i] = v
	end
	--[[ Each unnamed arg is 1 pin in format:
		x,y
		or
		x:#,y:#,version:#,npcid:#,notes:#
	]]
	args = p.parseArgs(args, 'npc')

	-- Default red pins
	if not hc(args.icon) then
		args.icon = 'redPin'
	end
	-- Pin group
	if not hc(args.group) then
		args.group = 'monsterspawns'
	end
	if not hc(args.align) then
		args.align = 'center'
	end
	
	args.etype = 'mapframe'
	args.features = 'pins'
	if hc(args.type) and string.lower(args.type) == 'maplink' then
		args.etype = 'maplink'
	end

	return p.createMap(args)
end
function p.objectmap(frame)
	local args = {}
	for i,v in pairs(frame:getParent().args) do
		args[i] = v
	end
	--[[ Each unnamed arg is 1 pin in format:
		x,y
		or
		x:#,y:#,version:#,objectid:#,notes:#
	]]
	args = p.parseArgs(args, 'object')

	-- Default green pins
	if not hc(args.icon) then
		args.icon = 'greenPin'
	end
	-- Pin group
	if not hc(args.group) then
		args.group = 'objectspawns'
	end
	if not hc(args.align) then
		args.align = 'center'
	end
	
	args.etype = 'mapframe'
	args.features = 'pins'
	if hc(args.type) and string.lower(args.type) == 'maplink' then
		args.etype = 'maplink'
	end

	return p.createMap(args)
end
function p.locationmap(frame)
	local args = {}
	for i,v in pairs(frame:getParent().args) do
		args[i] = v
	end
	if hc(args[1]) then
		local arr = mw.text.split(args[1], '%s*,%s*')
		args.x = arr[1]
		args.y = arr[2]
	end
	args.etype = 'mapframe'
	args.features = 'none'
	return p.blankMap(args)
end
function p.spawnmaplink(frame)
	local args = {}
	for i,v in pairs(frame:getParent().args) do
		args[i] = v
	end
	--[[ Each unnamed arg is 1 pin in format:
		x,y
		or
		x:#,y:#,qty:#,respawn:#,notes:#
	]]
	args = p.parseArgs(args, 'item')

	-- Default green pins
	if not hc(args.icon) then
		args.icon = 'greenPin'
	end
	-- Pin group
	if not hc(args.group) then
		args.group = 'spawns'
	end
	
	args.etype = 'maplink'
	args.features = 'pins'
	return '<span class="rsw-maplink-spawns">'..tostring(p.createMap(args))..'</span>'
end
function p.maptemp(frame)
	local args = {}
	for i,v in pairs(frame:getParent().args) do
		args[i] = v
	end
	-- Allow map/element type per template easily
	local inv_args = {}
	for i,v in pairs(frame.args) do
		inv_args[i] = v
	end

	--[[ Each unnamed arg is 1 pin in format:
		x,y
		or
		x:#,y:#,desc:#
	]]
	args = p.parseArgs(args, 'generic')

	if hc(args.iconSize) then
		if string.find(args.iconSize, ',') then
			local isize = mw.text.split(args.iconSize, '%s*,%s*')
			args.iconSize = { tonumber(isize[1]) or 25, tonumber(isize[2]) or 25}
		else
			args.iconSize = { tonumber(args.iconSize) or 25, tonumber(args.iconSize) or 25}
		end
	end
	if hc(args.iconAnchor) then
		if string.find(args.iconAnchor, ',') then
			local ianch = mw.text.split(args.iconAnchor, '%s*,%s*')
			args.iconAnchor = { tonumber(ianch[1]) or 0, tonumber(ianch[2]) or 0}
		else
			args.iconAnchor = { tonumber(args.iconAnchor) or 0, tonumber(args.iconAnchor) or 0}
		end
	end
	if hc(args.popupAnchor) then
		if string.find(args.popupAnchor, ',') then
			local panch = mw.text.split(args.popupAnchor, '%s*,%s*')
			args.popupAnchor = { tonumber(panch[1]) or 0, tonumber(panch[2]) or 0}
		else
			args.popupAnchor = { tonumber(args.popupAnchor) or 0, tonumber(args.popupAnchor) or 0}
		end
	end

	if hc(inv_args.mtype) then
		args.features = string.lower(inv_args.mtype)
	end
	if hc(args.mtype) then
		args.features = string.lower(args.mtype)
	end
	if not args.features then
		args.features = 'none'
	end

	args.etype = 'mapframe'
	if hc(inv_args.type) then
		args.etype = string.lower(inv_args.type)
	end
	if hc(args.type) then
		args.etype = string.lower(args.type)
	end

	return p.createMap(args)
end

-- Function for creating map or link
function p.createMap(args)
	local x, y = args.x, args.y
	local opts = {
		x = x,
		y = y,
		width = args.width or 300,
		height = args.height or 300,
		mapID = args.mapID or 28, -- RuneScape Surface
		plane = tonumber(args.plane) or 0,
		zoom = args.zoom or 2,
		align = args.align or 'center'
	}
	if hc(args.group) then
		opts.group = args.group
	end

	-- mapframe, maplink
	local etype = 'mapframe'
	if hc(args.etype) then
		etype = args.etype
	end
	
	-- translate "centre" spelling for align
	if opts.align == 'centre' then
		opts.align = 'center'
	end

	-- Caption or link text
	if etype == 'maplink' then
		opts.text = args.text or 'Maplink'
		if string.find(opts.text,'[%[%]]') then 
			return error('Text cannot contain links')
		end
	elseif hc(args.caption) then
		opts.text = args.caption
	else
		opts.frameless = ''
	end

	local featColl, features = {}, {}
	if hc(args.features) then
		local _features = string.lower(args.features)
		features = featureMap[_features] or {}
	end
	if features.square then
		table.insert(featColl, p.featSquare(args, opts))
	elseif features.circle then
		table.insert(featColl, p.featCircle(args, opts))
	end
	if features.polygon then
		table.insert(featColl, p.featPolygon(args, opts))
	elseif features.line then
		table.insert(featColl, p.featLine(args, opts))
	end
	if features.pins then
		if not opts.group then
			opts.group = 'pins'
		end
		opts.icon = args.icon or 'greenPin'
		for _,pin in ipairs(args.pins) do
			table.insert(featColl, p.featPin(args, opts, pin))
		end
	end

	local json = {}
	if #featColl > 0 then
		json = {
			type = 'FeatureCollection',
			features = featColl
		}

		-- Zoom
		local width,height = opts.width, opts.height
		if etype == 'maplink' then
			width,height = default_size, default_size
		end
		local x_range = tonumber(args.squareX) or 40
		local y_range = tonumber(args.squareY) or 40
		if tonumber(args.r) then
			x_range = tonumber(args.r)
			y_range = tonumber(args.r)
		end
		if tonumber(args.x_range) then
			x_range = tonumber(args.x_range)
		end
		if tonumber(args.y_range) then
			y_range = tonumber(args.y_range)
		end

		local zoom = -3
		for i,v in ipairs(zoomSizes) do
			local sqsx, sqsy = width/v[2], height/v[2]
			if sqsx > x_range and sqsy > y_range then
				zoom = v[1]
				break
			end
		end
		if zoom > 2 then
			zoom = 2
		end

		if tonumber(args.zoom) then
			opts.zoom = args.zoom
		else
			opts.zoom = zoom
		end
	end

	local map = createMapElement(etype, opts, json)
	if args.nopreprocess then
		return map
	end
	return mw.getCurrentFrame():preprocess(tostring(map))
end

-- Create a square feature
function p.featSquare(args, opts)
	local x, y = args.x, args.y
	local squareX = tonumber(args.squareX) or 20
	local squareY = tonumber(args.squareY) or 20
	squareX = math.max(1, args.r or math.floor(squareX / 2))
	squareY = math.max(1, args.r or math.floor(squareY / 2))

	local ftjson = {
		type = 'Feature',
		properties = {['_']='_', mapID=opts.mapID, plane=opts.plane},
		geometry = {
			type = 'Polygon',
			coordinates = {
				{
					{ x-squareX, y-squareY },
					{ x-squareX, y+squareY },
					{ x+squareX, y+squareY },
					{ x+squareX, y-squareY }
				}
			}
		}
	}

	ftjson = styles(ftjson, args, {}, 'polygon')
	return ftjson
end

-- Create a polygon feature
function p.featPolygon(args, opts)
	local points, lastpoint = {}, {}
	for _,v in ipairs(args.pins) do
		table.insert(points, {v.x, v.y,})
		lastpoint = {v.x, v.y,}
	end
	-- Close polygon
	if not (points[1][1] == lastpoint[1] and points[1][2] == lastpoint[2]) then
		table.insert(points, {points[1][1], points[1][2]})
	end

	local ftjson = {
		type = 'Feature',
		properties = {['_']='_', mapID=opts.mapID, plane=opts.plane},
		geometry = {
			type = 'Polygon',
			coordinates = { points }
		}
	}

	ftjson = styles(ftjson, args, {}, 'polygon')
	return ftjson
end

-- Create a line feature
function p.featLine(args, opts)
	local points, lastpoint = {}, {}
	for _,v in ipairs(args.pins) do
		table.insert(points, {v.x, v.y,})
		lastpoint = {v.x, v.y,}
	end
	if hc(args.close) then
		-- Close line
		if not (points[1][1] == lastpoint[1] and points[1][2] == lastpoint[2]) then
			table.insert(points, {points[1][1], points[1][2]})
		end
	end

	local ftjson = {
		type = 'Feature',
		properties = {
			['_'] = '_',
			shape = 'Line',
			mapID = opts.mapID,
			plane = opts.plane
		},
		geometry = {
			type = 'LineString',
			coordinates = points
		}
	}

	ftjson = styles(ftjson, args, {}, 'line')
	return ftjson
end

-- Create a circle feature
function p.featCircle(args, opts)
	local rad = tonumber(args.r) or 10
	local ftjson = {
		type = 'Feature',
		properties = {
			['_']='_',
			shape = 'Circle',
			radius = rad,
			mapID = opts.mapID,
			plane = opts.plane
		},
		geometry = {
			type = 'Point',
			coordinates = {
				args.x, args.y, opts.plane
			}	
		}
	}

	ftjson = styles(ftjson, args, {}, 'circle')
	return ftjson
end

-- Create a pin feature
-- Pin types: greyPin, greenPin, redPin
function p.featPin(args, opts, pin)
	local desc = pin.desc or pin.x..', '..pin.y
	local ftjson = {
		type = 'Feature',
		properties = {
			providerID = 0,
			description = desc,
			mapID = opts.mapID,
			plane = opts.plane
		},
		geometry = {
			type = 'Point',
			coordinates = {
				pin.x, pin.y, opts.plane
			}
		}
	}

	ftjson = styles(ftjson, args, pin, 'pin')

	if not (ftjson.properties.icon or ftjson.properties.iconWikiLink) then
		ftjson.properties.icon = 'greenPin'
	end

	return ftjson
end

return p
-- </nowiki>