Module:ResourceMap

From ARK Wiki
Jump to navigation Jump to search
Template-info.png Documentation

local p = {}
local Strings = mw.loadData('Module:ResourceMap/config')


local function getMarkerTypeInfo(markerType)
	return Strings.Markers[markerType] or Strings.Markers.Unrecognised
end


local function getMarkerInfo(node)
	return Strings.Markers[node.type] or Strings.Markers[node.rootType] or Strings.Markers.Unrecognised
end


local function getMapSettings(args)
	assert(args.id == nil or args.id == mw.text.encode(args.id), 'map ID may not contain any HTML entities or tokens')
	return {
		args = args,

		id = args.id or 'generic',
		size = tonumber(args.size) or tonumber(args.mapsize) or 800,
		image = args.map or 'Blank.png',
		borderCoordTop = tonumber(args.borderCoordTop) or 0,
		borderCoordBottom = tonumber(args.borderCoordBottom) or 100,
		borderCoordLeft = tonumber(args.borderCoordLeft) or 0,
		borderCoordRight = tonumber(args.borderCoordRight) or 100,

		legendNotice = args['legend notice'],
		legendSpecial = args['legend showSpecialSection'] and Strings.Layers[args['legend showSpecialSection']] or nil,
	}
end


local function gatherNodes(args)
	local nodes = {}
	local rootTypes = {}
	local typesSet = {}

	for argindex, v in ipairs(args) do 
		if #v > 0 then
			local info = mw.text.split(v, ',', true)
			if #info >= 3 then
				-- Extract comma-delimited properties
				local resourceType = mw.text.trim(info[3])
				local lat = tonumber(info[1])
				local long = tonumber(info[2])
				local customTitle = info[4] and mw.text.trim(info[4]) or ''

				if resourceType ~= nil and lat ~= nil and long ~= nil then
					-- Pack node information.
					local node = {
						type = resourceType,
						rootType = mw.text.split(resourceType, ' ', true)[1],
						lat = lat,
						long = long,
						customTitle = customTitle,
					}
					-- Push the node onto the list.
					if nodes[resourceType] == nil then
						nodes[resourceType] = { node }

						if typesSet[node.rootType] == nil then
							table.insert(rootTypes, node.rootType)
							typesSet[node.rootType] = true
						end
					else
						table.insert(nodes[resourceType], node)
					end
				else
					-- Resource type invalid, latitude invalid, or longitude invalid.
					error(string.format('arg#%d: missing resource type, or coordinates are not numbers'))
				end
			else
				-- Not enough comma-delimited values.
				error(string.format('arg#%d: at least 3 comma-delimited values are expected', argindex))
			end
		end
	end

	return {
		rootTypes = rootTypes,
		nodes = nodes,
	}
end


