Module:Sandbox
From HorizonXI Wiki
Documentation for this module may be created at Module:Sandbox/doc
-- <nowiki> local hc = require('Module:Paramtest').has_content local icons local mapVersionList local fileCache = {} 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 }, linestring = { line=true }, circle = { circle=true }, pin = { pins=true }, pins = { pins=true }, dot = { dots=true }, dots = { dots=true }, sqdot = { sqdot=true }, sqdots = { sqdot=true }, circlemarker = { cmarker=true }, icons = { icons=true }, icon = { icons=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 }, ['dot-polygon'] = { polygon=true, dots=true }, ['dots-polygon'] = { polygon=true, dots=true }, ['dot-line'] = { line=true, dots=true }, ['dots-line'] = { line=true, dots=true }, ['sqdot-polygon'] = { polygon=true, sqdot=true }, ['sqdot-polygon'] = { polygon=true, sqdot=true }, ['sqdot-line'] = { line=true, sqdot=true }, ['sqdot-line'] = { line=true, sqdot=true }, text = { text=true } } -- Possible properties 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 }, dot = { title=true, description=true, fill=true, iconSize=true }, sqdot = { title=true, description=true, fill=true, iconSize=true }, cmarker = { 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}, text = { title=true, description=true, label=true, direction=true, class=true } } local numprops = {'stroke-opacity', 'stroke-width', 'fill-opacity'} -- 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 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.item 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 == 'npc' then if pin.npcname then table.insert(desc, "'''NPC''': "..pin.npcname) elseif args.npcname then table.insert(desc, "'''NPC''': "..args.npcname) else table.insert(desc, "'''NPC''': "..pgname) end 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''': "..(pin.objectname or args.objectname 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 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 if type(v) == "table" then ftjson.properties[i] = mw.clone(v) else ftjson.properties[i] = v end end end for i,v in pairs(this) do if props[i] then ftjson.properties[i] = v end end for _,v in ipairs(numprops) do if ftjson.properties[v] then ftjson.properties[v] = tonumber(ftjson.properties[v]) end end return ftjson end -- Functions for templates were moved to the /templates submodule! -- -- Function for creating map or link function p.createMap(args) local opts = { mapID = args.mapID or 28, -- RuneScape Surface plane = tonumber(args.plane) or 0, } local featColl, features = {}, {} if hc(args.features) then local _features = string.lower(args.features) features = featureMap[_features] or {} end if features.square then table.insert(featColl, p.featSquare(args, opts)) elseif features.circle then table.insert(featColl, p.featCircle(args, opts)) end if features.polygon then table.insert(featColl, p.featPolygon(args, opts)) elseif features.line then table.insert(featColl, p.featLine(args, opts)) end if features.text then for _,pin in ipairs(args.pins) do table.insert(featColl, p.featText(args, opts, pin)) 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 table.insert(featColl, p.featPin(args, opts, pin)) end elseif features.icons then if not opts.group then opts.group = 'pins' end for _,pin in ipairs(args.pins) do table.insert(featColl, p.featIcon(args, opts, pin)) end elseif features.dots then if not opts.group then opts.group = 'dots' end for _,pin in ipairs(args.pins) do table.insert(featColl, p.featDot(args, opts, pin)) end elseif features.sqdots then if not opts.group then opts.group = 'dots' end for _,pin in ipairs(args.pins) do table.insert(featColl, p.featSqDot(args, opts, pin)) end elseif features.cmarker then if not opts.group then opts.group = 'dots' end for _,pin in ipairs(args.pins) do table.insert(featColl, p.featCirMark(args, opts, pin)) end end local json = {} if #featColl > 0 then json = { type = 'FeatureCollection', features = featColl } end return p.createFeatMap(args, json) end -- Function for creating map or link with features already generated function p.createFeatMap(args, ftcoljson) local x, y = args.x, args.y local opts = { x = x, y = y, width = args.width or 300, height = args.height or 300, mapID = 28, -- RuneScape Surface is default plane = tonumber(args.plane) or 0, zoom = args.zoom or 2, align = args.align or 'center' } -- make sure mapID passed as number 0 works if type( tonumber(args.mapID) ) == 'number' then opts.mapID = args.mapID end 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) or hc(args.mapVersion) then local mapvers = args.mapversion if hc(args.mapVersion) then mapvers = args.mapVersion end if not mapVersionList then mapVersionList = mw.loadData('Module:Map/versions') end if mapVersionList[mapvers] then opts.mapVersion = mapVersionList[mapvers] else opts.mapVersion = mapvers end 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 -- Zoom if type( tonumber(args.zoom) ) == 'number' then opts.zoom = args.zoom else 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 opts.zoom = zoom end local map = createMapElement(etype, opts, ftcoljson) if args.nopreprocess then return map end return mw.getCurrentFrame():preprocess(tostring(map)) 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)) if args.jagexCoords then x = x + 0.5 y = y + 0.5 end 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 complex polygon feature (allows nested coords array) function p.featComplPolygon(args, opts, coords) local ftjson = { type = 'Feature', properties = {['_']='_', mapID=opts.mapID, plane=opts.plane}, geometry = { type = 'Polygon', coordinates = coords } } 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 } } } -- Center circles on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end ftjson = styles(ftjson, args, {}, 'circle') return ftjson end -- Create a text label feature function p.featText(args, opts, pin) local desc = pin.desc or args.desc local ftjson = { type = 'Feature', properties = { shape = 'Text', description = desc, mapID = opts.mapID, plane = opts.plane }, geometry = { type = 'Point', coordinates = { pin.x, pin.y, opts.plane -- pin.x+0.5, pin.y-0.5, opts.plane } } } -- Center text on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end ftjson = styles(ftjson, args, pin, 'text') return ftjson end -- Create a dot type marker feature function p.featDot(args, opts, pin) local desc = pin.desc or pin.x..', '..pin.y local ftjson = { type = 'Feature', properties = { shape = 'Dot', description = desc, mapID = opts.mapID, plane = opts.plane }, geometry = { type = 'Point', coordinates = { pin.x, pin.y, opts.plane -- pin.x+0.5, pin.y-0.5, opts.plane } } } -- Center dots on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end ftjson = styles(ftjson, args, pin, 'dot') return ftjson end -- Create a square dot marker type feature function p.featSqDot(args, opts, pin) local desc = pin.desc or pin.x..', '..pin.y local ftjson = { type = 'Feature', properties = { shape = 'SquareDot', description = desc, mapID = opts.mapID, plane = opts.plane }, geometry = { type = 'Point', coordinates = { pin.x, pin.y, opts.plane -- pin.x+0.5, pin.y-0.5, opts.plane } } } -- Center square dots on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end ftjson = styles(ftjson, args, pin, 'sqdot') return ftjson end -- Create a circlemarker feature (like a pin it rescales on zoom) function p.featCirMark(args, opts, pin) local rad = tonumber(args.r) or 10 local desc = pin.desc or pin.x..', '..pin.y local ftjson = { type = 'Feature', properties = { shape = 'CircleMarker', radius = rad, description = desc, mapID = opts.mapID, plane = opts.plane }, geometry = { type = 'Point', coordinates = { pin.x, pin.y, opts.plane -- pin.x+0.5, pin.y-0.5, opts.plane } } } -- Center circle marker on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end ftjson = styles(ftjson, args, pin, 'cmarker') return ftjson end -- Create a pin feature -- Pin types: greyPin, redPin, greenPin, bluePin, cyanPin, magentaPin, yellowPin function p.featPin(args, opts, pin) mw.logObject(args) mw.logObject(opts) mw.logObject(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 -- pin.x+0.5, pin.y+0.5, opts.plane } } } -- Center pin on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end if args.iconWikiLink then if string.find(args.iconWikiLink, 'https://') or string.find(args.iconWikiLink, 'http://') then elseif fileCache[args.iconWikiLink] then args.iconWikiLink = fileCache[args.iconWikiLink] else local link = mw.ext.GloopTweaks.filepath(args.iconWikiLink) fileCache[args.iconWikiLink] = link args.iconWikiLink = link end if not args.popupAnchor and not pin.popupAnchor then args.popupAnchor = {0,0} end 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 -- Predefined icons for pins froom [[Module:Map/icons]] function p.featIcon(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 -- pin.x+0.5, pin.y-0.5, opts.plane } } } -- Center icon on tile, when using tile based coordinates eg from in game if args.jagexCoords then ftjson.geometry.coordinates[1] = ftjson.geometry.coordinates[1] + 0.5 ftjson.geometry.coordinates[2] = ftjson.geometry.coordinates[2] + 0.5 end if not icons then icons = mw.loadData('Module:Map/icons') end local ic = pin.icon or args.icon ic = icons[ic] if not ic then error('Invalid icon name, see [[Module:Map/icons]] for available icons and aliases') end if fileCache[ic.icon] then pin.iconWikiLink = fileCache[ic.icon] else local link = mw.ext.GloopTweaks.filepath(ic.icon) fileCache[ic.icon] = link pin.iconWikiLink = link end pin.iconSize = {ic.iconSize[1], ic.iconSize[2]} pin.iconAnchor = {ic.iconAnchor[1], ic.iconAnchor[2]} pin.popupAnchor = {ic.popupAnchor[1], ic.popupAnchor[2]} ftjson = styles(ftjson, args, pin, 'pin') return ftjson end return p -- </nowiki>