Module:Map

From the AARoads Wiki: Read about the road before you go
Jump to navigation Jump to search

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

local p = {}
local util = {}

--[[
Splits a list into a table sequence. The items in the list may be separated by
commas, or by semicolons (if items may contain commas), or by "###" (if items
may contain semicolons).
@param {string} listStringm
@returns {table} sequence of list items
]]--
function util.tableFromList(listString)
	if type(listString) ~= "string" or listString == "" then return nil end
	local separator = (mw.ustring.find(listString, "###", 0, true ) and "###") or
		(mw.ustring.find(listString, ";", 0, true ) and ";") or ","
	local pattern = "%s*"..separator.."%s*"
	return mw.text.split(listString, pattern)
end

function p.main (frame)
	return p._main(frame.getParent(frame).args)
end

function p._main (argsIn)
	local args = {}
	for key, value in pairs(argsIn) do
		args[key] = value
	end
	
	-- [[MediaWiki:Gadget-maps.js]] already defines default values for these
	-- parameters, but the HTML needs to specify an inline width and height
	-- upfront to keep the content from jumping around when the gadget loads.
	local width = args["width"] or "400px"
	if width == "full" then
		width = "100%"
	elseif not string.match(width, "%d*%D+") then
		width = width.."px"
	end
	args.width=width
	
	height=args["height"] or "400px"
	if not string.match(height, "%d*%D+") then
		height = height.."px"
	end
	args.height=height
	
	args.align = args.align or "right"
	
	if args.switch then
		args.plain = args.plain or "no"
		return tostring(p.switch(args))
	else
		return tostring(p.comparer(args))
	end
end

function p.switch (args)
	local variants = util.tableFromList(args["switch"])
	local variantArgs = {}
	
	for i = 1, #variants do
		variantArgs[i] = {}
	end
	
	for key, value in pairs(args) do
		if key ~= 'switch' and key ~= 'text' and key ~= 'plain' and key ~= 'align' then
			if string.match(value, "^SWITCH:.+") then
				local valueVariants = util.tableFromList(string.match(value, "^SWITCH:%s*(.*)"))
				local variantIndex = 1
				local i = 1
				while i <= #valueVariants do
					variantArgs[variantIndex][key] = valueVariants[i]
					variantIndex = variantIndex + 1
					if variantIndex > #variants then break end
					i = i+1
				end
				variantIndex = variantIndex - 1
				while variantIndex < #variants do
					variantArgs[variantIndex+1][key] = variantArgs[variantIndex][key]
					variantIndex = variantIndex+1
				end
			else
				for i = 1, #variants do
					variantArgs[i][key] = value
				end
			end
		end
	end
	
	local container = mw.html.create("div")
		:addClass("switcher-container")
	if args["align"] == "left" or args["align"] == "right" then
		container:addClass("float"..args["align"])
	else -- alignment is "center"
		container:addClass("center")
	end
	
	for i = 1, #variantArgs do
		container
			:node(p.comparer(variantArgs[i]))
			:tag("span")
				:addClass("switcher-label")
				:css("display", "none")
				:wikitext("Show "..mw.text.trim(variants[i]))
	end
	
	if not args["plain"] or string.lower(args["plain"])=="yes" then
		return container
	end
	
	local classlist = container:getAttr("class")
	classlist = mw.ustring.gsub(classlist, "%a*"..args.align, "")
	container:attr("class", classlist)
	local outerContainer = mw.html.create("div")
		:addClass("thumb")
	if args.align == "left" or args.align == "right" then
		outerContainer:addClass("t"..args.align)
	else -- alignment is "center"
		outerContainer
			:addClass("tnone")
			:addClass("center")
	end
	outerContainer
		:tag("div")
			:addClass("thumbinner")
			:css("width", args.width)
			:node(container)
			:node(args.text and mw.html.create("div")
				:addClass("thumbcaption")
				:wikitext(args.text)
			)
	return outerContainer
end

function p.comparer (args)
	local comparer = false
	for key, value in pairs(args) do
		if string.match(value, "^COMP:.+$") then
			comparer = true
		end
	end
	
	local mapElement
	if comparer then
		local body1 = {}
		local body2 = {}
		for key, value in pairs(args) do
			if string.match(value, "^COMP:.+$") then
				local division = util.tableFromList(string.match(value, "^COMP:%s*(.*)"))
				body1[key] = division[1]
				body2[key] = division[2]
			else
				body1[key] = value
				body2[key] = value
			end
		end
		
		mapElement = mw.html.create("div")
			:addClass("maplibre-comparison")
			:node(p.body(body1))
			:node(p.body(body2))
			:attr("data-width", args.width)
			:attr("data-height", args.height)
	else
		mapElement = p.body(args) 
	end
	
	local container;
	if args["plain"] and string.lower(args["plain"])~="yes" then
		container = mw.html.create("div")
			:addClass("thumb")
			:node(mw.html.create("div")
				:addClass("thumbinner")
				:css("width", args.width)
				:node(mapElement)
				:node(mw.html.create("div")
					:addClass("thumbcaption")
					:wikitext(args["text"] or "Map")
				)
			)
		if args.align == "left" or args.align == "right" then
			container:addClass("t"..args.align)
		elseif args.align == "center" then
			container:addClass("tnone")
				:addClass("center")
		end
	else 
		container = mapElement
	end
	
	return container
