Documentation for this module may be created at Module:Infobox mapframe/doc
local mf = require('Module:Mapframe')
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.25"
local DEFAULT_SHAPE_STROKE_WIDTH = "2"
local DEFAULT_SHAPE_STROKE_COLOR = "#FF0000"
local DEFAULT_SHAPE_FILL = "#606060"
local DEFAULT_SHAPE_FILL_OPACITY = "0.1"
local DEFAULT_LINE_STROKE_WIDTH = "5"
local DEFAULT_LINE_STROKE_COLOR = "#FF0000"
local DEFAULT_MARKER_COLOR = "#5E74F3"
local util = {}
function util.noop(info)
local DEFAULT_NOOP_OUTPUT = ""
-- uncomment this when debugging
-- DEFAULT_NOOP_OUTPUT = "debug: mapframe no-op: " .. info
-- mw.log(DEFAULT_NOOP_OUTPUT)
return DEFAULT_NOOP_OUTPUT
end
function util.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 util.shouldAutoRun(frame)
local pargs = frame.getParent(frame).args
local explicitlyOn = yesno(mw.text.trim(pargs.mapframe or ""))
if pargs.coordinates == "{{{coordinates}}}" then explicitlyOn = false end
if type(pargs.coordinates) == 'string' and string.match(pargs.coordinates, 'coord.missing') then explicitlyOn = false end
local onByDefault = (explicitlyOn == nil) and yesno(mw.text.trim(frame.args.onByDefault or ""), false)
return explicitlyOn or onByDefault
end
function util.argsFromAuto(frame)
local args = getArgs(frame, {parentFirst = true})
local fixedArgs = {}
for name, val in pairs(args) do
local fixedName = string.match(name, "^mapframe%-(.+)$")
if fixedName then
fixedArgs[fixedName] = val
elseif (name == "coordinates" or name == "coord" or name == "coordinate") and not fixedArgs.coord then
fixedArgs.coord = val
elseif (name == "id" or name == "qid") and not fixedArgs.id then
-- kept only so old template calls do not explode; ignored for data lookup
fixedArgs.id = val
elseif name == "captionstyle" and not fixedArgs.captionstyle then
fixedArgs.captionstyle = val
end
end
return fixedArgs
end
function util.parseCustomWikitext(customWikitext)
return infoboxImage({
args = {
image = customWikitext
}
})
end
function util.trackAndWarn(trackingCat, warning)
local title = mw.title.getCurrentTitle()
local results = title and title.namespace == 0 and trackingCat and '[[Category:'..trackingCat..']]' or ''
if warning then
local warn = require('Module:If preview')._warning
results = results .. warn({warning})
end
return results
end
function util.ternary(flag, other)
other = other or 'other'
flag = flag == 'none' and 'no' or flag
local yesNoOut = yesno(flag, other)
local yes = (yesNoOut == true)
local no = (yesNoOut == false)
return yes, no
end
-- True if value looks like an OSM relation id or a Commons Data page, not a Q-id
function util.isLocalGeoId(val)
if not val or val == '' then return false end
val = mw.text.trim(val)
if mw.ustring.match(val, '^[Qq]%d+$') then return false end
return true
end
local p = {}
p._caption = function(args)
if args.caption then
return args.caption
elseif args.switcher then
return util.noop("no caption or switcher")
end
return util.noop("no caption (Wikidata labels disabled)")
end
local tinyType = {
landmark = true,
railwaystation = true,
edu = true,
pass = true,
camera = true
}
p._main = function(_config)
local tracking = ''
local config = util.trimArgs(_config)
config.coord = config.coord or config.coordinates
-- Local geo sources only (OSM relation number or Commons Data title)
local osmId = config.osm or config["osm-id"] or config.relation
local fromPage = config.from
if config.id and util.isLocalGeoId(config.id) and not osmId then
osmId = config.id
end
local hasLocalGeo = util.isLocalGeoId(osmId) or (fromPage and fromPage ~= '')
if not config.coord and not hasLocalGeo and not config.custom then
return false, util.trackAndWarn('Pages using infobox mapframe with missing coordinates')
end
local args = {}
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"]
args["frame-lat"] = config["frame-lat"] or config["frame-latitude"]
args["frame-long"] = config["frame-long"] or config["frame-longitude"]
local zoom = config.zoom
if not zoom then
local getZoom = require('Module:Infobox dim')._zoom
zoom = getZoom({
length_km = config.length_km, length_mi = config.length_mi,
width_km = config.width_km, width_mi = config.width_mi,
area_km2 = config.area_km2, area_mi2 = config.area_mi2,
area_ha = config.area_ha, area_acre = config.area_acre,
type = config.type, population = config.population,
viewport_px = math.min(tonumber(args["frame-width"]) or 270, tonumber(args["frame-height"]) or 200)
})
end
args.zoom = zoom or DEFAULT_ZOOM
local forcePoint, suppressPoint = util.ternary(config.point)
local forceMarker, suppressMarker = util.ternary(config.marker, true)
forcePoint = forcePoint or forceMarker
suppressPoint = suppressPoint or suppressMarker
local showMarker = not suppressPoint and (forcePoint or config.coord or not hasLocalGeo)
local forceGeo, suppressGeo = util.ternary(config.wikidata)
-- "wikidata=" kept as a compatibility alias meaning "show shape/line from local geo ids"
local showShape = not suppressGeo and (forceGeo or hasLocalGeo)
local showLine = showShape
local forceShape, suppressShape = util.ternary(config.shape)
showShape = not suppressShape and (forceShape or showShape)
local shapeType = config.shape == 'inverse' and 'shape-inverse' or 'shape'
local forceLine, suppressLine = util.ternary(config.line)
showLine = not suppressLine and (forceLine or showLine)
local maskItem
if config.switcher == "zooms" then
local maxZoom = math.max(tonumber(args.zoom) or DEFAULT_ZOOM, 3)
local minZoom = 1
local midZoom = math.floor((maxZoom + minZoom) / 2)
args.switch = "zoomed in, zoomed midway, zoomed out"
args.zoom = string.format("SWITCH:%d,%d,%d", maxZoom, midZoom, minZoom)
elseif config.switcher == "geomasks" and config.geomask then
args.zoom = nil
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 config["geomask-labels"] then
maskLabels = mw.text.split(config["geomask-labels"], "%s*###%s*")
end
if #maskItems > 1 then
if #maskLabels ~= #maskItems then
maskLabels = {}
for i, item in ipairs(maskItems) do
table.insert(maskLabels, item)
end
end
args.switch = table.concat(maskLabels, "###")
maskItem = "SWITCH:" .. table.concat(maskItems, ",")
elseif #maskItems == 1 then
maskItem = maskItems[1]
end
elseif config.geomask and util.isLocalGeoId(config.geomask) then
maskItem = config.geomask
end
if (showLine or showShape) and hasLocalGeo and not maskItem
and not config.zoom and not (config.type and tinyType[config.type]) then
args.zoom = nil
end
if not maskItem and not showShape and not showLine and not showMarker and not config.coord then
return false, util.trackAndWarn('Pages using infobox mapframe with no geometry', 'No geometry specified for mapframe')
end
local argNumber = ''
local function incrementArgNumber()
if argNumber == '' then
argNumber = 2
else
argNumber = argNumber + 1
end
end
if maskItem then
args["type" .. argNumber] = "shape-inverse"
if mw.ustring.match(tostring(maskItem), '^SWITCH:') or tonumber(maskItem) then
args["id" .. argNumber] = maskItem
else
args["from" .. argNumber] = maskItem
end
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
if not config.zoom and not config.switcher then
args.zoom = nil
args["frame-coord"] = nil
args["frame-lat"] = nil
args["frame-long"] = nil
end
incrementArgNumber()
if not args.zoom then
args["type" .. argNumber] = "line"
if mw.ustring.match(tostring(maskItem), '^SWITCH:') or tonumber(maskItem) then
args["id" .. argNumber] = maskItem
else
args["from" .. argNumber] = maskItem
end
args["stroke-width" .. argNumber] = 0
incrementArgNumber()
end
end
if showShape and hasLocalGeo then
args["type" .. argNumber] = shapeType
if fromPage then
args["from" .. argNumber] = fromPage
elseif osmId then
args["id" .. argNumber] = osmId
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
if showLine and hasLocalGeo then
args["type" .. argNumber] = "line"
if fromPage then
args["from" .. argNumber] = fromPage
elseif osmId then
args["id" .. argNumber] = osmId
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
if showMarker then
args["type" .. argNumber] = "point"
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
if not maskItem and not showMarker and not hasLocalGeo then
if config.coord then
args["frame-coord"] = args["frame-coord"] or config.coord
end
tracking = tracking .. util.trackAndWarn('Pages using infobox mapframe with forced centering')
end
args["frame-coord"] = args["frame-coord"] or ""
args["frame-lat"] = args["frame-lat"] or ""
args["frame-long"] = args["frame-long"] or ""
local mapframe = args.switch and mf.multi(args) or mf._main(args)
return true, mapframe .. tracking
end
p.main = function(frame)
local parent = frame.getParent(frame)
local _, mapframe = p._main(parent.args)
return frame:preprocess(mapframe)
end
p.auto = function(frame)
if not util.shouldAutoRun(frame) then
return util.noop("auto should not autorun")
end
local args = util.argsFromAuto(frame)
if args.custom then
return frame:preprocess(util.parseCustomWikitext(args.custom))
end
local _, mapframe = p._main(args)
return frame:preprocess(mapframe)
end
p.autocaption = function(frame)
if not util.shouldAutoRun(frame) then
return util.noop("autocaption should not autorun")
end
local args = util.argsFromAuto(frame)
return p._caption(args)
end
p.autoWithCaption = function(frame)
if not util.shouldAutoRun(frame) then
return util.noop("autoWithCaption should not autorun")
end
local args = util.argsFromAuto(frame)
local wikitext
local ok
if args.custom then
ok = true
wikitext = util.parseCustomWikitext(args.custom)
else
ok, wikitext = p._main(args)
end
if not ok then return wikitext end
wikitext = frame:preprocess(wikitext)
local caption = p._caption(args)
local data = mw.html.create():wikitext(wikitext)
data:tag('div')
:addClass('infobox-caption')
:cssText(args.captionstyle)
:wikitext(caption)
return tostring(data)
end
return p