Module:Map
Documentation for this module may be created at Module:Map/doc
-- <nowiki>
local hc = require('Module:Paramtest').has_content
local p = {}
local zoomSizes = {
{ 3, 8 },
{ 2, 4 },
{ 1, 2 },
{ 0, 1 },
{ -1, 1/2 },
{ -2, 1/4 },
{ -3, 1/8 }
}
-- Default size of maps (to calc zoom)
local default_size = 800 -- 800px for full screen
-- Map feature (overlay) types
local featureMap = {
none = {},
square = { square=true },
rectangle = { square=true },
polygon = { polygon=true },
line = { line=true },
lines = { line=true },
circle = { circle=true },
pin = { pins=true },
pins = { pins=true },
['pin-polygon'] = { polygon=true, pins=true },
['pins-polygon'] = { polygon=true, pins=true },
['pin-line'] = { line=true, pins=true },
['pins-line'] = { line=true, pins=true },
['pin-circle'] = { circle=true, pins=true },
['pins-circle'] = { circle=true, pins=true }
}
-- Possible properties
local simplestyle = {'title', 'description', 'marker-size', 'marker-symbol', 'marker-color',
'stroke', 'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity'}
local properties = {
polygon = { title=true, description=true, stroke=true, ['stroke-opacity']=true, ['stroke-width']=true, fill=true, ['fill-opacity']=true },
line = { title=true, description=true, stroke=true, ['stroke-opacity']=true, ['stroke-width']=true },
circle = { title=true, description=true, stroke=true, ['stroke-opacity']=true, ['stroke-width']=true, fill=true, ['fill-opacity']=true },
pin = { title=true, description=true, icon=true, iconWikiLink=true, iconSize=true, iconAnchor=true, popupAnchor=true}
}
-- Create JSON
function toJSON(j)
local json_good, json = pcall(mw.text.jsonEncode, j)--, mw.text.JSON_PRETTY)
if json_good then
return json
end
return error('Error converting to JSON')
end
-- Create SMW string
function setSMW(obj, prop)
mw.smw.set({[prop] = toJSON(obj)})
end
-- Create map html element
function createMapElement(elem, args, json)
local mapelem = mw.html.create(elem)
mapelem:attr(args):newline():wikitext(toJSON(json)):newline()
return mapelem
end
-- Create pin description
function parseDesc(args, pin, pgname, ptype)
local desc = {}
if ptype == 'item' then
desc = {
"'''Item''': ".. (args.name or pgname),
"'''Quantity''': ".. (pin.qty or 1)
}
if pin.respawn then
table.insert(desc, "'''Respawn time''': "..pin.respawn)
elseif args.respawn then
table.insert(desc, "'''Respawn time''': "..args.respawn)
end
if pin.notes then
table.insert(desc, "'''Notes''': "..pin.notes)
elseif args.notes then
table.insert(desc, "'''Notes''': "..args.notes)
end
elseif ptype == 'monster' then
table.insert(desc, "'''Monster''': " .. (args.name or pgname))
if pin.levels then
table.insert(desc, "'''Level(s)''': " .. pin.levels)
elseif args.levels then
table.insert(desc, "'''Level(s)''': " .. args.levels)
end
elseif ptype == 'npc' then
table.insert(desc, "'''NPC''': "..(args.name or pgname))
if pin.version then
table.insert(desc, "'''Version''': "..pin.version)
elseif args.version then
table.insert(desc, "'''Version''': "..args.version)
end
if pin.npcid then
table.insert(desc, "'''NPC ID''': "..pin.npcid)
elseif args.npcid then
table.insert(desc, "'''NPC ID''': "..args.npcid)
end
if pin.objectid then
table.insert(desc, "'''Object ID''': "..pin.objectid)
elseif args.objectid then
table.insert(desc, "'''Object ID''': "..args.objectid)
end
if pin.respawn then
table.insert(desc, "'''Respawn time''': "..pin.respawn)
elseif args.respawn then
table.insert(desc, "'''Respawn time''': "..args.respawn)
end
if pin.notes then
table.insert(desc, "'''Notes''': "..pin.notes)
elseif args.notes then
table.insert(desc, "'''Notes''': "..args.notes)
end
elseif ptype == 'object' then
table.insert(desc, "'''Object''': "..(args.name or pgname))
if pin.version then
table.insert(desc, "'''Version''': "..pin.version)
elseif args.version then
table.insert(desc, "'''Version''': "..args.version)
end
if pin.objectid then
table.insert(desc, "'''Object ID''': "..pin.objectid)
elseif args.objectid then
table.insert(desc, "'''Object ID''': "..args.objectid)
end
if pin.npcid then
table.insert(desc, "'''NPC ID''': "..pin.npcid)
elseif args.npcid then
table.insert(desc, "'''NPC ID''': "..args.npcid)
end
if pin.respawn then
table.insert(desc, "'''Respawn time''': "..pin.respawn)
elseif args.respawn then
table.insert(desc, "'''Respawn time''': "..args.respawn)
end
if pin.notes then
table.insert(desc, "'''Notes''': "..pin.notes)
elseif args.notes then
table.insert(desc, "'''Notes''': "..args.notes)
end
else
if args.desc then
table.insert(desc, args.desc)
end
if pin.desc then
table.insert(desc, pin.desc)
elseif pin.x and pin.y then
table.insert(desc, 'X,Y: '..pin.x..','..pin.y)
end
end
return table.concat(desc, '<br>')
end
-- Parse unnamed arguments (arg = pin)
function p.parseArgs(args, ptype)
args.pins = {}
local sep = args.sep or '%s*,%s*'
local pgname = mw.title.getCurrentTitle().text
local rng = {
xmin = 10000000,
xmax = -10000000,
ymin = 10000000,
ymax = -10000000
}
local i,cnt = 1,0
while (args[i]) do
local v = mw.text.trim(args[i])
if hc(v) then
local pin = {}
for u in mw.text.gsplit(v, sep) do
local _u = mw.text.split(u, '%s*:%s*')
if _u[2] then
local k = mw.text.trim(_u[1])
if k == 'x' or k == 'y' then
pin[k] = tonumber(mw.text.trim(_u[2]))
else
pin[k] = mw.text.trim(_u[2])
end
else
if pin.x then
pin.y = tonumber(_u[1])
else
pin.x = tonumber(_u[1])
end
end
end
if pin.x > rng.xmax then
rng.xmax = pin.x
end
if pin.x < rng.xmin then
rng.xmin = pin.x
end
if pin.y > rng.ymax then
rng.ymax = pin.y
end
if pin.y < rng.ymin then
rng.ymin = pin.y
end
-- Pin size/location args
if pin.iconSizeX and pin.iconSizeY then
pin.iconSize = {tonumber(pin.iconSizeX), tonumber(pin.iconSizeY) }
elseif pin.iconSize then
pin.iconSize = { tonumber(pin.iconSize), tonumber(pin.iconSize)}
end
if pin.iconAnchorX and pin.iconAnchorY then
pin.iconAnchor = {tonumber(pin.iconAnchorX), tonumber(pin.iconAnchorY) }
elseif pin.iconAnchor then
pin.iconAnchor = {tonumber(pin.iconAnchor), tonumber(pin.iconAnchor)}
end
if pin.popupAnchorX and pin.popupAnchorY then
pin.popupAnchor = {tonumber(pin.popupAnchorX), tonumber(pin.popupAnchorY) }
elseif pin.popupAnchor then
pin.popupAnchor = {tonumber(pin.popupAnchor), tonumber(pin.popupAnchor)}
end
pin.desc = parseDesc(args, pin, pgname, ptype)
table.insert( args.pins, pin)
cnt = cnt + 1
end
i = i + 1
end
-- In no anonymous args then x,y are pin
if cnt == 0 then
local x = tonumber(args.x) or 3233 -- Default is Lumbridge loadstone
local y = tonumber(args.y) or 3222
rng.xmax = x
rng.xmin = x
rng.ymax = y
rng.ymin = y
local desc = parseDesc(args, {}, pgname, ptype)
table.insert( args.pins, {x = x, y = y, desc = desc} )
cnt = cnt + 1
end
local xrange = rng.xmax - rng.xmin
local yrange = rng.ymax - rng.ymin
if not tonumber(args.x) then
args.x = math.floor(rng.xmin + xrange/2)
end
if not tonumber(args.y) then
args.y = math.floor(rng.ymin + yrange/2)
end
-- Default range (1 pin) is 40
if not tonumber(args.x_range) then
if xrange > 0 then
args.x_range = xrange
else
args.x_range = 40
end
end
if not tonumber(args.y_range) then
if yrange > 0 then
args.y_range = yrange
else
args.y_range = 40
end
end
-- Default square (1 pin) is 20
if not tonumber(args.squareX) then
if xrange > 0 then
args.squareX = xrange
else
args.squareX = 20
end
end
if not tonumber(args.squareY) then
if yrange > 0 then
args.squareY = yrange
else
args.squareY = 20
end
end
args.pin_count = cnt
if args.smw == 'yes' or args.smw == 'y' or tonumber(args.smw) == 1 then
args.smw = "Location JSON"
elseif args.smw == 'hist' then
args.smw = "Historic Location JSON"
else
args.smw = nil
end
return args
end
-- Add styles
function styles(ftjson, args, this, ptype)
local props = properties[ptype]
for i,v in pairs(args) do
if props[i] then
ftjson.properties[i] = v
end
end
for i,v in pairs(this) do
if props[i] then
ftjson.properties[i] = v
end
end
return ftjson
end
function p.map(frame)
return p.buildMap(frame:getParent().args)
end
-- Functions for templates --
function p.buildMap(arguments)
local args = {}
for i,v in pairs(arguments) do
args[i] = v
end
-- Allow map/element type per template easily
local inv_args = {}
for i,v in pairs(arguments) do
inv_args[i] = v
end
--[[ Each unnamed arg is 1 pin in format:
x,y
or
x:#,y:#,desc:#
]]
args = p.parseArgs(args, args.ptype)
if hc(args.iconSize) then
if string.find(args.iconSize, ',') then
local isize = mw.text.split(args.iconSize, '%s*,%s*')
args.iconSize = { tonumber(isize[1]) or 25, tonumber(isize[2]) or 25}
else
args.iconSize = { tonumber(args.iconSize) or 25, tonumber(args.iconSize) or 25}
end
end
if hc(args.iconAnchor) then
if string.find(args.iconAnchor, ',') then
local ianch = mw.text.split(args.iconAnchor, '%s*,%s*')
args.iconAnchor = { tonumber(ianch[1]) or 0, tonumber(ianch[2]) or 0}
else
args.iconAnchor = { tonumber(args.iconAnchor) or 0, tonumber(args.iconAnchor) or 0}
end
end
if hc(args.popupAnchor) then
if string.find(args.popupAnchor, ',') then
local panch = mw.text.split(args.popupAnchor, '%s*,%s*')
args.popupAnchor = { tonumber(panch[1]) or 0, tonumber(panch[2]) or 0}
else
args.popupAnchor = { tonumber(args.popupAnchor) or 0, tonumber(args.popupAnchor) or 0}
end
end
if args.showPins then
if args.pin_count > 1 then
if not hc(args.text) then
args.text = 'Show exact locations'
end
local capt = string.format('%s locations', args.pin_count)
if hc(args.caption) then
capt = args.caption
end
args.etype = 'maplink'
args.features = 'pins'
local link = tostring(p.createMap(args))
capt = capt .. link
args.etype = 'mapframe'
args.caption = ''
args.features = 'square'
local map = tostring(p.createMap(args))
local classes = 'mw-kartographer-container thumb'
if hc(args.align) then
local align = string.lower(args.align)
if align == 'left' then
classes = classes..' tleft'
elseif align == 'right' then
classes = classes..' tright'
else
classes = classes..' center'
end
else
classes = classes..' center'
end
local width = args.width or 300
local ret = mw.html.create('div')
ret:addClass(classes)
:tag('div')
:addClass('thumbinner')
:css('width', width .. 'px')
:node(map)
:tag('div')
:addClass('thumbcaption')
:css('text-align', 'center')
:node(capt)
return ret
end
end
if hc(inv_args.mtype) then
args.features = string.lower(inv_args.mtype)
end
if hc(args.mtype) then
args.features = string.lower(args.mtype)
end
if not args.features then
args.features = 'none'
end
args.etype = 'mapframe'
if hc(inv_args.type) then
args.etype = string.lower(inv_args.type)
end
if hc(args.type) then
args.etype = string.lower(args.type)
end
return p.createMap(args)
end
-- Function for creating map or link
function p.createMap(args)
local x, y = args.x, args.y
local opts = {
x = x,
y = y,
width = args.width or 300,
height = args.height or 300,
mapID = args.mapID or 0, -- RuneScape Surface
plane = tonumber(args.plane) or 0,
zoom = args.zoom or 2,
align = args.align or 'center',
icon = args.icon or 'greenPin'
}
if hc(args.group) then
opts.group = args.group
end
if hc(args.show) then
opts.show = args.show
end
-- plain map tiles
if hc(args.plaintiles) then
opts.plainTiles = 'true'
end
if hc(args.plainTiles) then
opts.plainTiles = 'true'
end
-- other map tile version
if hc(args.mapversion) then
opts.mapVersion = args.mapversion
end
if hc(args.mapVersion) then
opts.mapVersion = args.mapVersion
end
-- mapframe, maplink
local etype = 'mapframe'
if hc(args.etype) then
etype = args.etype
end
-- translate "centre" spelling for align
if opts.align == 'centre' then
opts.align = 'center'
end
-- Caption or link text
if etype == 'maplink' then
opts.text = args.text or 'Maplink'
if string.find(opts.text,'[%[%]]') then
return error('Text cannot contain links')
end
elseif hc(args.caption) then
opts.text = args.caption
else
opts.frameless = ''
end
local featColl, features = {}, {}
local smwstr = ""
if hc(args.features) then
local _features = string.lower(args.features)
features = featureMap[_features] or {}
end
if features.square then
local feat = p.featSquare(args, opts)
table.insert(featColl, feat)
if args.smw then
setSMW(feat, args.smw)
end
elseif features.circle then
local feat = p.featCircle(args, opts)
table.insert(featColl, feat)
if args.smw then
setSMW(feat, args.smw)
end
end
if features.polygon then
local feat = p.featPolygon(args, opts)
table.insert(featColl, feat)
if args.smw then
setSMW(feat, args.smw)
end
elseif features.line then
local feat = p.featLine(args, opts)
table.insert(featColl, feat)
if args.smw then
setSMW(feat, args.smw)
end
end
if features.pins then
if not opts.group then
opts.group = 'pins'
end
-- opts.icon = args.icon or 'greenPin'
for _,pin in ipairs(args.pins) do
local feat = p.featPin(args, opts, pin)
table.insert(featColl, feat)
if args.smw then
setSMW(feat, args.smw)
end
end
end
local json = {}
if #featColl > 0 then
json = {
type = 'FeatureCollection',
features = featColl
}
-- Zoom
local width,height = opts.width, opts.height
if etype == 'maplink' then
width,height = default_size, default_size
end
local x_range = tonumber(args.squareX) or 40
local y_range = tonumber(args.squareY) or 40
if tonumber(args.r) then
x_range = tonumber(args.r)
y_range = tonumber(args.r)
end
if tonumber(args.x_range) then
x_range = tonumber(args.x_range)
end
if tonumber(args.y_range) then
y_range = tonumber(args.y_range)
end
local zoom = -3
for i,v in ipairs(zoomSizes) do
local sqsx, sqsy = width/v[2], height/v[2]
if sqsx > x_range and sqsy > y_range then
zoom = v[1]
break
end
end
if zoom > 2 then
zoom = 2
end
if tonumber(args.zoom) then
opts.zoom = args.zoom
else
opts.zoom = zoom
end
end
local map = createMapElement(etype, opts, json)
if args.nopreprocess then
return tostring(map) .. smwstr
end
return mw.getCurrentFrame():preprocess(tostring(map) .. smwstr)
end
-- Create a square feature
function p.featSquare(args, opts)
local x, y = args.x, args.y
local squareX = tonumber(args.squareX) or 20
local squareY = tonumber(args.squareY) or 20
squareX = math.max(1, args.r or math.floor(squareX / 2))
squareY = math.max(1, args.r or math.floor(squareY / 2))
local ftjson = {
type = 'Feature',
properties = {['_']='_', mapID=opts.mapID, plane=opts.plane},
geometry = {
type = 'Polygon',
coordinates = {
{
{ x-squareX, y-squareY },
{ x-squareX, y+squareY },
{ x+squareX, y+squareY },
{ x+squareX, y-squareY }
}
}
}
}
ftjson = styles(ftjson, args, {}, 'polygon')
return ftjson
end
-- Create a polygon feature
function p.featPolygon(args, opts)
local points, lastpoint = {}, {}
for _,v in ipairs(args.pins) do
table.insert(points, {v.x, v.y,})
lastpoint = {v.x, v.y,}
end
-- Close polygon
if not (points[1][1] == lastpoint[1] and points[1][2] == lastpoint[2]) then
table.insert(points, {points[1][1], points[1][2]})
end
local ftjson = {
type = 'Feature',
properties = {['_']='_', mapID=opts.mapID, plane=opts.plane},
geometry = {
type = 'Polygon',
coordinates = { points }
}
}
ftjson = styles(ftjson, args, {}, 'polygon')
return ftjson
end
-- Create a line feature
function p.featLine(args, opts)
local points, lastpoint = {}, {}
for _,v in ipairs(args.pins) do
table.insert(points, {v.x, v.y,})
lastpoint = {v.x, v.y,}
end
if hc(args.close) then
-- Close line
if not (points[1][1] == lastpoint[1] and points[1][2] == lastpoint[2]) then
table.insert(points, {points[1][1], points[1][2]})
end
end
local ftjson = {
type = 'Feature',
properties = {
['_'] = '_',
shape = 'Line',
mapID = opts.mapID,
plane = opts.plane
},
geometry = {
type = 'LineString',
coordinates = points
}
}
ftjson = styles(ftjson, args, {}, 'line')
return ftjson
end
-- Create a circle feature
function p.featCircle(args, opts)
local rad = tonumber(args.r) or 10
local ftjson = {
type = 'Feature',
properties = {
['_']='_',
shape = 'Circle',
radius = rad,
mapID = opts.mapID,
plane = opts.plane
},
geometry = {
type = 'Point',
coordinates = {
args.x, args.y, opts.plane
}
}
}
ftjson = styles(ftjson, args, {}, 'circle')
return ftjson
end
-- Create a pin feature
-- Pin types: greyPin, greenPin, redPin
function p.featPin(args, opts, pin)
local desc = pin.desc or pin.x..', '..pin.y
local ftjson = {
type = 'Feature',
properties = {
providerID = 0,
description = desc,
mapID = opts.mapID,
plane = opts.plane
},
geometry = {
type = 'Point',
coordinates = {
pin.x, pin.y, opts.plane
}
}
}
if pin.iconWikiLink then
pin.iconWikiLink = mw.ext.GloopTweaks.filepath(pin.iconWikiLink)
end
ftjson = styles(ftjson, args, pin, 'pin')
if not (ftjson.properties.icon or ftjson.properties.iconWikiLink) then
ftjson.properties.icon = 'greenPin'
end
return ftjson
end
return p
-- </nowiki>