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