Theme: iWiki Log in Register

Diff: Module:Mapframe

Comparing revision #1 (2023-03-21 11:20:30) with revision #2 (2024-03-04 22:45:19).

OldNew
-- Note: Originally written on English Wikipedia at https://en.wikipedia.org/wiki/Module:Mapframe
-- Note: Originally written on English Wikipedia at https://en.wikipedia.org/wiki/Module:Mapframe
--[[----------------------------------------------------------------------------
--[[----------------------------------------------------------------------------
 ##### Localisation (L10n) settings #####
 ##### Localisation (L10n) settings #####
 Replace values in quotes ("") with localised values
 Replace values in quotes ("") with localised values
----------------------------------------------------------------------------]]--
----------------------------------------------------------------------------]]--
local L10n = {}
local L10n = {}
-- Modue dependencies
-- Modue dependencies
local transcluder -- local copy of https://www.mediawiki.org/wiki/Module:Transcluder loaded lazily
local transcluder -- local copy of https://www.mediawiki.org/wiki/Module:Transcluder loaded lazily
-- "strict" should not be used, at least until all other modules which require this module are not using globals.
-- "strict" should not be used, at least until all other modules which require this module are not using globals.
-- Template parameter names (unnumbered versions only)
-- Template parameter names (unnumbered versions only)
--   Specify each as either a single string, or a table of strings (aliases)
--   Specify each as either a single string, or a table of strings (aliases)
--   Aliases are checked left-to-right, i.e. `{ "one", "two" }` is equivalent to using `{{{one| {{{two|}}} }}}` in a template
--   Aliases are checked left-to-right, i.e. `{ "one", "two" }` is equivalent to using `{{{one| {{{two|}}} }}}` in a template
L10n.para = {
L10n.para = {
	display		= "display",
	display		= "display",
	type		= "type",
	type		= "type",
	id              = { "id", "ids" },
	id              = { "id", "ids" },
	from		= "from",
	from		= "from",
	raw		= "raw",
	raw		= "raw",
	title		= "title",
	title		= "title",
	description	= "description",
	description	= "description",
	strokeColor     = { "stroke-color", "stroke-colour" },
	strokeColor     = { "stroke-color", "stroke-colour" },
	strokeWidth	= "stroke-width",
	strokeWidth	= "stroke-width",
	strokeOpacity = "stroke-opacity",
	strokeOpacity = "stroke-opacity",
	fill        = "fill",
	fill        = "fill",
	fillOpacity     = "fill-opacity",
	fillOpacity     = "fill-opacity",
	coord		= "coord",
	coord		= "coord",
	marker		= "marker",
	marker		= "marker",
	markerColor	= { "marker-color", "marker-colour" },
	markerColor	= { "marker-color", "marker-colour" },
	markerSize = "marker-size",
	markerSize = "marker-size",
	radius      = { "radius", "radius_m" },
	radius      = { "radius", "radius_m" },
	radiusKm    = "radius_km",
	radiusKm    = "radius_km",
	radiusFt    = "radius_ft",
	radiusFt    = "radius_ft",
	radiusMi    = "radius_mi",
	radiusMi    = "radius_mi",
	edges       = "edges",
	edges       = "edges",
	text		= "text",
	text		= "text",
	icon		= "icon",
	icon		= "icon",
	zoom		= "zoom",
	zoom		= "zoom",
	frame		= "frame",
	frame		= "frame",
	plain		= "plain",
	plain		= "plain",
	frameWidth	= "frame-width",
	frameWidth	= "frame-width",
	frameHeight	= "frame-height",
	frameHeight	= "frame-height",
	frameCoordinates = { "frame-coordinates", "frame-coord" },
	frameCoordinates = { "frame-coordinates", "frame-coord" },
	frameLatitude    = { "frame-lat", "frame-latitude" },
	frameLatitude    = { "frame-lat", "frame-latitude" },
	frameLongitude   = { "frame-long", "frame-longitude" },
	frameLongitude   = { "frame-long", "frame-longitude" },
	frameAlign       = "frame-align",
	frameAlign       = "frame-align",
	switch           = "switch",
	switch           = "switch",
	overlay          = "overlay",
	overlay          = "overlay",
	overlayBorder    = "overlay-border",
	overlayBorder    = "overlay-border",
	overlayHorizontalAlignment = "overlay-horizontal-alignment",
	overlayHorizontalAlignment = "overlay-horizontal-alignment",
	overlayVerticalAlignment = "overlay-vertical-alignment",
	overlayVerticalAlignment = "overlay-vertical-alignment",
	overlayHorizontalOffset = "overlay-horizontal-offset",
	overlayHorizontalOffset = "overlay-horizontal-offset",
	overlayVerticalOffset = "overlay-vertical-offset"
	overlayVerticalOffset = "overlay-vertical-offset"
}
}
-- Names of other templates this module can extract coordinates from
-- Names of other templates this module can extract coordinates from
L10n.template = {
L10n.template = {
	coord     = { -- The coord template, as well as templates with output that contains {{coord}}
	coord     = { -- The coord template, as well as templates with output that contains {{coord}}
		"Coord", "Coord/sandbox",
		"Coord", "Coord/sandbox",
		"NRHP row", "NRHP row/sandbox",
		"NRHP row", "NRHP row/sandbox",
		"WikidataCoord", "WikidataCoord/sandbox", "Wikidatacoord", "Wikidata coord"
		"WikidataCoord", "WikidataCoord/sandbox", "Wikidatacoord", "Wikidata coord"
	}
	}
}
}
-- Error messages
-- Error messages
L10n.error = {
L10n.error = {
	badDisplayPara    = "Invalid display parameter",
	badDisplayPara    = "Invalid display parameter",
	noCoords	      = "Coordinates must be specified on Wikidata or in |" .. ( type(L10n.para.coord)== 'table' and L10n.para.coord[1] or L10n.para.coord ) .. "=",
	noCoords	      = "Coordinates must be specified on Wikidata or in |" .. ( type(L10n.para.coord)== 'table' and L10n.para.coord[1] or L10n.para.coord ) .. "=",
	wikidataCoords    = "Coordinates not found on Wikidata",
	wikidataCoords    = "Coordinates not found on Wikidata",
	noCircleCoords    = "Circle centre coordinates must be specified, or available via Wikidata",
	noCircleCoords    = "Circle centre coordinates must be specified, or available via Wikidata",
	negativeRadius    = "Circle radius must be a positive number",
	negativeRadius    = "Circle radius must be a positive number",
	noRadius          = "Circle radius must be specified",
	noRadius          = "Circle radius must be specified",
	negativeEdges     = "Circle edges must be a positive number",
	negativeEdges     = "Circle edges must be a positive number",
	noSwitchPara      = "Found only one switch value in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=",
	noSwitchPara      = "Found only one switch value in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=",
	oneSwitchLabel    = "Found only one label in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=",
	oneSwitchLabel    = "Found only one label in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=",
	noSwitchLists     = "At least one parameter must have a SWITCH: list",
	noSwitchLists     = "At least one parameter must have a SWITCH: list",
	switchMismatches  = "All SWITCH: lists must have the same number of values",
	switchMismatches  = "All SWITCH: lists must have the same number of values",
	 -- "%s" and "%d" tokens will be replaced with strings and numbers when used
	 -- "%s" and "%d" tokens will be replaced with strings and numbers when used
	oneSwitchValue    = "Found only one switch value in |%s=",
	oneSwitchValue    = "Found only one switch value in |%s=",
	fewerSwitchLabels = "Found %d switch values but only %d labels in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=",
	fewerSwitchLabels = "Found %d switch values but only %d labels in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.para.switch ) .. "=",
	noNamedCoords     = "No named coordinates found in %s"
	noNamedCoords     = "No named coordinates found in %s"
}
}
-- Other strings
-- Other strings
L10n.str = {
L10n.str = {
	-- valid values for display parameter, e.g. (|display=inline) or (|display=title) or (|display=inline,title) or (|display=title,inline)
	-- valid values for display parameter, e.g. (|display=inline) or (|display=title) or (|display=inline,title) or (|display=title,inline)
	inline		= "inline",
	inline		= "inline",
	title		= "title",
	title		= "title",
	dsep		= ",",			-- separator between inline and title (comma in the example above)
	dsep		= ",",			-- separator between inline and title (comma in the example above)
	-- valid values for type paramter
	-- valid values for type paramter
	line		= "line",		-- geoline feature (e.g. a road)
	line		= "line",		-- geoline feature (e.g. a road)
	shape		= "shape",		-- geoshape feature (e.g. a state or province)
	shape		= "shape",		-- geoshape feature (e.g. a state or province)
	shapeInverse	= "shape-inverse",	-- geomask feature (the inverse of a geoshape)
	shapeInverse	= "shape-inverse",	-- geomask feature (the inverse of a geoshape)
	data		= "data",		-- geoJSON data page on Commons
	data		= "data",		-- geoJSON data page on Commons
	point		= "point",		-- single point feature (coordinates)
	point		= "point",		-- single point feature (coordinates)
	circle      = "circle",     -- circular area around a point
	circle      = "circle",     -- circular area around a point
	named       = "named",      -- all named coordinates in an article or section
	named       = "named",      -- all named coordinates in an article or section
	-- Keyword to indicate a switch list. Must NOT use the special characters ^$()%.[]*+-?
	-- Keyword to indicate a switch list. Must NOT use the special characters ^$()%.[]*+-?
	switch = "SWITCH",
	switch = "SWITCH",
	-- valid values for icon, frame, and plain parameters
	-- valid values for icon, frame, and plain parameters
	affirmedWords = ' '..table.concat({
	affirmedWords = ' '..table.concat({
		"add",
		"add",
		"added",
		"added",
		"affirm",
		"affirm",
		"affirmed",
		"affirmed",
		"include",
		"include",
		"included",
		"included",
		"on",
		"on",
		"true",
		"true",
		"yes",
		"yes",
		"y"
		"y"
	}, ' ')..' ',
	}, ' ')..' ',
	declinedWords = ' '..table.concat({
	declinedWords = ' '..table.concat({
		"decline",
		"decline",
		"declined",
		"declined",
		"exclude",
		"exclude",
		"excluded",
		"excluded",
		"false",
		"false",
		"none",
		"none",
		"not",
		"not",
		"no",
		"no",
		"n",
		"n",
		"off",
		"off",
		"omit",
		"omit",
		"omitted",
		"omitted",
		"remove",
		"remove",
		"removed"
		"removed"
	}, ' ')..' '
	}, ' ')..' '
}
}
-- Default values for parameters
-- Default values for parameters
L10n.defaults = {
L10n.defaults = {
	display		= L10n.str.inline,
	display		= L10n.str.inline,
	text		= "Map",
	text		= "Map",
	frameWidth	= "300",
	frameWidth	= "300",
	frameHeight	= "200",
	frameHeight	= "200",
	frameAlign  = "right",
	frameAlign  = "right",
	markerColor	= "5E74F3",
	markerColor	= "5E74F3",
	markerSize	= nil,
	markerSize	= nil,
	strokeColor	= "#ff0000",
	strokeColor	= "#ff0000",
	strokeWidth	= 6,
	strokeWidth	= 6,
	edges = 32, -- number of edges used to approximate a circle
	edges = 32, -- number of edges used to approximate a circle
	overlayBorder = "1px solid white",
	overlayBorder = "1px solid white",
	overlayHorizontalAlignment = "right",
	overlayHorizontalAlignment = "right",
	overlayHorizontalOffset = "0",
	overlayHorizontalOffset = "0",
	overlayVerticalAlignment = "bottom",
	overlayVerticalAlignment = "bottom",
	overlayVerticalOffset = "0"
	overlayVerticalOffset = "0"
}
}
-- #### End of L10n settings ####
-- #### End of L10n settings ####
--[[----------------------------------------------------------------------------
--[[----------------------------------------------------------------------------
 Utility methods
 Utility methods
----------------------------------------------------------------------------]]--
----------------------------------------------------------------------------]]--
local util = {}
local util = {}
--[[
--[[
Looks up a parameter value based on the id (a key from the L10n.para table) and
Looks up a parameter value based on the id (a key from the L10n.para table) and
optionally a suffix, for parameters that can be suffixed (e.g. type2 is type
optionally a suffix, for parameters that can be suffixed (e.g. type2 is type
with suffix 2).
with suffix 2).
@param {table} args  key-value pairs of parameter names and their values
@param {table} args  key-value pairs of parameter names and their values
@param {string} param_id  id for parameter name (key from the L10n.para table)
@param {string} param_id  id for parameter name (key from the L10n.para table)
@param {string} [suffix]  suffix for parameter name
@param {string} [suffix]  suffix for parameter name
@returns {string|nil} parameter value if found, or nil if not found
@returns {string|nil} parameter value if found, or nil if not found
]]--
]]--
function util.getParameterValue(args, param_id, suffix)
function util.getParameterValue(args, param_id, suffix)
	suffix = suffix or ''
	suffix = suffix or ''
	if type( L10n.para[param_id] ) ~= 'table' then
	if type( L10n.para[param_id] ) ~= 'table' then
		return args[L10n.para[param_id]..suffix]
		return args[L10n.para[param_id]..suffix]
	end
	end
	for _i, paramAlias in ipairs(L10n.para[param_id]) do
	for _i, paramAlias in ipairs(L10n.para[param_id]) do
		if args[paramAlias..suffix] then
		if args[paramAlias..suffix] then
			return args[paramAlias..suffix]
			return args[paramAlias..suffix]
		end
		end
	end
	end
	return nil
	return nil
