Module:Map

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to navigation Jump to search
Module documentation
This documentation is transcluded from Module:Map/doc. [edit] [history] [purge]
Module:Map requires Module:Paramtest.
Module:Map loads data from Module:Map/icons.
Module:Map loads data from Module:Map/versions.

Generates instances of interactive maps (maps and maplinks). Map id's are available at RuneScape:Map/mapIDs. Please use Module:Map/sandbox for testing.

Template invocation handlers have been moved to the Module:Map/templates submodule.

Jump to helper functions.

Per map arguments

used with createMap function.

  • x - X coordinate for map center
  • y - Y coordinate for map center
  • width - Width of mapframe (defaults to 300) in pixels
  • height - Height of mapframe (defaults to 300) in pixels
  • mapID - mapID, see RuneScape:Map/mapIDs (defaults to 28 surface)
  • plainTiles - Use plain tiles
  • mapVersion - Use a different set of map tiles than the current ones
  • plane - map plane (defaults to 0)
  • zoom - Zoom level (defaults to 2)
  • group - Group for features (all features in same group on same page appear on each others maps)
  • show - Allows showing groups in addition to the group set above, or if no group is set, show only the groups listed. A single group, or comma separated list of group names
  • text - Text to display for maplink
  • caption - Caption
  • etype - maplink or mapframe
  • features - String of the map features to generate. See bellow for possible values.
  • pins - A table (array) of pins. For arguments per pin see bellow.
  • nopreprocess - Giving any value will return the actual maplink/mapframe code (element and json)

Per pin arguments

Used with parseArgs function

  • x - X coordinate for map center
  • y - Y coordinate for map center
  • title - Title for the pin (tooltip text and at top of description)
  • description - Description for the pin (may contain wikitext)
  • icon - String of pin type to use, one of: greyPin, redPin, greenPin, bluePin, cyanPin, magentaPin, yellowPin
  • iconWikiLink - Wiki image to use as a marker, is case sensitive and spaces must be replaced by _(underscore). If this is specified iconSize must also be given
  • iconSize - x,y dimension in pixels for marker icon
    • iconSizeX - x dimension in pixels for marker icon
    • iconSizeY - y dimension in pixels for marker icon
  • iconAnchor - x,y offset in pixels of map point to image (from top right corner of image, positive down and right)
    • iconAnchorX - x offset in pixels of map point to image (from right side of image, positive to right)
    • iconAnchorY - y offset in pixels of map point to image (from top of image, positive down)
  • popupAnchor - x,y offset in pixels of popup (description) anchor to image anchor (down and to right is positive)
    • popupAnchorX - x offset in pixels of popup (description) anchor to image anchor (to right is positive)
    • popupAnchorY - y offset in pixels of popup (description) anchor to image anchor (down is positive)

Possible features strings

  • none
  • square or rectangle
  • polygon
  • line or lines
  • circle
  • pin or pins
  • dot or dots or circlemarker
  • pin-polygon or pins-polygon
  • pin-line or pins-line
  • pin-circle or pins-circle
  • dot-polygon or dots-polygon
  • dot-line or dots-line

Possible Styles

See RuneScape:Map for more information on styling map features. RuneScape:Map/Style args contains the possible syle arguments and which feature types they apply too and their use.

Pin

  • title
  • description
  • icon
  • iconWikiLink
  • iconSize
  • iconAnchor
  • popupAnchor

Dot / SquareDot

  • title
  • description
  • fill

Polygon (square/rectangle) / circle

  • title
  • description
  • stroke
  • stroke-opactiy
  • stroke-width
  • fill
  • fill-opacity

Line

  • title
  • description
  • stroke
  • stroke-opactiy
  • stroke-width

Text

  • label
  • title
  • description
  • direction
  • class

Helper functions

This module is a helper module to be used by other modules; it may not designed to be invoked directly. See RuneScape:Lua/Helper modules for a full list and more information. For a full list of modules using this helper click here

FunctionTypeUse
parseArgs(args, ptype)table, stringParses unnamed arguments into points. Returns the arguments given with a pins table and pin_count, x_range, y_range, squareX, squareY if not otherwise specified.
  • Arguments are strings in the format x,y or x:#,y:#,plane:#.... For arguments per pin see Module:Map.
  • ptype is the pin type (used to generate descriptions).
createMap(args)tableCreates a map or maplink for the args. Returns the preprocessed <maplink> or <map> tag. For poossible arguments see Module:Map.
featSquare(args, opts)table, tableCreates the json for a square, to be used within the FeatureCollection json. Args are: {x=#, y=#, [squareX=#], [squareY=#], [r=#], [styles]}. Opts are: {mapID=#, plane=#}.
featPolygon(args, opts)table, tableGenerates the json for a single polygon from the given points (to be used within the FeatureCollection json). Args are: {pins={}, [styles]} where pins are {x=#, y=#}. Opts are: {mapID=#, plane=#}.
featComplPolygon(args, opts, coords)table, table, tableGenerates the json for a complex polygon (can have cutouts etc) from the given points (to be used within the FeatureCollection json). Args are: {[styles]}. Opts are: {mapID=#, plane=#}. Coords are nested tables, the first nested table is the outer bounds, the following tables are cutouts (or filled regions within cutouts) and coordinates are unkeyed: { {{x,y}, {x,y}, {x,y}...}, {{x,y}, {x,y}, {x,y}...}... }.
featLine(args, opts)table, tableGenerates the json for a line from the given points (to be used within the FeatureCollection json). Args are: {pins={}, [close=boolean], [styles]} where pins are {x=#, y=#}. Opts are: {mapID=#, plane=#}.
featCircle(args, opts)table, tableGenerates the json for a circle (to be used within the FeatureCollection json). Args are: {x=#, y=#, [r=#], [styles]}, opts are: {mapID=#, plane=#}.
featPin(args, opts, pin)table, table, tableGenerates the json for a single pin (to be used within the FeatureCollection json). Args are: {[styles]}, opts are: {mapID=#, plane=#}, pin is {x=#, y=#, [desc=string]}
featIcon(args, opts, pin)table, table, tableGenerates the json for a single pin using a predefined icon from Module:Map/icons (to be used within the FeatureCollection json). Args are: {icon=string, [styles]}, opts are: {mapID=#, plane=#}, pin is {x=#, y=#, [desc=string]}
featDot(args, opts, pin)table, table, tableGenerates the json for a single dot (to be used within the FeatureCollection json). Dots are circles but like normal pins the scale with zoom. Args are: {[r=#], [styles]}, opts are: {mapID=#, plane=#}, pin is {x=#, y=#, [desc=string]}
featSqDot(args, opts, pin)table, table, tableGenerates the json for a single square dot (to be used within the FeatureCollection json). Square dots are squares but like normal pins the scale with zoom. Args are: {[styles]}, opts are: {mapID=#, plane=#}, pin is {x=#, y=#, [desc=string]}

-- <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>