function p.renderNode(settings, node)
	local info = getMarkerInfo(node)
	-- Construct tooltip text.
	local title = string.format('%s
lat %.2f, lon %.2f', info[1], node.lat, node.long)
	if node.customTitle ~= nil then
		title = title .. '
' .. node.customTitle
	end
	-- Get marker size
	local markerSize = info[2] or 7
	-- Calculate position offsets.
	local top = 100 * ((node.lat - settings.borderCoordTop) / (settings.borderCoordBottom - settings.borderCoordTop) - markerSize/(2*settings.size))
	local left = 100 * ((node.long - settings.borderCoordLeft) / (settings.borderCoordRight - settings.borderCoordLeft) - markerSize/(2*settings.size))
	-- Construct resulting HTML.
	return string.format('<div style="left:%.1f%%;top:%.2f%%" title="%s"></div>', left, top, title)
end


function p.renderNodeGroups(settings, nodes)
	local html = {}

	for resourceType, nodes in pairs(nodes) do 
		html[#html+1] = '<div class="' .. resourceType .. ' dots">'
		for _, node in ipairs(nodes) do
			html[#html+1] = p.renderNode(settings, node)
		end
		html[#html+1] = '</div>'
	end
	
	return table.concat(html)
end


local function encodeAsDataAttributes(t)
	local out = {}
	for key, value in pairs(t) do
		if value ~= nil then
			if type(value) == 'boolean' then
				value = value and '1' or '0'
			end
			out[#out+1] = 'data-'..key..'="'..value..'"'
		end
	end
	return table.concat(out, ' ')
end


local function renderLegendHeading(text, needsJs)
	return table.concat({
		'<tr class="no-icon',
		needsJs and ' data-map-needs-js' or '',
		'">',
		'<td colspan="2"><b>',
		text,
		'</b></td>',
		'</tr>',
	})
end


local function compareMarkerRootTypesForLegend(a, b)
	if Strings.ForceOrder[a] or Strings.ForceOrder[b] then
		return (Strings.ForceOrder[a] or 999) < (Strings.ForceOrder[b] or 999)
	end
	return getMarkerTypeInfo(a)[1] < getMarkerTypeInfo(b)[1]
end


local function getMarkerIconClassDefinitions(settings)
	local result = {}
	for name, value in pairs(settings.args) do
		name = mw.text.split(name, ' ', true)
		if #name == 3 and name[1] == 'marker' and name[3] == 'icon' then
			local url = mw.text.split(value, '/', true)
			assert(#url == 7, 'marker icon value must be a result of {{filepath:...}}')
			result[#result+1] = name[2] .. ':' .. url[5] .. '/' .. url[6] .. '/' .. url[7]
		end
	end
	return #result > 0 and table.concat(result, ';') or nil
end


function p.renderLegend(settings, rootTypes)
	table.sort(rootTypes, compareMarkerRootTypesForLegend)

	local html = {
		'<table id="map-legend-'..settings.id..'" class="map-legend" ',
		encodeAsDataAttributes {
			["marker-icons"] = getMarkerIconClassDefinitions(settings),
		},
		'>',
	}
	
	if settings.legendSpecial then
		html[#html+1] = renderLegendHeading(settings.legendSpecial[1], true)
		for index, layer in ipairs(settings.legendSpecial) do
			if index > 1 then
				html[#html+1] = '<tr class="data-map-needs-js">'
				html[#html+1] = '<td class="dots"></td>'
				html[#html+1] = '<td class="map-legend-checkbox" '
				html[#html+1] = encodeAsDataAttributes {
					["marker-name"] = layer[1],
				}
				html[#html+1] = '>'..layer[2]..'</td>'
				html[#html+1] = '</tr>'
			end
		end
	end

	if #rootTypes > 0 then
		html[#html+1] = renderLegendHeading(Strings.Resources)
	end
	for _, markerType in ipairs(rootTypes) do
        local typeInfo = getMarkerTypeInfo(markerType) -- { name, size, colour, border settings }
		local label = settings.args['marker ' .. markerType .. ' name']
		if label == nil then
			label = typeInfo[1]
			if typeInfo == Strings.Markers.Unrecognised then
				label = label .. ' (' .. markerType .. ')'
			end
		end
	
		html[#html+1] = '<tr>'
		html[#html+1] = '<td class="dots"><div class="legend '..markerType..'"></div></td>'
		html[#html+1] = '<td class="map-legend-checkbox" '
		html[#html+1] = encodeAsDataAttributes {
			["marker-name"] = markerType,
            ["marker-size"] = typeInfo[2],
            ["marker-color"] = typeInfo[3],
            ["marker-border"] = typeInfo[4],
		}
		html[#html+1] = '>'..label..'</td>'
		html[#html+1] = '</tr>'
		
		if settings.args['marker  ' .. markerType .. ' note'] then
			html[#html+1] = '<tr class="no-icon"><td></td><td>'
			html[#html+1] = settings.args['marker  ' .. markerType .. ' note']
			html[#html+1] = '</td></tr>'
		end
	end
	html[#html+1] = '</table>'
	
	return table.concat(html)
end


function p.renderMapW(f)
	local args = f:getParent().args
	local settings = getMapSettings(args)

	local nodeData = gatherNodes(args)
	
	local html = {
		'<div class="data-map-container" id="map-', args.id, '" ',
		encodeAsDataAttributes {
			["border-top"] = args.borderCoordTop,
			["border-left"] = args.borderCoordLeft,
			["border-right"] = args.borderCoordRight,
			["border-bottom"] = args.borderCoordBottom,
			["spawn-data-page-name"] = args.spawnDataPage or nil,
			["spawn-data-cache-id"] = args.spawnDataCacheId and tonumber(args.spawnDataCacheId) or nil,
		},
		'>',
	}

	html[#html+1] = '<div class="map-legend-container">'
	if settings.legendNotice ~= nil then
		html[#html+1] = string.format('<div class="map-notice">%s</div>', settings.legendNotice)
	end
	html[#html+1] = p.renderLegend(settings, nodeData.rootTypes)
	html[#html+1] = '</div>'

	local maxWidthStyle = 'style="max-width:'..settings.size..'px"'
	html[#html+1] = '<table class="wikitable resourcemaptable" '..maxWidthStyle..'>'
	html[#html+1] = '<tr><td>'
	html[#html+1] = '<div class="map-container" '..maxWidthStyle .. '>'
	html[#html+1] = p.renderNodeGroups(settings, nodeData.nodes)
	html[#html+1] = '[[File:'..settings.image..'|class=resourcemap|link=]]'
	html[#html+1] = '</div>'
	html[#html+1] = '</td></tr>'
	html[#html+1] = '<tr><td align="middle">' .. (args.caption or '') .. '</td></tr>'
	if args.spawnDataPage then
		html[#html+1] = '<tr><td align="top">'
		html[#html+1] = f:expandTemplate{ title = 'RarityLegend', args = { '330', forInteractiveMap = 'yes' } }
		html[#html+1] = '</td></tr>'
	end
	html[#html+1] = '</table>'

	html[#html+1] = '</div>'
	return table.concat(html)
end


return p