end
end
--[[
--[[
Trim whitespace from args, and remove empty args. Also fix control characters.
Trim whitespace from args, and remove empty args. Also fix control characters.
@param {table} argsTable
@param {table} argsTable
@returns {table} trimmed args table
@returns {table} trimmed args table
]]--
]]--
function util.trimArgs(argsTable)
function util.trimArgs(argsTable)
	local cleanArgs = {}
	local cleanArgs = {}
	for key, val in pairs(argsTable) do
	for key, val in pairs(argsTable) do
		if type(key) == 'string'  and type(val) == 'string' then
		if type(key) == 'string'  and type(val) == 'string' then
			val = val:match('^%s*(.-)%s*$')
			val = val:match('^%s*(.-)%s*$')
			if val ~= '' then
			if val ~= '' then
				-- control characters inside json need to be escaped, but stripping them is simpler
				-- control characters inside json need to be escaped, but stripping them is simpler
				-- See also T214984
				-- See also T214984
				-- However, *don't* strip control characters from wikitext (text or description parameters) or you'll break strip markers
				-- However, *don't* strip control characters from wikitext (text or description parameters) or you'll break strip markers
				-- Alternatively it might be better to only strip control char from raw parameter content
				-- Alternatively it might be better to only strip control char from raw parameter content
				if util.matchesParam('text', key) or util.matchesParam('description', key, key:gsub('^%D+(%d+)$', '%1') ) then
				if util.matchesParam('text', key) or util.matchesParam('description', key, key:gsub('^%D+(%d+)$', '%1') ) then
					cleanArgs[key] = val
					cleanArgs[key] = val
				else
				else
					cleanArgs[key] = val:gsub('%c',' ')
					cleanArgs[key] = val:gsub('%c',' ')
				end
				end
			end
			end
		else
		else
			cleanArgs[key] = val
			cleanArgs[key] = val
		end
		end
	end
	end
	return cleanArgs
	return cleanArgs
end
end
--[[
--[[
Check if a parameter name matches an unlocalized parameter key
Check if a parameter name matches an unlocalized parameter key
@param {string} key - the unlocalized parameter name to search through
@param {string} key - the unlocalized parameter name to search through
@param {string} name - the localized parameter name to check
@param {string} name - the localized parameter name to check
@param {string|nil} - an optional suffix to apply to the value(s) from the localization key
@param {string|nil} - an optional suffix to apply to the value(s) from the localization key
@returns {boolean} true if the name matches the parameter, false otherwise
@returns {boolean} true if the name matches the parameter, false otherwise
]]--
]]--
function util.matchesParam(key, name, suffix)
function util.matchesParam(key, name, suffix)
	local param = L10n.para[key]
	local param = L10n.para[key]
	suffix = suffix or ''
	suffix = suffix or ''
	if type(param) == 'table' then
	if type(param) == 'table' then
		for _, v in pairs(param) do
		for _, v in pairs(param) do
			if (v .. suffix) == name then return true end
			if (v .. suffix) == name then return true end
		end
		end
		return false
		return false
	end
	end
	return ((param .. suffix) == name)
	return ((param .. suffix) == name)
end
end
--[[
--[[
Check if a value is affirmed (one of the values in L10n.str.affirmedWords)
Check if a value is affirmed (one of the values in L10n.str.affirmedWords)
@param {string} val  Value to be checked
@param {string} val  Value to be checked
@returns {boolean} true if affirmed, false otherwise
@returns {boolean} true if affirmed, false otherwise
]]--
]]--
function util.isAffirmed(val)
function util.isAffirmed(val)
	if not(val) then return false end
	if not(val) then return false end
	return string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or false
	return string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or false
end
end
--[[
--[[
Check if a value is declined (one of the values in L10n.str.declinedWords)
Check if a value is declined (one of the values in L10n.str.declinedWords)
@param {string} val  Value to be checked
@param {string} val  Value to be checked
@returns {boolean} true if declined, false otherwise
@returns {boolean} true if declined, false otherwise
]]--
]]--
function util.isDeclined(val)
function util.isDeclined(val)
	if not(val) then return false end
	if not(val) then return false end
	return string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or false
	return string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or false
end
end
--[[
--[[
Check if the name of a template matches the known coord templates or wrappers
Check if the name of a template matches the known coord templates or wrappers
(in L10n.template.coord). The name is normalised when checked, so e.g. the names
(in L10n.template.coord). The name is normalised when checked, so e.g. the names
"Coord", "coord", and "  Coord" all return true.
"Coord", "coord", and "  Coord" all return true.
@param {string} name
@param {string} name
@returns {boolean} true if it is a coord template or wrapper, false otherwise
@returns {boolean} true if it is a coord template or wrapper, false otherwise
]]--
]]--
function util.isCoordTemplateOrWrapper(name)
function util.isCoordTemplateOrWrapper(name)
	name = mw.text.trim(name)
	name = mw.text.trim(name)
	local inputTitle = mw.title.new(name, 'Template')
	local inputTitle = mw.title.new(name, 'Template')
	if not inputTitle then
	if not inputTitle then
		return false
		return false
	end
	end
	-- Create (or reuse) mw.title objects for each known coord template/wrapper.
	-- Create (or reuse) mw.title objects for each known coord template/wrapper.
	-- Stored in L10n.template.title so that they don't need to be recreated
	-- Stored in L10n.template.title so that they don't need to be recreated
	-- each time this function is called
	-- each time this function is called
	if not L10n.template.titles then
	if not L10n.template.titles then
		L10n.template.titles = {}
		L10n.template.titles = {}
		for _, v in pairs(L10n.template.coord) do
		for _, v in pairs(L10n.template.coord) do
			table.insert(L10n.template.titles, mw.title.new(v, 'Template'))
			table.insert(L10n.template.titles, mw.title.new(v, 'Template'))
		end
		end
	end
	end
	for _, templateTitle in pairs(L10n.template.titles) do
	for _, templateTitle in pairs(L10n.template.titles) do
		if mw.title.equals(inputTitle, templateTitle) then
		if mw.title.equals(inputTitle, templateTitle) then
			return true
			return true
		end
		end
	end
	end
	return false
	return false
end
end
--[[
--[[
Recursively extract coord templates which have a name parameter.
Recursively extract coord templates which have a name parameter.
@param {string} wikitext
@param {string} wikitext
@returns {table} table sequence of coord templates
@returns {table} table sequence of coord templates
]]--
]]--
function util.extractCoordTemplates(wikitext)
function util.extractCoordTemplates(wikitext)
	local output = {}
	local output = {}
	local templates = mw.ustring.gmatch(wikitext, '{%b{}}')
	local templates = mw.ustring.gmatch(wikitext, '{%b{}}')
	local subtemplates = {}
	local subtemplates = {}
	for template in templates do
	for template in templates do
		local templateName = mw.ustring.match(template, '{{([^}|]+)')
		local templateName = mw.ustring.match(template, '{{([^}|]+)')
		local nameParam = mw.ustring.match(template, "|%s*name%s*=%s*[^}|]+")
		local nameParam = mw.ustring.match(template, "|%s*name%s*=%s*[^}|]+")
		if util.isCoordTemplateOrWrapper(templateName) then
		if util.isCoordTemplateOrWrapper(templateName) then
			if nameParam then table.insert(output, template) end
			if nameParam then table.insert(output, template) end
		elseif mw.ustring.find(mw.ustring.sub(template, 2), "{{") then
		elseif mw.ustring.find(mw.ustring.sub(template, 2), "{{") then
			local subOutput = util.extractCoordTemplates(mw.ustring.sub(template, 2))
			local subOutput = util.extractCoordTemplates(mw.ustring.sub(template, 2))
			for _, t in pairs(subOutput) do
			for _, t in pairs(subOutput) do
				table.insert(output, t)
				table.insert(output, t)
			end
			end
		end
		end
	end
	end
	-- ensure coords are not using title display
	-- ensure coords are not using title display
	for k, v in pairs(output) do
	for k, v in pairs(output) do
		output[k] = mw.ustring.gsub(v, "|%s*display%s*=[^|}]+", "|display=inline")
		output[k] = mw.ustring.gsub(v, "|%s*display%s*=[^|}]+", "|display=inline")
	end
	end
	return output
	return output