end

function p.body (args) 
	-- Copy frame arguments verbatim to the container’s data attributes.
	local zoom = args.zoom -- defaults in gadget
	local simpleAttrs = {
		"lat", "lon", "bearing", "pitch", "layer", "date",
		"navigation-position", "full-screen-position", "attribution-position",
	}
	local hasMarker = args.marker ~= "no"
	local dataAttrs = {
		["data-width"] = args.width,
		["data-height"] = args.height,
		["data-zoom"] = zoom,
	}
	for i, attr in ipairs(simpleAttrs) do
		dataAttrs["data-" .. attr] = args[attr]
	end
	
	local container = mw.html.create("div")
		:addClass("maplibre-map")
		:attr(dataAttrs)
		-- [[MediaWiki:Gadget-maps.js]] already sets these dimensions in an
		-- inline style, but the transcluded HTML needs to specify these same
		-- dimensions upfront to keep the content from jumping around when the
		-- gadget loads.
		:css("width", width)
		:css("height", height)
	
	-- Numbered series of arguments can start with an unnumbered argument.
	-- Also backfill legacy unnumbered arguments.
	args.commons1 = args.commons1 or args.commons
	args.from1 = args.from1 or args.from
	args.commons = nil
	args.from = nil --prevents calling the same feature twice, since the JS still looks for this argument
	args["marker1-lat"] = args["marker1-lat"] or args["marker-lat"] or args.mlat
	args["marker1-lon"] = args["marker1-lon"] or args["marker-lon"] or args.mlon

	-- Gather numbered series arguments by index so they overlay sequentially and not randomly.
	local commonsAttrIndices = {}
	local fromAttrIndices = {}
	local markerAttrIndices = {}
	for key, value in pairs(args) do
		local index = mw.ustring.match(key, "^commons(%d+)$")
		if index then
			table.insert(commonsAttrIndices, tonumber(index))
		end
		index = mw.ustring.match(key, "^from(%d+)$")
		if index then
			table.insert(fromAttrIndices, tonumber(index))
		end
		index = mw.ustring.match(key, "^marker(%d+)-lat$") or
			mw.ustring.match(key, "^marker(%d+)-lon")
		if index then
			table.insert(markerAttrIndices, tonumber(index))
		end
	end
	-- Numerically sort the indices in these arguments.
	table.sort(commonsAttrIndices)
	table.sort(markerAttrIndices)
	table.sort(fromAttrIndices)
	
	--weaves 'from' attributes into 'commons' attributes list
	local commonsAttrsIndex = 1 --will always be the most recent index with a value
	if #fromAttrIndices > 0 then
		for _, i in ipairs(fromAttrIndices) do
			if not (commonsAttrIndices[1] == 1) then
				table.insert(commonsAttrIndices, 1, 1)
				args["commons1"] = args["from"..i]
			else
				while true do
					if #commonsAttrIndices==commonsAttrsIndex then
						table.insert(commonsAttrIndices, commonsAttrIndices[commonsAttrsIndex]+1)
						args["commons"..commonsAttrsIndex+1] = args["from"..i]
						commonsAttrsIndex = commonsAttrsIndex + 1
						break
					elseif commonsAttrIndices[commonsAttrsIndex+1]-commonsAttrIndices[commonsAttrsIndex] > 1 then
						table.insert(commonsAttrIndices, commonsAttrsIndex+1, commonsAttrIndices[commonsAttrsIndex]+1)
						args["commons"..commonsAttrIndices[commonsAttrsIndex+1]] = args["from"..i]
						commonsAttrsIndex = commonsAttrsIndex + 1
						break
					else
						commonsAttrsIndex = commonsAttrsIndex + 1
					end
				end
			end
		end
	end
	
	-- Insert a placeholder child element for each GeoJSON overlay hosted on
	-- Wikimedia Commons.
	if #commonsAttrIndices > 0 then
		for _, i in ipairs(commonsAttrIndices) do
			container:tag("span")
				:addClass("maplibre-map-geojson")
				:attr("data-commons", args["commons" .. i])
		end
	end
	
	-- Insert a placeholder child element for each marker overlay.
	if args.marker ~= "no" and #markerAttrIndices > 0 then
		-- Deduplicate indices since marker#-lat and marker#-lon typically come
		-- in pairs.
		local insertedIndices = {}
		for _, i in ipairs(markerAttrIndices) do
			if not insertedIndices[i] then
				container:tag("span")
					:addClass("maplibre-map-marker")
					:attr("data-lat", args["marker" .. i .. "-lat"])
					:attr("data-lon", args["marker" .. i .. "-lon"])
				insertedIndices[i] = true
			end
		end
	end
	
	return container
end

return p