Module:Map
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Map/doc
-- <nowiki>
local hc = require('Module:Paramtest').has_content
local icons
local mapVersionList
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 },
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 = {pin.iconSizeX, pin.iconSizeY }
elseif pin.iconSize then
pin.iconSize = {pin.iconSize, pin.iconSize}
end
if pin.iconAnchorX and pin.iconAnchorY then
pin.iconAnchor = {pin.iconAnchorX, pin.iconAnchorY }
elseif pin.iconAnchor then
pin.iconAnchor = {pin.iconAnchor, pin.iconAnchor}
end
if pin.popupAnchorX and pin.popupAnchorY then
pin.popupAnchor = {pin.popupAnchorX, pin.popupAnchorY }
elseif pin.popupAnchor then
pin.popupAnchor = {pin.popupAnchor, 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
ftjson.properties[i] = v
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))
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
}
}
}
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+0.5, pin.y-0.5, opts.plane
}
}
}
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+0.5, pin.y-0.5, opts.plane
}
}
}
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+0.5, pin.y-0.5, opts.plane
}
}
}
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+0.5, pin.y-0.5, opts.plane
}
}
}
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)
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+0.5, pin.y-0.5, opts.plane
}
}
}
if args.iconWikiLink then
args.iconWikiLink = mw.ext.GloopTweaks.filepath(args.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
-- 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+0.5, pin.y-0.5, opts.plane
}
}
}
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
pin.iconWikiLink = mw.ext.GloopTweaks.filepath(ic.icon)
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>