end
end
--[[
--[[
Gets all named coordiates from a page or a section of a page.
Gets all named coordiates from a page or a section of a page.
@param {string|nil} page  Page name, or name#section, to get named coordinates
@param {string|nil} page  Page name, or name#section, to get named coordinates
  from. If the name is omitted, i.e. #section or nil or empty string, then
  from. If the name is omitted, i.e. #section or nil or empty string, then
  the current page will be used.
  the current page will be used.
@returns {table} sequence of {coord, name, description} tables where coord is
@returns {table} sequence of {coord, name, description} tables where coord is
  the coordinates in a format suitable for #util.parseCoords, name is a string,
  the coordinates in a format suitable for #util.parseCoords, name is a string,
  and description is a string (coordinates in a format suitable for displaying
  and description is a string (coordinates in a format suitable for displaying
  to the reader). If for some reason the name can't be found, the description
  to the reader). If for some reason the name can't be found, the description
  is nil and the name contains display-format coordinates.
  is nil and the name contains display-format coordinates.
@throws {L10n.error.noNamedCoords} if no named coordinates are found.
@throws {L10n.error.noNamedCoords} if no named coordinates are found.
]]--
]]--
function util.getNamedCoords(page)
function util.getNamedCoords(page)
	if transcluder == nil then
	if transcluder == nil then
		-- load [[Module:Transcluder]] lazily so it is only transcluded on pages that
		-- load [[Module:Transcluder]] lazily so it is only transcluded on pages that
		-- actually use named coordinates
		-- actually use named coordinates
		transcluder = require("Module:Transcluder")
		transcluder = require("Module:Transcluder")
	end
	end
	local parts = mw.text.split(page or "", "#", true)
	local parts = mw.text.split(page or "", "#", true)
	local name = parts[1] == "" and mw.title.getCurrentTitle().prefixedText or parts[1]
	local name = parts[1] == "" and mw.title.getCurrentTitle().prefixedText or parts[1]
	local section = parts[2]
	local section = parts[2]
	local pageWikitext = transcluder.get(section and name.."#"..section or name)
	local pageWikitext = transcluder.get(section and name.."#"..section or name)
	local coordTemplates = util.extractCoordTemplates(pageWikitext)
	local coordTemplates = util.extractCoordTemplates(pageWikitext)
	if #coordTemplates == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end
	if #coordTemplates == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end
	local frame = mw.getCurrentFrame()
	local frame = mw.getCurrentFrame()
	local sep = "________"
	local sep = "________"
	local expandedContent = frame:preprocess(table.concat(coordTemplates, sep))
	local expandedContent = frame:preprocess(table.concat(coordTemplates, sep))
	local expandedTemplates = mw.text.split(expandedContent, sep)
	local expandedTemplates = mw.text.split(expandedContent, sep)
	local namedCoords = {}
	local namedCoords = {}
	for _, expandedTemplate in pairs(expandedTemplates) do
	for _, expandedTemplate in pairs(expandedTemplates) do
		local coord = mw.ustring.match(expandedTemplate, "<span class=\"geo%-dec\".->(.-)</span>")
		local coord = mw.ustring.match(expandedTemplate, "<span class=\"geo%-dec\".->(.-)</span>")
		if coord then
		if coord then
			local name = (
			local name = (
				-- name specified by a wrapper template, e.g [[Article|Name]]
				-- name specified by a wrapper template, e.g [[Article|Name]]
				mw.ustring.match(expandedTemplate, "<span class=\"mapframe%-coord%-name\">(.-)</span>") or
				mw.ustring.match(expandedTemplate, "<span class=\"mapframe%-coord%-name\">(.-)</span>") or
				-- name passed into coord template
				-- name passed into coord template
				mw.ustring.match(expandedTemplate, "<span class=\"fn org\">(.-)</span>") or
				mw.ustring.match(expandedTemplate, "<span class=\"fn org\">(.-)</span>") or
				-- default to the coordinates if the name can't be retrieved
				-- default to the coordinates if the name can't be retrieved
				coord
				coord
			)
			)
			local description = name ~= coord and coord
			local description = name ~= coord and coord
			local coord = mw.ustring.gsub(coord, "[° ]", "_")
			local coord = mw.ustring.gsub(coord, "[° ]", "_")
			table.insert(namedCoords, {coord=coord, name=name, description=description})
			table.insert(namedCoords, {coord=coord, name=name, description=description})
		end
		end
	end
	end
	if #namedCoords == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end
	if #namedCoords == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) end
	return namedCoords
	return namedCoords
