local mf = require('মডিউল:মানচিত্রের কাঠামো') local getArgs = require('Module:Arguments').getArgs local yesno = require('Module:Yesno') local infoboxImage = require('Module:InfoboxImage').InfoboxImage  -- Defaults local DEFAULT_FRAME_WIDTH = "270" local DEFAULT_FRAME_HEIGHT = "200" local DEFAULT_ZOOM = 10 local DEFAULT_GEOMASK_STROKE_WIDTH = "1" local DEFAULT_GEOMASK_STROKE_COLOR = "#777777" local DEFAULT_GEOMASK_FILL = "#888888" local DEFAULT_GEOMASK_FILL_OPACITY = "0.5" local DEFAULT_SHAPE_STROKE_WIDTH = "3" local DEFAULT_SHAPE_STROKE_COLOR = "#FF0000" local DEFAULT_SHAPE_FILL = "#606060" local DEFAULT_SHAPE_FILL_OPACITY = "0.5" local DEFAULT_LINE_STROKE_WIDTH = "5" local DEFAULT_LINE_STROKE_COLOR = "#FF0000" local DEFAULT_MARKER_COLOR = "#5E74F3"  -- Trim whitespace from args, and remove empty args function trimArgs(argsTable) 	local cleanArgs = {} 	for key, val in pairs(argsTable) do 		if type(val) == 'string' then 			val = val:match('^%s*(.-)%s*$') 			if val ~=  then 				cleanArgs[key] = val 			end 		else 			cleanArgs[key] = val 		end 	end 	return cleanArgs end  function getBestStatement(item_id, property_id) 	if not(item_id) or not(mw.wikibase.isValidEntityId(item_id)) or not(mw.wikibase.entityExists(item_id)) then 		return false 	end 	local statements = mw.wikibase.getBestStatements(item_id, property_id) 	if not statements or #statements == 0 then 		return false 	end 	local hasNoValue = ( statements[1].mainsnak and statements[1].mainsnak.snaktype == 'novalue' ) 	if hasNoValue then 		return false 	end 	return statements[1] end  function hasWikidataProperty(item_id, property_id) 	return getBestStatement(item_id, property_id) and true or false end  function getStatementValue(statement) 	return statement and statement.mainsnak and statement.mainsnak.datavalue and statement.mainsnak.datavalue.value or nil end  function relatedEntity(item_id, property_id) 	local value = getStatementValue( getBestStatement(item_id, property_id) ) 	return value and value.id or false end  function idType(id) 	if not id then  		return nil 	elseif mw.ustring.match(id, "[Pp]%d+") then 		return "property" 	elseif mw.ustring.match(id, "[Qq]%d+") then 		return "item" 	else 		return nil 	end end  function getZoom(value, unit) 	local length_km 	if unit == 'km' then 		length_km = tonumber(value) 	elseif unit == 'mi' then 		length_km = tonumber(value)*1.609344 	elseif unit == 'km2' then 		length_km = math.sqrt(tonumber(value)) 	elseif unit == 'mi2' then 		length_km = math.sqrt(tonumber(value))*1.609344 	end 	-- max for zoom 2 is 6400km, for zoom 3 is 3200km, for zoom 4 is 1600km, etc 	local zoom = math.floor(8 - (math.log10(length_km) - 2)/(math.log10(2))) 	-- limit to values below 17 	zoom = math.min(17, zoom) 	-- take off 1 when calculated from area, to account for unusual shapes 	if unit == 'km2' or unit == 'mi2' then 		zoom = zoom - 1 	end 	-- minimum value is 1 	return math.max(1, zoom) end  function shouldAutoRun(frame) 	-- Check if should be running 	local explicitlyOn = yesno(mw.text.trim(frame.getParent(frame).args.mapframe or "")) -- true of false or nil 	local onByDefault = (explicitlyOn == nil) and yesno(mw.text.trim(frame.args.onByDefault or ""), false) -- true or false 	return explicitlyOn or onByDefault end  function argsFromAuto(frame) 	-- Get args from the frame (invoke call) and the parent (template call). 	-- Frame arguments are default values which are overridden by parent values 	-- when both are present 	local args = getArgs(frame, {parentFirst = true}) 	 	-- Discard args not prefixed with "mapframe-", remove that prefix from those that remain 	local fixedArgs = {} 	for name, val in pairs(args) do 		local fixedName = string.match(name, "^mapframe%-(.+)$" ) 		if fixedName then 			fixedArgs[fixedName] = val 		-- allow coord, coordinates, etc to be unprefixed 		elseif name == "coordinates" or name == "coord" or name == "coordinate" and not fixedArgs.coord then 			fixedArgs.coord = val 		-- allow id, qid to be unprefixed, map to id (if not already present) 		elseif name == "id" or name == "qid" and not fixedArgs.id then 			fixedArgs.id = val 		end 	end 	return fixedArgs end  local p = {}  p.autocaption = function(frame) 	if not shouldAutoRun(frame) then return "" end 	local args = argsFromAuto(frame) 	if args.caption then 		return args.caption 	elseif args.switcher then  		return "" 	end 	local maskItem 	local maskType = idType(args.geomask) 	if maskType == 'item' then 		maskItem = args.geomask 	elseif maskType == "property" then 		maskItem = relatedEntity(args.id or mw.wikibase.getEntityIdForCurrentPage(), args.geomask) 	end 	local maskItemLabel = maskItem and mw.wikibase.getLabel( maskItem ) 	return maskItemLabel and "Location in "..maskItemLabel or "" end  function parseCustomWikitext(customWikitext) 	-- infoboxImage will format an image if given wikitext containing an 	-- image, or else pass through the wikitext unmodified 	return infoboxImage({ 		args = { 			image = customWikitext 		} 	}) end  p.auto = function(frame) 	if not shouldAutoRun(frame) then return "" end 	local args = argsFromAuto(frame) 	if args.custom then 		return frame:preprocess(parseCustomWikitext(args.custom)) 	end 	local mapframe = p._main(args) 	return frame:preprocess(mapframe) end  p.main = function(frame) 	local parent = frame.getParent(frame) 	local parentArgs = parent.args 	local mapframe = p._main(parentArgs) 	return frame:preprocess(mapframe) end  p._main = function(_config) 	-- `config` is the args passed to this module 	local config = trimArgs(_config) 	 	-- Require wikidata item, or specified coords 	local wikidataId = config.id or mw.wikibase.getEntityIdForCurrentPage() 	if not(wikidataId) and not(config.coord) then 		return  	end  	-- Require coords (specified or from wikidata), so that map will be centred somewhere 	-- (P625 = coordinate location) 	local hasCoordinates = hasWikidataProperty(wikidataId, 'P625') or config.coordinates or config.coord 	if not hasCoordinates then   		return 	end  	-- `args` is the arguments which will be passed to the mapframe module 	local args = {}  	-- Some defaults/overrides for infobox presentation 	args.display = "inline" 	args.frame = "yes" 	args.plain = "yes" 	args["frame-width"]  = config["frame-width"] or config.width or DEFAULT_FRAME_WIDTH 	args["frame-height"] = config["frame-height"] or config.height or DEFAULT_FRAME_HEIGHT 	args["frame-align"]  = "center"  	args["frame-coord"] = config["frame-coordinates"] or config["frame-coord"] or "" 	-- Note: config["coordinates"] or config["coord"] should not be used for the alignment of the frame; 	-- see talk page ( https://en.wikipedia.org/wiki/Special:Diff/876492931 )  	-- deprecated lat and long parameters 	args["frame-lat"]    = config["frame-lat"] or config["frame-latitude"] or "" 	args["frame-long"]   = config["frame-long"] or config["frame-longitude"] or ""  	-- Calculate zoom from length or area (converted to km or km2) 	if config.length_km then 		args.zoom = getZoom(config.length_km, 'km') 	elseif config.length_mi then 		args.zoom = getZoom(config.length_mi, 'mi') 	elseif config.area_km2 then 		args.zoom = getZoom(config.area_km2, 'km2') 	elseif config.area_mi2 then 		args.zoom = getZoom(config.area_mi2, 'mi2') 	else 		args.zoom = config.zoom or DEFAULT_ZOOM 	end  	-- Conditionals: whether point, geomask should be shown 	local hasOsmRelationId = hasWikidataProperty(wikidataId, 'P402') -- P402 is OSM relation ID 	local shouldShowPointMarker; 	if config.point == "on" then 		shouldShowPointMarker = true  	elseif config.point == "none" then 		shouldShowPointMarker = false 	else 		shouldShowPointMarker = not(hasOsmRelationId) or (config.marker and config.marker ~= 'none') or (config.coordinates or config.coord) 	end 	local shouldShowShape = config.shape ~= 'none' 	local shapeType = config.shape == 'inverse' and 'shape-inverse' or 'shape' 	local shouldShowLine = config.line ~= 'none' 	local maskItem 	local useWikidata = true -- Use shapes/lines based on wikidata id 	-- Do not use wikidata when coords are specified (and not turned off), unless explicitly set 	if config.coord and shouldShowPointMarker then 		useWikidata = config.wikidata and true or false 	end 	 	-- Switcher 	if config.switcher == "zooms" then 		-- switching between zoom levels 		local maxZoom = math.max(tonumber(args.zoom), 3) -- what zoom would have otherwise been (if 3 or more, otherwise 3) 		local minZoom = 1 -- completely zoomed out 		local midZoom = math.floor((maxZoom + minZoom)/2) -- midway between maxn and min 		args.switch = "zoomed in, zoomed midway, zoomed out" 		args.zoom = string.format("SWITCH:%d,%d,%d", maxZoom, midZoom, minZoom) 	elseif config.switcher == "auto" then 		-- switching between P276 and P131 areas with recursive lookup, e.g. item's city, 		-- that city's state, and that state's country 		args.zoom = nil -- let kartographer determine the zoom 		local maskLabels = {} 		local maskItems = {} 		local maskItemId = relatedEntity(wikidataId, "P276") or  relatedEntity(wikidataId, "P131")  		local maskLabel = mw.wikibase.getLabel(maskItemId) 		while maskItemId and maskLabel and mw.text.trim(maskLabel) ~= "" do 			table.insert(maskLabels, maskLabel) 			table.insert(maskItems, maskItemId) 			maskItemId = maskItemId and relatedEntity(maskItemId, "P131") 			maskLabel = maskItemId and mw.wikibase.getLabel(maskItemId) 		end 		if #maskLabels > 1 then 			args.switch = table.concat(maskLabels, "###") 			maskItem = "SWITCH:" .. table.concat(maskItems, ",") 		elseif #maskLabels == 1 then 			maskItem = maskItemId[1] 		end 	elseif config.switcher == "geomasks" and config.geomask then 		-- switching between items in geomask parameter 		args.zoom = nil -- let kartographer determine the zoom 		local separator = (mw.ustring.find(config.geomask, "###", 0, true ) and "###") or 			(mw.ustring.find(config.geomask, ";", 0, true ) and ";") or "," 		local pattern = "%s*"..separator.."%s*" 		local maskItems = mw.text.split(mw.ustring.gsub(config.geomask, "SWITCH:", ""), pattern) 		local maskLabels = {} 		if #maskItems > 1 then 			for i, item in ipairs(maskItems) do 				table.insert(maskLabels, mw.wikibase.getLabel(item)) 			end 			args.switch = table.concat(maskLabels, "###") 			maskItem = "SWITCH:" .. table.concat(maskItems, ",") 		end 	end 	 	-- resolve geomask item id (if not using geomask switcher) 	if not maskItem then --   		local maskType = idType(config.geomask) 		if maskType == 'item' then 			maskItem = config.geomask 		elseif maskType == "property" then 			maskItem = relatedEntity(wikidataId, config.geomask) 		end 	end 	 	-- Keep track of arg numbering 	local argNumber =  	local function incrementArgNumber() 		if argNumber ==  then 			argNumber = 2 		else 			argNumber = argNumber + 1 		end 	end 	 	-- Geomask 	if maskItem then 		args["type"..argNumber] = "shape-inverse" 		args["id"..argNumber] = maskItem 		args["stroke-width"..argNumber] = config["geomask-stroke-width"] or DEFAULT_GEOMASK_STROKE_WIDTH 		args["stroke-color"..argNumber] = config["geomask-stroke-color"] or config["geomask-stroke-colour"] or DEFAULT_GEOMASK_STROKE_COLOR 		args["fill"..argNumber] = config["geomask-fill"] or DEFAULT_GEOMASK_FILL 		args["fill-opacity"..argNumber] = config["geomask-fill-opacity"] or DEFAULT_SHAPE_FILL_OPACITY 		-- Let kartographer determine zoom and position, unless it is explicitly set in config 		if not config.zoom and not config.switcher then 			args.zoom = nil 			args["frame-coord"] = nil 			args["frame-lat"] = nil 			args["frame-long"] = nil 	 			local maskArea = getStatementValue( getBestStatement(maskItem, 'P2046') ) 		end 		incrementArgNumber() 		-- Hack to fix phab:T255932 		if not args.zoom then 			args["type"..argNumber] = "line" 			args["id"..argNumber] = maskItem 			args["stroke-width"..argNumber] = 0 			incrementArgNumber() 		end 	end 	 	-- Shape (or shape-inverse) 	if useWikidata and shouldShowShape then 		args["type"..argNumber] = shapeType 		if config.id then args["id"..argNumber] = config.id end 		args["stroke-width"..argNumber] = config["shape-stroke-width"] or config["stroke-width"] or DEFAULT_SHAPE_STROKE_WIDTH 		args["stroke-color"..argNumber] = config["shape-stroke-color"] or config["shape-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_SHAPE_STROKE_COLOR 		args["fill"..argNumber] = config["shape-fill"] or DEFAULT_SHAPE_FILL 		args["fill-opacity"..argNumber] = config["shape-fill-opacity"] or DEFAULT_SHAPE_FILL_OPACITY 		incrementArgNumber() 	end 	 	-- Line 	if useWikidata and shouldShowLine then 		args["type"..argNumber] = "line" 		if config.id then args["id"..argNumber] = config.id end 		args["stroke-width"..argNumber] = config["line-stroke-width"] or config["stroke-width"] or DEFAULT_LINE_STROKE_WIDTH 		args["stroke-color"..argNumber] = config["line-stroke-color"] or config["line-stroke-colour"] or config["stroke-color"] or config["stroke-colour"] or DEFAULT_LINE_STROKE_COLOR 		incrementArgNumber() 	end  	-- Point 	if shouldShowPointMarker then 		args["type"..argNumber] = "point" 		if config.id then args["id"..argNumber] = config.id end 		if config.coord then args["coord"..argNumber] = config.coord end 		if config.marker then args["marker"..argNumber] = config.marker end 		args["marker-color"..argNumber] = config["marker-color"] or config["marker-colour"] or DEFAULT_MARKER_COLOR 		incrementArgNumber() 	end  	local mapframe = args.switch and mf.multi(args) or mf._main(args) 	local tracking = hasOsmRelationId and  or 'বিষয়শ্রেণী:উইকিউপাত্তে ওএসএম সম্পর্ক আইডি ছাড়া তথ্যছক মানচিত্রের কাঠামো' 	return mapframe .. tracking end  return p