end
end
--[[
--[[
Parse coordinate values from the params passed in a GeoHack url (such as
Parse coordinate values from the params passed in a GeoHack url (such as
//tools.wmflabs.org/geohack/geohack.php?pagename=Example&params=1_2_N_3_4_W_ or
//tools.wmflabs.org/geohack/geohack.php?pagename=Example&params=1_2_N_3_4_W_ or
//tools.wmflabs.org/geohack/geohack.php?pagename=Example&params=1.23_S_4.56_E_ )
//tools.wmflabs.org/geohack/geohack.php?pagename=Example&params=1.23_S_4.56_E_ )
or non-url string in the same format (such as `1_2_N_3_4_W_` or `1.23_S_4.56_E_`)
or non-url string in the same format (such as `1_2_N_3_4_W_` or `1.23_S_4.56_E_`)
@param {string} coords  string containing coordinates
@param {string} coords  string containing coordinates
@returns {number, number} latitude, longitude
@returns {number, number} latitude, longitude
]]--
]]--
function util.parseCoords(coords)
function util.parseCoords(coords)
	local coordsPatt
	local coordsPatt
	if mw.ustring.find(coords, "params=", 1, true) then
	if mw.ustring.find(coords, "params=", 1, true) then
		-- prevent false matches from page name, e.g. ?pagename=Lorem_S._Ipsum
		-- prevent false matches from page name, e.g. ?pagename=Lorem_S._Ipsum
		coordsPatt = 'params=([_%.%d]+[NS][_%.%d]+[EW])'
		coordsPatt = 'params=([_%.%d]+[NS][_%.%d]+[EW])'
	else
	else
		-- not actually a geohack url, just the same format
		-- not actually a geohack url, just the same format
		coordsPatt = '[_%.%d]+[NS][_%.%d]+[EW]'
		coordsPatt = '[_%.%d]+[NS][_%.%d]+[EW]'
	end
	end
	local parts = mw.text.split((mw.ustring.match(coords, coordsPatt) or ''), '_')
	local parts = mw.text.split((mw.ustring.match(coords, coordsPatt) or ''), '_')
	local lat_d = tonumber(parts[1])
	local lat_d = tonumber(parts[1])
	local lat_m = tonumber(parts[2]) -- nil if coords are in decimal format
	local lat_m = tonumber(parts[2]) -- nil if coords are in decimal format
	local lat_s = lat_m and tonumber(parts[3]) -- nil if coords are either in decimal format or degrees and minutes only
	local lat_s = lat_m and tonumber(parts[3]) -- nil if coords are either in decimal format or degrees and minutes only
	local lat = lat_d + (lat_m or 0)/60 + (lat_s or 0)/3600
	local lat = lat_d + (lat_m or 0)/60 + (lat_s or 0)/3600
	if parts[#parts/2] == 'S' then
	if parts[#parts/2] == 'S' then
		lat = lat * -1
		lat = lat * -1
	end
	end
	local long_d = tonumber(parts[1+#parts/2])
	local long_d = tonumber(parts[1+#parts/2])
	local long_m = tonumber(parts[2+#parts/2]) -- nil if coords are in decimal format
	local long_m = tonumber(parts[2+#parts/2]) -- nil if coords are in decimal format
	local long_s = long_m and tonumber(parts[3+#parts/2]) -- nil if coords are either in decimal format or degrees and minutes only
	local long_s = long_m and tonumber(parts[3+#parts/2]) -- nil if coords are either in decimal format or degrees and minutes only
	local long = long_d + (long_m or 0)/60 + (long_s or 0)/3600
	local long = long_d + (long_m or 0)/60 + (long_s or 0)/3600
	if parts[#parts] == 'W' then
	if parts[#parts] == 'W' then
		long = long * -1
		long = long * -1
	end
	end
	return lat, long
	return lat, long
end
end
--[[
--[[
Get coordinates from a Wikidata item
Get coordinates from a Wikidata item
@param {string} item_id  Wikidata item id (Q number)
@param {string} item_id  Wikidata item id (Q number)
@returns {number, number} latitude, longitude
@returns {number, number} latitude, longitude
@throws {L10n.error.noCoords} if item_id is invalid or the item does not exist
@throws {L10n.error.noCoords} if item_id is invalid or the item does not exist
@throws {L10n.error.wikidataCoords} if the the item does not have a P625
@throws {L10n.error.wikidataCoords} if the the item does not have a P625
  statement (coordinates), or it is set to "no value"
  statement (coordinates), or it is set to "no value"
]]--
]]--
function util.wikidataCoords(item_id)
function util.wikidataCoords(item_id)
	if not (item_id and mw.wikibase.isValidEntityId(item_id) and mw.wikibase.entityExists(item_id)) then
	if not (item_id and mw.wikibase.isValidEntityId(item_id) and mw.wikibase.entityExists(item_id)) then
		error(L10n.error.noCoords, 0)
		error(L10n.error.noCoords, 0)
	end
	end
	local coordStatements = mw.wikibase.getBestStatements(item_id, 'P625')
	local coordStatements = mw.wikibase.getBestStatements(item_id, 'P625')
	if not coordStatements or #coordStatements == 0 then
	if not coordStatements or #coordStatements == 0 then
		error(L10n.error.wikidataCoords, 0)
		error(L10n.error.wikidataCoords, 0)
	end
	end
	local hasNoValue = ( coordStatements[1].mainsnak and (coordStatements[1].mainsnak.snaktype == 'novalue' or coordStatements[1].mainsnak.snaktype == 'somevalue') )
	local hasNoValue = ( coordStatements[1].mainsnak and (coordStatements[1].mainsnak.snaktype == 'novalue' or coordStatements[1].mainsnak.snaktype == 'somevalue') )
	if hasNoValue then
	if hasNoValue then
		error(L10n.error.wikidataCoords, 0)
		error(L10n.error.wikidataCoords, 0)
	end
	end
	local wdCoords = coordStatements[1]['mainsnak']['datavalue']['value']
	local wdCoords = coordStatements[1]['mainsnak']['datavalue']['value']
	return tonumber(wdCoords['latitude']), tonumber(wdCoords['longitude'])
	return tonumber(wdCoords['latitude']), tonumber(wdCoords['longitude'])
end
end
--[[
--[[
Creates a polygon that approximates a circle
Creates a polygon that approximates a circle
@param {number} lat  Latitude
@param {number} lat  Latitude
@param {number} long  Longitude
@param {number} long  Longitude
@param {number} radius  Radius in metres
@param {number} radius  Radius in metres
@param {number} n  Number of edges for the polygon
@param {number} n  Number of edges for the polygon
@returns {table} sequence of {latitude, longitude} table sequences, where
@returns {table} sequence of {latitude, longitude} table sequences, where
  latitude and longitude are both numbers
  latitude and longitude are both numbers
]]--
]]--
function util.circleToPolygon(lat, long, radius, n) -- n is number of edges
function util.circleToPolygon(lat, long, radius, n) -- n is number of edges
	-- Based on https://github.com/gabzim/circle-to-polygon, ISC licence
	-- Based on https://github.com/gabzim/circle-to-polygon, ISC licence
	local function offset(cLat, cLon, distance, bearing)
	local function offset(cLat, cLon, distance, bearing)
		local lat1 = math.rad(cLat)
		local lat1 = math.rad(cLat)
		local lon1 = math.rad(cLon)
		local lon1 = math.rad(cLon)
		local dByR = distance / 6378137 -- distance divided by 6378137 (radius of the earth) wgs84
		local dByR = distance / 6378137 -- distance divided by 6378137 (radius of the earth) wgs84
		local lat = math.asin(
		local lat = math.asin(
			math.sin(lat1) * math.cos(dByR) +
			math.sin(lat1) * math.cos(dByR) +
			math.cos(lat1) * math.sin(dByR) * math.cos(bearing)
			math.cos(lat1) * math.sin(dByR) * math.cos(bearing)
		)
		)
		local lon = lon1 + math.atan2(
		local lon = lon1 + math.atan2(
			math.sin(bearing) * math.sin(dByR) * math.cos(lat1),
			math.sin(bearing) * math.sin(dByR) * math.cos(lat1),
			math.cos(dByR) - math.sin(lat1) * math.sin(lat)
			math.cos(dByR) - math.sin(lat1) * math.sin(lat)
		)
		)
		return {math.deg(lon), math.deg(lat)}
		return {math.deg(lon), math.deg(lat)}
	end
	end
	local coordinates = {};
	local coordinates = {};
	local i = 0;
	local i = 0;
	while i < n do
	while i < n do
		table.insert(coordinates,
		table.insert(coordinates,
			offset(lat, long, radius, (2*math.pi*i*-1)/n)
			offset(lat, long, radius, (2*math.pi*i*-1)/n)
		)
		)
		i = i + 1
		i = i + 1
	end
	end
	table.insert(coordinates, offset(lat, long, radius, 0))
	table.insert(coordinates, offset(lat, long, radius, 0))
	return coordinates
	return coordinates
end
end
--[[
--[[
Get the number of key-value pairs in a table, which might not be a sequence.
Get the number of key-value pairs in a table, which might not be a sequence.
@param {table} t
@param {table} t
@returns {number} count of key-value pairs
@returns {number} count of key-value pairs
]]--
]]--
function util.tableCount(t)
function util.tableCount(t)
	local count = 0
	local count = 0
	for k, v in pairs(t) do
	for k, v in pairs(t) do
		count = count + 1
		count = count + 1
	end
	end
	return count
	return count
end
end
--[[
--[[
For a table where the values are all tables, returns either the util.tableCount
For a table where the values are all tables, returns either the util.tableCount
of the subtables if they are all the same, or nil if they are not all the same.
of the subtables if they are all the same, or nil if they are not all the same.
@param {table} t
@param {table} t
@returns {number|nil} count of key-value pairs of subtable, or nil if subtables
@returns {number|nil} count of key-value pairs of subtable, or nil if subtables
  have different counts
  have different counts
]]--
]]--
function util.subTablesCount(t)
function util.subTablesCount(t)
	local count = nil
	local count = nil
	for k, v in pairs(t) do
	for k, v in pairs(t) do
		if count == nil then
		if count == nil then
			count = util.tableCount(v)
			count = util.tableCount(v)
		elseif count ~= util.tableCount(v) then
		elseif count ~= util.tableCount(v) then
			return nil
			return nil
		end
		end
	end
	end
	return count
	return count
end
end
--[[
--[[
Splits a list into a table sequence. The items in the list may be separated by
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
commas, or by semicolons (if items may contain commas), or by "###" (if items
may contain semicolons).
may contain semicolons).
@param {string} listString
@param {string} listString
@returns {table} sequence of list items
@returns {table} sequence of list items
]]--
]]--
function util.tableFromList(listString)
function util.tableFromList(listString)
	if type(listString) ~= "string" or listString == "" then return nil end
	if type(listString) ~= "string" or listString == "" then return nil end
	local separator = (mw.ustring.find(listString, "###", 0, true ) and "###") or
	local separator = (mw.ustring.find(listString, "###", 0, true ) and "###") or
		(mw.ustring.find(listString, ";", 0, true ) and ";") or ","
		(mw.ustring.find(listString, ";", 0, true ) and ";") or ","
	local pattern = "%s*"..separator.."%s*"
	local pattern = "%s*"..separator.."%s*"
	return mw.text.split(listString, pattern)
	return mw.text.split(listString, pattern)
end
end
-- Boolean in outer scope indicating if Kartographer should be able to
-- Boolean in outer scope indicating if Kartographer should be able to
-- automatically calculate coordinates (see phab:T227402)
-- automatically calculate coordinates (see phab:T227402)
local coordsDerivedFromFeatures = false;
local coordsDerivedFromFeatures = false;
--[[----------------------------------------------------------------------------
--[[----------------------------------------------------------------------------
 Make methods: These take in a table of arguments, and return either a string
 Make methods: These take in a table of arguments, and return either a string
 or a table to be used in the eventual output.
 or a table to be used in the eventual output.
----------------------------------------------------------------------------]]--
----------------------------------------------------------------------------]]--
local make = {}
local make = {}
--[[
--[[
Makes content to go inside the maplink or mapframe tag.
Makes content to go inside the maplink or mapframe tag.
@param {table} args
@param {table} args
@returns {string} tag content
@returns {string} tag content
]]--
]]--
function make.content(args)
function make.content(args)
	if util.getParameterValue(args, 'raw') then
	if util.getParameterValue(args, 'raw') then
		coordsDerivedFromFeatures = true -- Kartographer should be able to automatically calculate coords from raw geoJSON
		coordsDerivedFromFeatures = true -- Kartographer should be able to automatically calculate coords from raw geoJSON
		return util.getParameterValue(args, 'raw')
		return util.getParameterValue(args, 'raw')
	end
	end
	local content = {}
	local content = {}
    local argsExpanded = {}
    local argsExpanded = {}
    for k, v in pairs(args) do
    for k, v in pairs(args) do
		local index = string.match( k, '^[^0-9]+([0-9]*)$' )
		local index = string.match( k, '^[^0-9]+([0-9]*)$' )
		if index ~= nil then
		if index ~= nil then
			local indexNumber = ''
			local indexNumber = ''
			if index ~= '' then
			if index ~= '' then
				indexNumber = tonumber(index)
				indexNumber = tonumber(index)
			else
			else
				indexNumber = 1
				indexNumber = 1
			end
			end
			if argsExpanded[indexNumber] == nil then
			if argsExpanded[indexNumber] == nil then
				argsExpanded[indexNumber] = {}
				argsExpanded[indexNumber] = {}
			end
			end
			argsExpanded[indexNumber][ string.gsub(k, index, '') ] = v
			argsExpanded[indexNumber][ string.gsub(k, index, '') ] = v
		end
		end
    end
    end
	for contentIndex, contentArgs in pairs(argsExpanded) do
	for contentIndex, contentArgs in pairs(argsExpanded) do
		local argType = util.getParameterValue(contentArgs, "type")
		local argType = util.getParameterValue(contentArgs, "type")
		-- Kartographer automatically calculates coords if geolines/shapes are used (T227402)
		-- Kartographer automatically calculates coords if geolines/shapes are used (T227402)
		if not coordsDerivedFromFeatures then
		if not coordsDerivedFromFeatures then
			coordsDerivedFromFeatures = ( argType == L10n.str.line or argType == L10n.str.shape ) and true or false
			coordsDerivedFromFeatures = ( argType == L10n.str.line or argType == L10n.str.shape ) and true or false
		end
		end
		if argType == L10n.str.named then
		if argType == L10n.str.named then
			local namedCoords = util.getNamedCoords(util.getParameterValue(contentArgs, "from"))
			local namedCoords = util.getNamedCoords(util.getParameterValue(contentArgs, "from"))
			local typeKey = type(L10n.para.type) == "table" and L10n.para.type[1] or L10n.para.type
			local typeKey = type(L10n.para.type) == "table" and L10n.para.type[1] or L10n.para.type
			local coordKey = type(L10n.para.coord) == "table" and L10n.para.coord[1] or L10n.para.coord
			local coordKey = type(L10n.para.coord) == "table" and L10n.para.coord[1] or L10n.para.coord
			local titleKey = type(L10n.para.title) == "table" and L10n.para.title[1] or L10n.para.title
			local titleKey = type(L10n.para.title) == "table" and L10n.para.title[1] or L10n.para.title
			local descKey = type(L10n.para.description) == "table" and L10n.para.description[1] or L10n.para.description
			local descKey = type(L10n.para.description) == "table" and L10n.para.description[1] or L10n.para.description
			for _, namedCoord in pairs(namedCoords) do
			for _, namedCoord in pairs(namedCoords) do
				contentArgs[typeKey] = "point"
				contentArgs[typeKey] = "point"
				contentArgs[coordKey]  = namedCoord.coord
				contentArgs[coordKey]  = namedCoord.coord
				contentArgs[titleKey]  = namedCoord.name
				contentArgs[titleKey]  = namedCoord.name
				contentArgs[descKey]  = namedCoord.description
				contentArgs[descKey]  = namedCoord.description
				content[#content+1] = make.contentJson(contentArgs)
				content[#content+1] = make.contentJson(contentArgs)
			end
			end
		else
		else
			content[#content + 1] = make.contentJson(contentArgs)
			content[#content + 1] = make.contentJson(contentArgs)
		end
		end
	end
	end
	--Single item, no array needed
	--Single item, no array needed
	if #content==1 then return content[1] end
	if #content==1 then return content[1] end
	--Multiple items get placed in a FeatureCollection
	--Multiple items get placed in a FeatureCollection
	local contentArray = '[\n' .. table.concat( content, ',\n') .. '\n]'
	local contentArray = '[\n' .. table.concat( content, ',\n') .. '\n]'
	return contentArray
	return contentArray
end
end
--[[
--[[
Make coordinates from the coord arg, or the id arg, or the current page's
Make coordinates from the coord arg, or the id arg, or the current page's
Wikidata item.
Wikidata item.
@param {table} args
@param {table} args
@param {boolean} [plainOutput]
@param {boolean} [plainOutput]
@returns {Mixed} Either:
@returns {Mixed} Either:
  {number, number} latitude, longitude  if plainOutput is true; or
  {number, number} latitude, longitude  if plainOutput is true; or
  {table} table sequence of longitude, then latitude (gives the required format
  {table} table sequence of longitude, then latitude (gives the required format
   for GeoJSON when encoded)
   for GeoJSON when encoded)
]]--
]]--
function make.coords(args, plainOutput)
function make.coords(args, plainOutput)
	local coords, lat, long
	local coords, lat, long
	local frame = mw.getCurrentFrame()
	local frame = mw.getCurrentFrame()
	if util.getParameterValue(args, 'coord') then
	if util.getParameterValue(args, 'coord') then
		coords = frame:preprocess( util.getParameterValue(args, 'coord') )
		coords = frame:preprocess( util.getParameterValue(args, 'coord') )
		lat, long = util.parseCoords(coords)
		lat, long = util.parseCoords(coords)
	else
	else
		lat, long = util.wikidataCoords(util.getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage())
		lat, long = util.wikidataCoords(util.getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage())
	end
	end
	if plainOutput then
	if plainOutput then
		return lat, long
		return lat, long
	end
	end
	return {[0] = long, [1] = lat}
	return {[0] = long, [1] = lat}
end
end
--[[
--[[
Makes a table of coordinates that approximate a circle.
Makes a table of coordinates that approximate a circle.
@param {table} args
@param {table} args
@returns {table} sequence of {latitude, longitude} table sequences, where
@returns {table} sequence of {latitude, longitude} table sequences, where
  latitude and longitude are both numbers
  latitude and longitude are both numbers
@throws {L10n.error.noCircleCoords} if centre coordinates are not specified
@throws {L10n.error.noCircleCoords} if centre coordinates are not specified
@throws {L10n.error.noRadius} if radius is not specified
@throws {L10n.error.noRadius} if radius is not specified
@throws {L10n.error.negativeRadius} if radius is negative or zero
@throws {L10n.error.negativeRadius} if radius is negative or zero
@throws {L10n.error.negativeEdges} if edges is negative or zero
@throws {L10n.error.negativeEdges} if edges is negative or zero
]]--
]]--
function make.circleCoords(args)
function make.circleCoords(args)
	local lat, long = make.coords(args, true)
	local lat, long = make.coords(args, true)
	local radius = util.getParameterValue(args, 'radius')
	local radius = util.getParameterValue(args, 'radius')
	if not radius then
	if not radius then
		radius = util.getParameterValue(args, 'radiusKm') and tonumber(util.getParameterValue(args, 'radiusKm'))*1000
		radius = util.getParameterValue(args, 'radiusKm') and tonumber(util.getParameterValue(args, 'radiusKm'))*1000
		if not radius then
		if not radius then
			radius = util.getParameterValue(args, 'radiusMi') and tonumber(util.getParameterValue(args, 'radiusMi'))*1609.344
			radius = util.getParameterValue(args, 'radiusMi') and tonumber(util.getParameterValue(args, 'radiusMi'))*1609.344
			if not radius then
			if not radius then
				radius = util.getParameterValue(args, 'radiusFt') and tonumber(util.getParameterValue(args, 'radiusFt'))*0.3048
				radius = util.getParameterValue(args, 'radiusFt') and tonumber(util.getParameterValue(args, 'radiusFt'))*0.3048
			end
			end
		end
		end
	end
	end
	local edges = util.getParameterValue(args, 'edges') or L10n.defaults.edges
	local edges = util.getParameterValue(args, 'edges') or L10n.defaults.edges
	if not lat or not long then
	if not lat or not long then
		error(L10n.error.noCircleCoords, 0)
		error(L10n.error.noCircleCoords, 0)
	elseif not radius then
	elseif not radius then
		error(L10n.error.noRadius, 0)
		error(L10n.error.noRadius, 0)
	elseif tonumber(radius) <= 0 then
	elseif tonumber(radius) <= 0 then
		error(L10n.error.negativeRadius, 0)
		error(L10n.error.negativeRadius, 0)
	elseif tonumber(edges) <= 0 then
	elseif tonumber(edges) <= 0 then
		error(L10n.error.negativeEdges, 0)
		error(L10n.error.negativeEdges, 0)
	end
	end
	return util.circleToPolygon(lat, long, radius, tonumber(edges))
	return util.circleToPolygon(lat, long, radius, tonumber(edges))
end
end
--[[
--[[
Makes JSON data for a feature
Makes JSON data for a feature
@param contentArgs  args for this feature. Keys must be the non-suffixed version
@param contentArgs  args for this feature. Keys must be the non-suffixed version
  of the parameter names, i.e. use type, stroke, fill,... rather than type3,
  of the parameter names, i.e. use type, stroke, fill,... rather than type3,
  stroke3, fill3,...
  stroke3, fill3,...
@returns {string} JSON encoded data
@returns {string} JSON encoded data
]]--
]]--
function make.contentJson(contentArgs)
function make.contentJson(contentArgs)
	local data = {}
	local data = {}
	if util.getParameterValue(contentArgs, 'type') == L10n.str.point or util.getParameterValue(contentArgs, 'type') == L10n.str.circle then
	if util.getParameterValue(contentArgs, 'type') == L10n.str.point or util.getParameterValue(contentArgs, 'type') == L10n.str.circle then
		local isCircle = util.getParameterValue(contentArgs, 'type') == L10n.str.circle
		local isCircle = util.getParameterValue(contentArgs, 'type') == L10n.str.circle
		data.type = "Feature"
		data.type = "Feature"
		data.geometry = {
		data.geometry = {
			type = isCircle and "LineString" or "Point",
			type = isCircle and "LineString" or "Point",
			coordinates = isCircle and make.circleCoords(contentArgs) or make.coords(contentArgs)
			coordinates = isCircle and make.circleCoords(contentArgs) or make.coords(contentArgs)
		}
		}
		data.properties = {
		data.properties = {
			title = util.getParameterValue(contentArgs, 'title') or mw.getCurrentFrame():getParent():getTitle()
			title = util.getParameterValue(contentArgs, 'title') or mw.getCurrentFrame():getParent():getTitle()
		}
		}
		if isCircle then
		if isCircle then
			-- TODO: This is very similar to below, should be extracted into a function
			-- TODO: This is very similar to below, should be extracted into a function
			data.properties.stroke = util.getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor
			data.properties.stroke = util.getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor
			data.properties["stroke-width"] = tonumber(util.getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth
			data.properties["stroke-width"] = tonumber(util.getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth
			local strokeOpacity = util.getParameterValue(contentArgs, 'strokeOpacity')
			local strokeOpacity = util.getParameterValue(contentArgs, 'strokeOpacity')
			if strokeOpacity then
			if strokeOpacity then
				data.properties['stroke-opacity'] = tonumber(strokeOpacity)
				data.properties['stroke-opacity'] = tonumber(strokeOpacity)
			end
			end
			local fill = util.getParameterValue(contentArgs, 'fill')
			local fill = util.getParameterValue(contentArgs, 'fill')
			if fill then
			if fill then
				data.properties.fill = fill
				data.properties.fill = fill
				local fillOpacity = util.getParameterValue(contentArgs, 'fillOpacity')
				local fillOpacity = util.getParameterValue(contentArgs, 'fillOpacity')
				data.properties['fill-opacity'] = fillOpacity and tonumber(fillOpacity) or 0.6
				data.properties['fill-opacity'] = fillOpacity and tonumber(fillOpacity) or 0.6
			end
			end
		else -- is a point
		else -- is a point
			local markerSymbol = util.getParameterValue(contentArgs, 'marker') or L10n.defaults.marker
			local markerSymbol = util.getParameterValue(contentArgs, 'marker') or L10n.defaults.marker
			-- allow blank to be explicitly specified, for overriding infoboxes or other templates with a default value
			-- allow blank to be explicitly specified, for overriding infoboxes or other templates with a default value
			if markerSymbol ~= "blank" then
			if markerSymbol ~= "blank" then
				data.properties["marker-symbol"] = markerSymbol
				data.properties["marker-symbol"] = markerSymbol
			end
			end
			data.properties["marker-color"] = util.getParameterValue(contentArgs, 'markerColor') or L10n.defaults.markerColor
			data.properties["marker-color"] = util.getParameterValue(contentArgs, 'markerColor') or L10n.defaults.markerColor
			data.properties["marker-size"] = util.getParameterValue(contentArgs, 'markerSize') or L10n.defaults.markerSize
			data.properties["marker-size"] = util.getParameterValue(contentArgs, 'markerSize') or L10n.defaults.markerSize
		end
		end
	else
	else
		data.type = "ExternalData"
		data.type = "ExternalData"
		if util.getParameterValue(contentArgs, 'type') == L10n.str.data or util.getParameterValue(contentArgs, 'from') then
		if util.getParameterValue(contentArgs, 'type') == L10n.str.data or util.getParameterValue(contentArgs, 'from') then
			data.service = "page"
			data.service = "page"
		elseif util.getParameterValue(contentArgs, 'type') == L10n.str.line then
		elseif util.getParameterValue(contentArgs, 'type') == L10n.str.line then
			data.service = "geoline"
			data.service = "geoline"
		elseif util.getParameterValue(contentArgs, 'type') == L10n.str.shape then
		elseif util.getParameterValue(contentArgs, 'type') == L10n.str.shape then
			data.service = "geoshape"
			data.service = "geoshape"
		elseif util.getParameterValue(contentArgs, 'type') == L10n.str.shapeInverse then
		elseif util.getParameterValue(contentArgs, 'type') == L10n.str.shapeInverse then
			data.service = "geomask"
			data.service = "geomask"
		end
		end
		if util.getParameterValue(contentArgs, 'id') or (not (util.getParameterValue(contentArgs, 'from')) and mw.wikibase.getEntityIdForCurrentPage()) then
		if util.getParameterValue(contentArgs, 'id') or (not (util.getParameterValue(contentArgs, 'from')) and mw.wikibase.getEntityIdForCurrentPage()) then
			data.ids = util.getParameterValue(contentArgs, 'id') or mw.wikibase.getEntityIdForCurrentPage()
			data.ids = util.getParameterValue(contentArgs, 'id') or mw.wikibase.getEntityIdForCurrentPage()
		else
		else
			data.title = util.getParameterValue(contentArgs, 'from')
			data.title = util.getParameterValue(contentArgs, 'from')
		end
		end
		data.properties = {
		data.properties = {
			stroke = util.getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor,
			stroke = util.getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor,
			["stroke-width"] = tonumber(util.getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth
			["stroke-width"] = tonumber(util.getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth
		}
		}
		local strokeOpacity = util.getParameterValue(contentArgs, 'strokeOpacity')
		local strokeOpacity = util.getParameterValue(contentArgs, 'strokeOpacity')
		if strokeOpacity then
		if strokeOpacity then
			data.properties['stroke-opacity'] = tonumber(strokeOpacity)
			data.properties['stroke-opacity'] = tonumber(strokeOpacity)
		end
		end
		local fill = util.getParameterValue(contentArgs, 'fill')
		local fill = util.getParameterValue(contentArgs, 'fill')
		if fill and (data.service == "geoshape" or data.service == "geomask") then
		if fill and (data.service == "geoshape" or data.service == "geomask") then
			data.properties.fill = fill
			data.properties.fill = fill
			local fillOpacity = util.getParameterValue(contentArgs, 'fillOpacity')
			local fillOpacity = util.getParameterValue(contentArgs, 'fillOpacity')
			if fillOpacity then
			if fillOpacity then
				data.properties['fill-opacity'] = tonumber(fillOpacity)
				data.properties['fill-opacity'] = tonumber(fillOpacity)
			end
			end
		end
		end
	end
	end
	data.properties.title = util.getParameterValue(contentArgs, 'title') or mw.title.getCurrentTitle().text
	data.properties.title = util.getParameterValue(contentArgs, 'title') or mw.title.getCurrentTitle().text
	if util.getParameterValue(contentArgs, 'description') then
	if util.getParameterValue(contentArgs, 'description') then
		data.properties.description = util.getParameterValue(contentArgs, 'description')
		data.properties.description = util.getParameterValue(contentArgs, 'description')
	end
	end
	return mw.text.jsonEncode(data)
	return mw.text.jsonEncode(data)
end
end
--[[
--[[
Makes attributes for the maplink or mapframe tag.
Makes attributes for the maplink or mapframe tag.
@param {table} args
@param {table} args
@param {boolean} [isTitle]  Tag is to be displayed in the title of page rather
@param {boolean} [isTitle]  Tag is to be displayed in the title of page rather
  than inline
  than inline
@returns {table<string,string>} key-value pairs of attribute names and values
@returns {table<string,string>} key-value pairs of attribute names and values
]]--
]]--
function make.tagAttribs(args, isTitle)
function make.tagAttribs(args, isTitle)
	local attribs = {}
	local attribs = {}
	if util.getParameterValue(args, 'zoom') then
	if util.getParameterValue(args, 'zoom') then
		attribs.zoom = util.getParameterValue(args, 'zoom')
		attribs.zoom = util.getParameterValue(args, 'zoom')
	end
	end
	if util.isDeclined(util.getParameterValue(args, 'icon')) then
	if util.isDeclined(util.getParameterValue(args, 'icon')) then
		attribs.class = "no-icon"
		attribs.class = "no-icon"
	end
	end
	if util.getParameterValue(args, 'type') == L10n.str.point and not coordsDerivedFromFeatures then
	if util.getParameterValue(args, 'type') == L10n.str.point and not coordsDerivedFromFeatures then
		local lat, long = make.coords(args, 'plainOutput')
		local lat, long = make.coords(args, 'plainOutput')
		attribs.latitude = tostring(lat)
		attribs.latitude = tostring(lat)
		attribs.longitude = tostring(long)
		attribs.longitude = tostring(long)
	end
	end
	if util.isAffirmed(util.getParameterValue(args, 'frame')) and not(isTitle) then
	if util.isAffirmed(util.getParameterValue(args, 'frame')) and not(isTitle) then
		attribs.width = util.getParameterValue(args, 'frameWidth') or L10n.defaults.frameWidth
		attribs.width = util.getParameterValue(args, 'frameWidth') or L10n.defaults.frameWidth
		attribs.height = util.getParameterValue(args, 'frameHeight') or L10n.defaults.frameHeight
		attribs.height = util.getParameterValue(args, 'frameHeight') or L10n.defaults.frameHeight
		if util.getParameterValue(args, 'frameCoordinates') then
		if util.getParameterValue(args, 'frameCoordinates') then
			local frameLat, frameLong = util.parseCoords(util.getParameterValue(args, 'frameCoordinates'))
			local frameLat, frameLong = util.parseCoords(util.getParameterValue(args, 'frameCoordinates'))
			attribs.latitude = frameLat
			attribs.latitude = frameLat
			attribs.longitude = frameLong
			attribs.longitude = frameLong
		else
		else
			if util.getParameterValue(args, 'frameLatitude') then
			if util.getParameterValue(args, 'frameLatitude') then
				attribs.latitude = util.getParameterValue(args, 'frameLatitude')
				attribs.latitude = util.getParameterValue(args, 'frameLatitude')
			end
			end
			if util.getParameterValue(args, 'frameLongitude') then
			if util.getParameterValue(args, 'frameLongitude') then
				attribs.longitude = util.getParameterValue(args, 'frameLongitude')
				attribs.longitude = util.getParameterValue(args, 'frameLongitude')
			end
			end
		end
		end
		if not attribs.latitude and not attribs.longitude and not coordsDerivedFromFeatures then
		if not attribs.latitude and not attribs.longitude and not coordsDerivedFromFeatures then
			local success, lat, long = pcall(util.wikidataCoords, util.getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage())
			local success, lat, long = pcall(util.wikidataCoords, util.getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage())
			if success then
			if success then
				attribs.latitude = tostring(lat)
				attribs.latitude = tostring(lat)
				attribs.longitude = tostring(long)
				attribs.longitude = tostring(long)
			end
			end
		end
		end
		if util.getParameterValue(args, 'frameAlign') then
		if util.getParameterValue(args, 'frameAlign') then
			attribs.align = util.getParameterValue(args, 'frameAlign')
			attribs.align = util.getParameterValue(args, 'frameAlign')
		end
		end
		if util.isAffirmed(util.getParameterValue(args, 'plain')) then
		if util.isAffirmed(util.getParameterValue(args, 'plain')) then
			attribs.frameless = "1"
			attribs.frameless = "1"
		else
		else
			attribs.text = util.getParameterValue(args, 'text') or L10n.defaults.text
			attribs.text = util.getParameterValue(args, 'text') or L10n.defaults.text
		end
		end
	else
	else
		attribs.text = util.getParameterValue(args, 'text') or L10n.defaults.text
		attribs.text = util.getParameterValue(args, 'text') or L10n.defaults.text
	end
	end
	return attribs
	return attribs
end
end
--[[
--[[
Makes maplink wikitext that will be located in the top-right of the title of the
Makes maplink wikitext that will be located in the top-right of the title of the
page (the same place where coords with |display=title are positioned).
page (the same place where coords with |display=title are positioned).
@param {table} args
@param {table} args
@param {string} tagContent  Content for the maplink tag
@param {string} tagContent  Content for the maplink tag
@returns {string}
@returns {string}
]]--
]]--
function make.titleOutput(args, tagContent)
function make.titleOutput(args, tagContent)
	local titleTag = mw.text.tag('maplink', make.tagAttribs(args, true), tagContent)
	local titleTag = mw.text.tag('maplink', make.tagAttribs(args, true), tagContent)
	local spanAttribs = {
	local spanAttribs = {
		style = "font-size: small;",
		style = "font-size: small;",
		id = "coordinates"
		id = "coordinates"
	}
	}
	return mw.text.tag('span', spanAttribs, titleTag)
	return mw.text.tag('span', spanAttribs, titleTag)
end
end
--[[
--[[
Makes maplink or mapframe wikitext that will be located inline.
Makes maplink or mapframe wikitext that will be located inline.
@param {table} args
@param {table} args
@param {string} tagContent  Content for the maplink tag
@param {string} tagContent  Content for the maplink tag
@returns {string}
@returns {string}
]]--
]]--
function make.inlineOutput(args, tagContent)
function make.inlineOutput(args, tagContent)
	local tagName = 'maplink'
	local tagName = 'maplink'
	if util.getParameterValue(args, 'frame') then
	if util.getParameterValue(args, 'frame') then
		tagName = 'mapframe'
		tagName = 'mapframe'
	end
	end
	return mw.text.tag(tagName, make.tagAttribs(args), tagContent)
	return mw.text.tag(tagName, make.tagAttribs(args), tagContent)
end
end
--[[
--[[
Makes the HTML required for the swicther to work, including the templatestyles
Makes the HTML required for the swicther to work, including the templatestyles
tag.
tag.
@param {table} params  table sequence of {map, label} tables
@param {table} params  table sequence of {map, label} tables
  @param {string} params{}.map  Wikitext for mapframe map
  @param {string} params{}.map  Wikitext for mapframe map
  @param {string} params{}.label  Label text for swicther option
  @param {string} params{}.label  Label text for swicther option
@param {table} options
@param {table} options
  @param {string} options.alignment  "left" or "center" or "right"
  @param {string} options.alignment  "left" or "center" or "right"
  @param {boolean} options.isThumbnail  Display in a thumbnail
  @param {boolean} options.isThumbnail  Display in a thumbnail
  @param {string} options.width  Width of frame, e.g. "200"
  @param {string} options.width  Width of frame, e.g. "200"
  @param {string} [options.caption]  Caption wikitext for thumnail
  @param {string} [options.caption]  Caption wikitext for thumnail
@retruns {string} swicther HTML
@retruns {string} swicther HTML
]]--
]]--
function make.switcherHtml(params, options)
function make.switcherHtml(params, options)
	options = options or {}
	options = options or {}
	local frame = mw.getCurrentFrame()
	local frame = mw.getCurrentFrame()
	local styles = frame:extensionTag{
	local styles = frame:extensionTag{
		name = "templatestyles",
		name = "templatestyles",
		args = {src = "Template:Maplink/styles-multi.css"}
		args = {src = "Template:Maplink/styles-multi.css"}
	}
	}
	local container = mw.html.create("div")
	local container = mw.html.create("div")
		:addClass("switcher-container")
		:addClass("switcher-container")
		:addClass("mapframe-multi-container")
		:addClass("mapframe-multi-container")
	if options.alignment == "left" or options.alignment == "right" then
	if options.alignment == "left" or options.alignment == "right" then
		container:addClass("float"..options.alignment)
		container:addClass("float"..options.alignment)
	else -- alignment is "center"
	else -- alignment is "center"
		container:addClass("center")
		container:addClass("center")
	end
	end
	for i = 1, #params do
	for i = 1, #params do
		container
		container
			:tag("div")
			:tag("div")
				:wikitext(params[i].map)
				:wikitext(params[i].map)
				:tag("span")
				:tag("span")
					:addClass("switcher-label")
					:addClass("switcher-label")
					:css("display", "none")
					:css("display", "none")
					:wikitext(mw.text.trim(params[i].label))
					:wikitext(mw.text.trim(params[i].label))
	end
	end
	if not options.isThumbnail then
	if not options.isThumbnail then
		return styles .. tostring(container)
		return styles .. tostring(container)
	end
	end
	local classlist = container:getAttr("class")
	local classlist = container:getAttr("class")
	classlist = mw.ustring.gsub(classlist, "%a*"..options.alignment, "")
	classlist = mw.ustring.gsub(classlist, "%a*"..options.alignment, "")
	container:attr("class", classlist)
	container:attr("class", classlist)
	local outerCountainer = mw.html.create("div")
	local outerCountainer = mw.html.create("div")
		:addClass("mapframe-multi-outer-container")
		:addClass("mapframe-multi-outer-container")
		:addClass("mw-kartographer-container")
		:addClass("mw-kartographer-container")
		:addClass("thumb")
		:addClass("thumb")
	if options.alignment == "left" or options.alignment == "right" then
	if options.alignment == "left" or options.alignment == "right" then
		outerCountainer:addClass("t"..options.alignment)
		outerCountainer:addClass("t"..options.alignment)
	else -- alignment is "center"
	else -- alignment is "center"
		outerCountainer
		outerCountainer
			:addClass("tnone")
			:addClass("tnone")
			:addClass("center")
			:addClass("center")
	end
	end
	outerCountainer
	outerCountainer
		:tag("div")
		:tag("div")
			:addClass("thumbinner")
			:addClass("thumbinner")
			:css("width", options.width.."px")
			:css("width", options.width.."px")
			:node(container)
			:node(container)
			:node(options.caption and mw.html.create("div")
			:node(options.caption and mw.html.create("div")
				:addClass("thumbcaption")
				:addClass("thumbcaption")
				:wikitext(options.caption)
				:wikitext(options.caption)
			)
			)
	return styles .. tostring(outerCountainer)
	return styles .. tostring(outerCountainer)
end
end
--[[
--[[
Makes the HTML required for an overlay map to work
Makes the HTML required for an overlay map to work
tag.
tag.
@param {string} overlayMap  wikitext for the overlay map
@param {string} overlayMap  wikitext for the overlay map
@param {string} baseMap  wikitext for the base map
@param {string} baseMap  wikitext for the base map
@param {table} options  various styling/display options
@param {table} options  various styling/display options
  @param {string} options.align  "left" or "center" or "right"
  @param {string} options.align  "left" or "center" or "right"
  @param {string|number} options.width  Width of the base map, e.g. "300"
  @param {string|number} options.width  Width of the base map, e.g. "300"
  @param {string|number} options.width  Height of the base map, e.g. "200"
  @param {string|number} options.width  Height of the base map, e.g. "200"
  @param {string} options.border  Border style for the overlayed map, e.g. "1px solid white"
  @param {string} options.border  Border style for the overlayed map, e.g. "1px solid white"
  @param {string} options.horizontalAlignment  Horizontal alignment for overlay map, "left" or "right"
  @param {string} options.horizontalAlignment  Horizontal alignment for overlay map, "left" or "right"
  @param {string|number} options.horizontalOffset  Horizontal offset in pixels from the alignment edge, e.g "10"
  @param {string|number} options.horizontalOffset  Horizontal offset in pixels from the alignment edge, e.g "10"
  @param {string} options.verticalAlignment  Vertical alignment for overlay map, "top" or "bottom"
  @param {string} options.verticalAlignment  Vertical alignment for overlay map, "top" or "bottom"
  @param {string|number} options.verticalOffset  Vertical offset in pixels from the alignment edge, e.g. is "10"
  @param {string|number} options.verticalOffset  Vertical offset in pixels from the alignment edge, e.g. is "10"
  @param {boolean} options.isThumbnail  Display in a thumbnail
  @param {boolean} options.isThumbnail  Display in a thumbnail
  @param {string} [options.caption]  Caption wikitext for thumnail
  @param {string} [options.caption]  Caption wikitext for thumnail
@retruns {string} HTML for basemap with overlay
@retruns {string} HTML for basemap with overlay
]]--
]]--
function make.overlayHtml(overlayMap, baseMap, options)
function make.overlayHtml(overlayMap, baseMap, options)
	options = options or {}
	options = options or {}
	local containerFloatClass = "float"..(options.align or "none")
	local containerFloatClass = "float"..(options.align or "none")
	if options.align == "center" then
	if options.align == "center" then
		containerFloatClass = "center"
		containerFloatClass = "center"
	end
	end
	local containerStyle = {
	local containerStyle = {
		position = "relative",
		position = "relative",
		width = options.width .. "px",
		width = options.width .. "px",
		height = options.height .. "px",
		height = options.height .. "px",
		overflow = "hidden" -- mobile/minerva tends to add scrollbars for a couple of pixels
		overflow = "hidden" -- mobile/minerva tends to add scrollbars for a couple of pixels
	}
	}
	if options.align == "center" then
	if options.align == "center" then
		containerStyle["margin-left"] = "auto"
		containerStyle["margin-left"] = "auto"
		containerStyle["margin-right"] = "auto"
		containerStyle["margin-right"] = "auto"
	end
	end
	local container = mw.html.create("div")
	local container = mw.html.create("div")
		:addClass("mapframe-withOverlay-container")
		:addClass("mapframe-withOverlay-container")
		:addClass(containerFloatClass)
		:addClass(containerFloatClass)
		:addClass("noresize")
		:addClass("noresize")
		:css(containerStyle)
		:css(containerStyle)
	local overlayStyle = {
	local overlayStyle = {
		position = "absolute",
		position = "absolute",
		["z-index"] = "1",
		["z-index"] = "1",
		border = options.border or "1px solid white"
		border = options.border or "1px solid white"
	}
	}
	if options.horizontalAlignment == "right" then
	if options.horizontalAlignment == "right" then
		overlayStyle.right = options.horizontalOffset .. "px"
		overlayStyle.right = options.horizontalOffset .. "px"
	else
	else
		overlayStyle.left = options.horizontalOffset .. "px"
		overlayStyle.left = options.horizontalOffset .. "px"
	end
	end
	if options.verticalAlignment == "bottom" then
	if options.verticalAlignment == "bottom" then
		overlayStyle.bottom = options.verticalOffset .. "px"
		overlayStyle.bottom = options.verticalOffset .. "px"
	else
	else
		overlayStyle.top = options.verticalOffset .. "px"
		overlayStyle.top = options.verticalOffset .. "px"
	end
	end
	local overlayDiv = mw.html.create("div")
	local overlayDiv = mw.html.create("div")
		:css(overlayStyle)
		:css(overlayStyle)
		:wikitext(overlayMap)
		:wikitext(overlayMap)
	container
	container
		:node(overlayDiv)
		:node(overlayDiv)
		:wikitext(baseMap)
		:wikitext(baseMap)
	if not options.isThumbnail then
	if not options.isThumbnail then
		return tostring(container)
		return tostring(container)
	end
	end
	local classlist = container:getAttr("class")
	local classlist = container:getAttr("class")
	classlist = mw.ustring.gsub(classlist, "%a*"..options.align, "")
	classlist = mw.ustring.gsub(classlist, "%a*"..options.align, "")
	container:attr("class", classlist)
	container:attr("class", classlist)
	local outerCountainer = mw.html.create("div")
	local outerCountainer = mw.html.create("div")
		:addClass("mapframe-withOverlay-outerContainer")
		:addClass("mapframe-withOverlay-outerContainer")
		:addClass("mw-kartographer-container")
		:addClass("mw-kartographer-container")
		:addClass("thumb")
		:addClass("thumb")
	if options.align == "left" or options.align == "right" then
	if options.align == "left" or options.align == "right" then
		outerCountainer:addClass("t"..options.align)
		outerCountainer:addClass("t"..options.align)
	else -- alignment is "center"
	else -- alignment is "center"
		outerCountainer
		outerCountainer
			:addClass("tnone")
			:addClass("tnone")
			:addClass("center")
			:addClass("center")
	end
	end
	outerCountainer
	outerCountainer
		:tag("div")
		:tag("div")
			:addClass("thumbinner")
			:addClass("thumbinner")
			:css("width", options.width.."px")
			:css("width", options.width.."px")
			:node(container)
			:node(container)
			:node(options.caption and mw.html.create("div")
			:node(options.caption and mw.html.create("div")
				:addClass("thumbcaption")
				:addClass("thumbcaption")
				:wikitext(options.caption)
				:wikitext(options.caption)
			)
			)
	return tostring(outerCountainer)
	return tostring(outerCountainer)
end
end
--[[----------------------------------------------------------------------------
--[[----------------------------------------------------------------------------
 Package to be exported, i.e. methods which will available to templates and
 Package to be exported, i.e. methods which will available to templates and
 other modules.
 other modules.
----------------------------------------------------------------------------]]--
----------------------------------------------------------------------------]]--
local p = {}
local p = {}
-- Entry point for templates
-- Entry point for templates
function p.main(frame)
function p.main(frame)
	local parent = frame.getParent(frame)
	local parent = frame.getParent(frame)
	-- Check for overlay option
	-- Check for overlay option
	local overlay = util.getParameterValue(parent.args, 'overlay')
	local overlay = util.getParameterValue(parent.args, 'overlay')
	local hasOverlay = overlay and mw.text.trim(overlay) ~= ""
	local hasOverlay = overlay and mw.text.trim(overlay) ~= ""
	-- Check for switch option
	-- Check for switch option
	local switch = util.getParameterValue(parent.args, 'switch')
	local switch = util.getParameterValue(parent.args, 'switch')
	local isMulti = switch and mw.text.trim(switch) ~= ""
	local isMulti = switch and mw.text.trim(switch) ~= ""
	-- Create output by choosing method to suit options
	-- Create output by choosing method to suit options
	local output
	local output
	if hasOverlay then
	if hasOverlay then
		output = p.withOverlay(parent.args)
		output = p.withOverlay(parent.args)
	elseif isMulti then
	elseif isMulti then
		output = p.multi(parent.args)
		output = p.multi(parent.args)
	else
	else
		output = p._main(parent.args)
		output = p._main(parent.args)
	end
	end
	-- Preprocess output before returning it
	-- Preprocess output before returning it
	return frame:preprocess(output)
	return frame:preprocess(output)
end
end
-- Entry points for modules
-- Entry points for modules
function p._main(_args)
function p._main(_args)
	local args = util.trimArgs(_args)
	local args = util.trimArgs(_args)
	local tagContent = make.content(args)
	local tagContent = make.content(args)
	local display = mw.text.split(util.getParameterValue(args, 'display') or L10n.defaults.display, '%s*' .. L10n.str.dsep .. '%s*')
	local display = mw.text.split(util.getParameterValue(args, 'display') or L10n.defaults.display, '%s*' .. L10n.str.dsep .. '%s*')
	local displayInTitle = display[1] ==  L10n.str.title or display[2] ==  L10n.str.title
	local displayInTitle = display[1] ==  L10n.str.title or display[2] ==  L10n.str.title
	local displayInline = display[1] ==  L10n.str.inline or display[2] ==  L10n.str.inline
	local displayInline = display[1] ==  L10n.str.inline or display[2] ==  L10n.str.inline
	local output
	local output
	if displayInTitle and displayInline then
	if displayInTitle and displayInline then
		output = make.titleOutput(args, tagContent) .. make.inlineOutput(args, tagContent)
		output = make.titleOutput(args, tagContent) .. make.inlineOutput(args, tagContent)
	elseif displayInTitle then
	elseif displayInTitle then
		output = make.titleOutput(args, tagContent)
		output = make.titleOutput(args, tagContent)
	elseif displayInline then
	elseif displayInline then
		output = make.inlineOutput(args, tagContent)
		output = make.inlineOutput(args, tagContent)
	else
	else
		error(L10n.error.badDisplayPara)
		error(L10n.error.badDisplayPara)
	end
	end
	return output
	return output
end
end
function p.multi(_args)
function p.multi(_args)
	local args = util.trimArgs(_args)
	local args = util.trimArgs(_args)
	if not args[L10n.para.switch] then error(L10n.error.noSwitchPara, 0) end
	if not args[L10n.para.switch] then error(L10n.error.noSwitchPara, 0) end
	local switchParamValue = util.getParameterValue(args, 'switch')
	local switchParamValue = util.getParameterValue(args, 'switch')
	local switchLabels = util.tableFromList(switchParamValue)
	local switchLabels = util.tableFromList(switchParamValue)
	if #switchLabels == 1 then error(L10n.error.oneSwitchLabel, 0) end
	if #switchLabels == 1 then error(L10n.error.oneSwitchLabel, 0) end
	local mapframeArgs = {}
	local mapframeArgs = {}
	local switchParams = {}
	local switchParams = {}
	for name, val in pairs(args) do
	for name, val in pairs(args) do
		-- Copy to mapframeArgs, if not the switch labels or a switch parameter
		-- Copy to mapframeArgs, if not the switch labels or a switch parameter
		if val ~= switchParamValue and not string.match(val, "^"..L10n.str.switch..":") then
		if val ~= switchParamValue and not string.match(val, "^"..L10n.str.switch..":") then
			mapframeArgs[name] = val
			mapframeArgs[name] = val
		end
		end
		-- Check if this is a param to switch. If so, store the name and switch
		-- Check if this is a param to switch. If so, store the name and switch
		-- values in switchParams table.
		-- values in switchParams table.
		local switchList = string.match(val, "^"..L10n.str.switch..":(.+)")
		local switchList = string.match(val, "^"..L10n.str.switch..":(.+)")
		if switchList ~= nil then
		if switchList ~= nil then
			local values = util.tableFromList(switchList)
			local values = util.tableFromList(switchList)
			if #values == 1 then
			if #values == 1 then
				error(string.format(L10n.error.oneSwitchValue, name), 0)
				error(string.format(L10n.error.oneSwitchValue, name), 0)
			end
			end
			switchParams[name] = values
			switchParams[name] = values
		end
		end
	end
	end
	if util.tableCount(switchParams) == 0 then
	if util.tableCount(switchParams) == 0 then
		error(L10n.error.noSwitchLists, 0)
		error(L10n.error.noSwitchLists, 0)
	end
	end
	local switchCount = util.subTablesCount(switchParams)
	local switchCount = util.subTablesCount(switchParams)
	if not switchCount then
	if not switchCount then
		error(L10n.error.switchMismatches, 0)
		error(L10n.error.switchMismatches, 0)
	elseif switchCount > #switchLabels then
	elseif switchCount > #switchLabels then
		error(string.format(L10n.error.fewerSwitchLabels, switchCount, #switchLabels), 0)
		error(string.format(L10n.error.fewerSwitchLabels, switchCount, #switchLabels), 0)
	end
	end
	-- Ensure a plain frame will be used (thumbnail will be built by the
	-- Ensure a plain frame will be used (thumbnail will be built by the
	-- make.switcherHtml function if required, so that switcher options are
	-- make.switcherHtml function if required, so that switcher options are
	-- inside the thumnail)
	-- inside the thumnail)
	mapframeArgs.plain = "yes"
	mapframeArgs.plain = "yes"
	local switcher = {}
	local switcher = {}
	for i = 1, switchCount do
	for i = 1, switchCount do
		local label = switchLabels[i]
		local label = switchLabels[i]
		for name, values in pairs(switchParams) do
		for name, values in pairs(switchParams) do
			mapframeArgs[name] = values[i]
			mapframeArgs[name] = values[i]
		end
		end
		table.insert(switcher, {
		table.insert(switcher, {
			map = p._main(mapframeArgs),
			map = p._main(mapframeArgs),
			label = "Show "..label
			label = "Show "..label
		})
		})
	end
	end
	return make.switcherHtml(switcher, {
	return make.switcherHtml(switcher, {
		alignment = args["frame-align"] or "right",
		alignment = args["frame-align"] or "right",
		isThumbnail = (args.frame and not args.plain) and true or false,
		isThumbnail = (args.frame and not args.plain) and true or false,
		width = args["frame-width"] or L10n.defaults.frameWidth,
		width = args["frame-width"] or L10n.defaults.frameWidth,
		caption = args.text
		caption = args.text
	})
	})
end
end
function p.withOverlay(_args)
function p.withOverlay(_args)
	-- Get and trim wikitext for overlay map
	-- Get and trim wikitext for overlay map
	local overlayMap = _args.overlay
	local overlayMap = _args.overlay
	if type(overlayMap) == 'string' then
	if type(overlayMap) == 'string' then
		overlayMap = overlayMap:match('^%s*(.-)%s*$')
		overlayMap = overlayMap:match('^%s*(.-)%s*$')
	end
	end
	local isThumbnail = (util.getParameterValue(_args, "frame") and not util.getParameterValue(_args, "plain")) and true or false
	local isThumbnail = (util.getParameterValue(_args, "frame") and not util.getParameterValue(_args, "plain")) and true or false
	-- Get base map using the _main function, as a plain map
	-- Get base map using the _main function, as a plain map
	local args = util.trimArgs(_args)
	local args = util.trimArgs(_args)
	args.plain = "yes"
	args.plain = "yes"
	local basemap = p._main(args)
	local basemap = p._main(args)
	-- Extract overlay options from args
	-- Extract overlay options from args
	local overlayOptions = {
	local overlayOptions = {
		width = util.getParameterValue(args, "frameWidth") or L10n.defaults.frameWidth,
		width = util.getParameterValue(args, "frameWidth") or L10n.defaults.frameWidth,
		height = util.getParameterValue(args, "frameHeight") or L10n.defaults.frameHeight,
		height = util.getParameterValue(args, "frameHeight") or L10n.defaults.frameHeight,
		align = util.getParameterValue(args, "frameAlign") or L10n.defaults.frameAlign,
		align = util.getParameterValue(args, "frameAlign") or L10n.defaults.frameAlign,
		border = util.getParameterValue(args, "overlayBorder") or L10n.defaults.overlayBorder,
		border = util.getParameterValue(args, "overlayBorder") or L10n.defaults.overlayBorder,
		horizontalAlignment = util.getParameterValue(args, "overlayHorizontalAlignment") or L10n.defaults.overlayHorizontalAlignment,
		horizontalAlignment = util.getParameterValue(args, "overlayHorizontalAlignment") or L10n.defaults.overlayHorizontalAlignment,
		horizontalOffset = util.getParameterValue(args, "overlayHorizontalOffset") or L10n.defaults.overlayHorizontalOffset,
		horizontalOffset = util.getParameterValue(args, "overlayHorizontalOffset") or L10n.defaults.overlayHorizontalOffset,
		verticalAlignment = util.getParameterValue(args, "overlayVerticalAlignment") or L10n.defaults.overlayVerticalAlignment,
		verticalAlignment = util.getParameterValue(args, "overlayVerticalAlignment") or L10n.defaults.overlayVerticalAlignment,
		verticalOffset = util.getParameterValue(args, "overlayVerticalOffset") or L10n.defaults.overlayVerticalOffset,
		verticalOffset = util.getParameterValue(args, "overlayVerticalOffset") or L10n.defaults.overlayVerticalOffset,
		isThumbnail = isThumbnail,
		isThumbnail = isThumbnail,
		caption = util.getParameterValue(args, "text") or L10n.defaults.text
		caption = util.getParameterValue(args, "text") or L10n.defaults.text
	}
	}
	-- Make the HTML for the overlaying maps
	-- Make the HTML for the overlaying maps
	return make.overlayHtml(overlayMap, basemap, overlayOptions)
	return make.overlayHtml(overlayMap, basemap, overlayOptions)
end
end
return p
return p