[view] [edit] [history] [purge] Documentation

This module provides input parameters for mapframe and maplink functions supported by Extension:Kartographer.

Usage:

{{#invoke:map|tag|type=maplink|geotype=Point|title=Example|latitude=59.0|longitude=29.0}} 
Parameters for Module:Map
Parameter Usage
type maplink or mapframe depending on which function should be invoked
geotype Point for individual points, Polygon for polygons
title Object name
latitude and longitude use 'NA' to disable coordinates, including the ones from wikidata
zoom Zoom level of the map
marker-symbol Symbol, letter, or number for displaying on the map as marker
marker-color Color of the map marker
group Group of markers (see, eat, drink, etc.)
show Which marker groups to show (by default shows the most common groups like see, eat, drink, ...)
data data=values fills the polygon given by data
data=world;;values fills the area outside of the polygon
image Name of the image shown in the thumbnail
width and height map width and map height in px or % of screen width, only for mapframe
wikidata if specified, the missing title/lat/long/image fields will be fetched from the respective wikidata entry fields

local getArgs = require('Module:Arguments').getArgs local p = {}  function dbg(v, msg)     mw.log((msg or '') .. mw.text.jsonEncode(v)) end  local function has_value (tab, val)     for index, value in ipairs(tab) do         if value == val then             return true         end     end      return false end  -- Parse all unnamed string parameters in a form of "latitude, longitude" into the real number pairs function getSequence(args)     local coords = {}     for ind, val in pairs( args ) do         if type(ind) == "number" then             local valid = false             local val2 = mw.text.split( val, ',', true )             -- allow for elevation             if #val2 >= 2 and #val2 <= 3 then                 local lat = tonumber(val2[1])                 local lon = tonumber(val2[2])                 if lat ~= nil and lon ~= nil then                     table.insert(coords, { lon, lat } )                     valid = true                 end             end             if not valid then error('Unnamed parameter #' .. ind .. ' "' .. val .. '" is not recognized as a valid "latitude,longitude" value') end         end     end     return coords end  --   See http://geojson.org/geojson-spec.html -- Convert a comma and semicolon separated numbers into geojson coordinate arrays -- Each geotype expects a certain array depth: --   Point           - [ lon, lat ]  All other types use point as the basic type --   MultiPoint      - array of points: [ point, ... ] --   LineString      - array of 2 or more points: [ point, point, ... ] --   MultiLineString - array of LineStrings: [ [ point, point, ... ], ... ] --   Polygon         - [ [ point, point, point, point, ... ], ... ] --                     each LinearRing is an array of 4 or more points, where first and last must be the same --                     first LinearRing is the exterior ring, subsequent rings are holes in it --   MultiPolygon    - array of Polygons: [ [ [ point, point, point, point, ... ], ... ], ... ] -- -- For example, for the LineString, data "p1;p2;p3" would be converted to [p1,p2,p3] (each "p" is a [lon,lat] value) -- LineString has the depth of "1" -- array of points (each point being a two value array) -- For Polygon, the same sequence "p1;p2;p3" would be converted to [[p1,p2,p3]] -- Which is an array of array of points. But sometimes we need to specify two subarrays of points: -- [[p1,p2],[p3]] (last point is in a separate array), and we do it with "p1;p2;;p3" -- Similarly, for MultiPolygon, "p1;p2;;;p3" would generate [[[p1,p2]],[[p3]]] -- function p.parseGeoSequence(args)     local result = p._parseGeoSequence(args)     if type(result) == 'string' then error(result) end     return result end  function p._parseGeoSequence(args)     local allTypes = {         -- how many nested array levels until we get to the Point,         -- second is the minimum number of values each Points array must have         Point           = { 1, 1 },         MultiPoint      = { 1, 0 },         LineString      = { 1, 2 },         MultiLineString = { 2, 2 },         Polygon         = { 2, 4 },         MultiPolygon    = { 3, 4 },     }      if not allTypes[args.geotype] then return ('Unknown geotype ' .. args.geotype) end     local levels, min = unpack(allTypes[args.geotype])      local result     result = {}     for i = 1, levels do result[i] = {} end     local gap = 0      -- Example for levels==3, converting "p1 ; p2 ; ; ; p3 ; ; p4" => [[[p1, p2]], [[p3],[p4]]]     -- This function will be called after each gap, and all values are done, so the above will call:     -- before p3:  gap=2, [],[],[p1,p2]            => [[[p1,p2]]],[],[]     -- before p4:  gap=1, [[[p1,p2]]],[],[p3]      => [[[p1,p2]]],[[p3]]],[]     -- the end,    gap=2, [[[p1,p2]]],[[p3]]],[p4] => [[[p1,p2]],[[p3],[p4]]],[],[]     -- Here, convert at "p1 ; ; " from [[],[p1]]     local closeArrays = function (gap)         if #result[levels] < min then             error('Each points array must be at least ' .. min .. ' values')         elseif min == 1 and #result[levels] ~= 1 then             -- Point             error('Point must have exactly one data point')         end         -- attach arrays in reverse order to the higher order ones         for i = levels, levels-gap+1, -1 do             table.insert(result[i-1], result[i])             result[i] = {}         end         return 0     end      local usedSequence = false     for val in mw.text.gsplit(args.data, ';', true) do         local val2 = mw.text.split(val, ',', true)         -- allow for elevation         if #val2 >= 2 and #val2 <= 3 and not usedSequence then             if gap > 0 then gap = closeArrays(gap) end             local lat = tonumber(val2[1])             local lon = tonumber(val2[2])             if lat == nil or lon == nil then return ('Bad data value "' .. val .. '"') end             table.insert(result[levels], { lon, lat } )         else             val = mw.text.trim(val)             if val == '' then                 usedSequence = false                 gap = gap + 1                 if (gap >= levels) then return ('Data must not skip more than ' .. levels-1 .. ' values') end             elseif usedSequence then                 return ('Coordinates may not be added right after the named sequence')             else                 if gap > 0 then                     gap = closeArrays(gap)                 elseif #result[levels] > 0 then                     return ('Named sequence "' .. val .. '" cannot be used in the middle of the sequence')                 end                  -- Parse value as a sequence name. Eventually we can load data from external data sources                 if val == 'values' then                     val = getSequence(args)                 elseif min == 4 and val == 'world' then                     val = {{36000,-180}, {36000,180}, {-36000,180}, {-36000,-180}, {36000,-180}}                 elseif tonumber(val) ~= nil then                     return ('Not a valid coordinate or a sequence name: ' .. val)                 else                     return ('Sequence "' .. val .. '" is not known. Try "values" or "world" (for Polygons), or specify values as lat,lon;lat,lon;... pairs')                 end                 result[levels] = val                 usedSequence = true             end         end     end     -- allow one empty last value (some might close the list with an extra semicolon)     if (gap > 1) then return ('Data values must not have blanks at the end') end     closeArrays(levels-1)     return args.geotype == 'Point' and result[1][1] or result[1] end  -- Run this function to check that the above works ok function p.parseGeoSequenceTest()     local testSeq = function(data, expected)         local result = getSequence(data)         if type(result) == 'table' then             local actual = mw.text.jsonEncode(result)             result = actual ~= expected and 'data="' .. mw.text.jsonEncode(data) .. '", actual="' .. actual .. '", expected="' .. expected .. '"<br>\n' or ''         else             result = result .. '<br>\n'         end         return result     end     local test = function(geotype, data, expected, values)         values = values or {}         values.geotype = geotype;         values.data = data;         local result = p._parseGeoSequence(values)         if type(result) == 'table' then             local actual = mw.text.jsonEncode(result)             result = actual ~= expected and 'geotype="' .. geotype .. '", data="' .. data .. '", actual="' .. actual .. '", expected="' .. expected .. '"<br>\n' or ''         else             result = 'geotype="' .. geotype .. '", data="' .. data .. '", error="' .. result .. '<br>\n'         end         return result     end     local values = {' 9 , 8 ','7,6'}     local result = '' ..             testSeq({}, '[]') ..             testSeq({'\t\n 1 \r,-10'}, '[[-10,1]]') ..             testSeq(values, '[[8,9],[6,7]]') ..             test('Point', '1,2', '[2,1]') ..             test('MultiPoint', '1,2;3,4;5,6', '[[2,1],[4,3],[6,5]]') ..             test('LineString', '1,2;3,4', '[[2,1],[4,3]]') ..             test('MultiLineString', '1,2;3,4', '[[[2,1],[4,3]]]') ..             test('MultiLineString', '1,2;3,4;;5,6;7,8', '[[[2,1],[4,3]],[[6,5],[8,7]]]') ..             test('Polygon', '1,2;3,4;5,6;1,2', '[[[2,1],[4,3],[6,5],[2,1]]]') ..             test('MultiPolygon', '1,2;3,4;5,6;1,2', '[[[[2,1],[4,3],[6,5],[2,1]]]]') ..             test('MultiPolygon', '1,2;3,4;5,6;1,2;;11,12;13,14;15,16;11,12', '[[[[2,1],[4,3],[6,5],[2,1]],[[12,11],[14,13],[16,15],[12,11]]]]') ..             test('MultiPolygon', '1,2;3,4;5,6;1,2;;;11,12;13,14;15,16;11,12', '[[[[2,1],[4,3],[6,5],[2,1]]],[[[12,11],[14,13],[16,15],[12,11]]]]') ..             test('MultiPolygon', '1,2;3,4;5,6;1,2;;;11,12;13,14;15,16;11,12;;21,22;23,24;25,26;21,22', '[[[[2,1],[4,3],[6,5],[2,1]]],[[[12,11],[14,13],[16,15],[12,11]],[[22,21],[24,23],[26,25],[22,21]]]]') ..             test('MultiLineString', 'values;;1,2;3,4', '[[[8,9],[6,7]],[[2,1],[4,3]]]', values) ..             test('Polygon', 'world;;world', '[[[36000,-180],[36000,180],[-36000,180],[-36000,-180],[36000,-180]],[[36000,-180],[36000,180],[-36000,180],[-36000,-180],[36000,-180]]]') ..             ''     return result ~= '' and result or 'Tests passed' end   function p._tag(args)     local tagname = args.type or 'maplink'     if tagname ~= 'maplink' and tagname ~= 'mapframe' then error('unknown type "' .. tagname .. '"') end      local geojson     local tagArgs = {         text = args.text,         zoom = tonumber(args.zoom),         latitude = tonumber(args.latitude),         longitude = tonumber(args.longitude),         group = args.group,         show = args.show,         class = args.class,         url = args.url,         image = args.image,     }     if (args.wikidata ~= nil) then     	local e = mw.wikibase.getEntity(args.wikidata)     	if e.claims ~= nil then     		if (not tagArgs.latitude or not tagArgs.longitude) then 		    	if e.claims.P625 ~= nil then 		    		tagArgs.latitude = e.claims.P625[1].mainsnak.datavalue.value.latitude 		    		tagArgs.longitude = e.claims.P625[1].mainsnak.datavalue.value.longitude 		    	end 		    end 		    if e.labels.en ~= nil then 		    	-- always try to fetch title, to get a reference in 'Wikidata entities used in this page' 	    		title = e.labels.en.value 	    	end 	    	if not args.title then 	    		args.title = title 	    	end 	    	--if not tagArgs.url then 	    	--	if e.claims.P856 ~= nil then 	    	--		tagArgs.url = e.claims.P856[1].mainsnak.datavalue.value 	    	--	end 	    	--end 	    	if not tagArgs.image then 	    		if e.claims.P18 ~= nil then 	    			tagArgs.image = e.claims.P18[1].mainsnak.datavalue.value 	    		end 	    	end 	    end     end     if not args.title then     	args.title = ''     end     if not tagArgs.url then 		tagArgs.url = '' 	end 	if not tagArgs.image then 		tagArgs.image = '' 	end 	tagArgs.title = args.title     if args.ismarker and (args.latitude == 'NA' or args.longitude == 'NA' or not tagArgs.latitude or not tagArgs.longitude) then     	return 'nowiki', '', tagArgs     end     if tagname == 'mapframe' then         tagArgs.width = args.width == nil and 420 or args.width         tagArgs.height = args.height == nil and 420 or args.height         tagArgs.align = args.align == nil and 'right' or args.align     elseif not args.class and (args.text == '' or args.text == '""') then 		-- Hide pushpin icon in front of an empty text link 		tagArgs.class = 'no-icon' 	end      if args.data == '' then args.data = nil end     if (not args.geotype) ~= (not args.data) then         -- one is given, but not the other         if args.data then             error('Parameter "data" is given, but "geotype" is not set. Use one of these: Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon')         elseif args.geotype == "Point" and tagArgs.latitude ~= nil and tagArgs.longitude ~= nil then             -- For Point geotype, it is enough to set latitude and logitude, and data will be set up automatically             args.data = tagArgs.latitude .. ',' .. tagArgs.longitude         else             error('Parameter data must be set. Use "values" to use all unnamed parameters as coordinates (lat,lon|lat,lon|...), "world" for the whole world, a combination to make a mask, e.g. "world;;values", or direct values "lat,lon;lat,lon..." with ";" as value separator')         end     end      -- Kartographer can now automatically calculate needed zoom & lat/long based on the data provided     -- Current version ignores mapmasks, but that will also be fixed soon.  Leaving this for now, but can be removed if all is good.     -- tagArgs.zoom = tagArgs.zoom == nil and 14 or tagArgs.zoom     -- tagArgs.latitude = tagArgs.latitude == nil and 51.47766 or tagArgs.latitude     -- tagArgs.longitude = tagArgs.longitude == nil and -0.00115 or tagArgs.longitude  	if tagArgs.image ~= '' then 		args.description = (args.description or '') .. '[[file:' .. tagArgs.image .. '|300px]]' 	end      if args.geotype then         geojson = {             type = "Feature",             properties = {                 title = args.title,                 description = args.description,                 ['marker-size'] = args['marker-size'],                 ['marker-symbol'] = args['marker-symbol'],                 ['marker-color'] = args['marker-color'],                 stroke = args.stroke,                 ['stroke-opacity'] = tonumber(args['stroke-opacity']),                 ['stroke-width'] = tonumber(args['stroke-width']),                 fill = args.fill,                 ['fill-opacity'] = tonumber(args['fill-opacity']),             },             geometry = {                 type = args.geotype,                 coordinates = p.parseGeoSequence(args)             }         }     end      if args.debug ~= nil then         local html = mw.html.create(tagname, not geojson and {selfClosing=true} or nil)         :attr(tagArgs)         if geojson then             html:wikitext( mw.text.jsonEncode(geojson, mw.text.JSON_PRETTY) )         end         return 'syntaxhighlight', tostring(html) .. mw.text.jsonEncode(args, mw.text.JSON_PRETTY), { lang = 'json', latitude=0, longitude=0, title='', url='' } 	end 	 	return tagname, geojson and mw.text.jsonEncode(geojson) or '', tagArgs end  function p.tag(frame) 	out = {} 	local args = getArgs(frame) 	local tag, geojson, tagArgs = p._tag(args) 	local listingTypes = {'see', 'eat', 'buy', 'drink', 'sleep'} 	if args.ismarker == 'yes' then 		if mw.title.getCurrentTitle().namespace == 0 and 		has_value({'do', unpack(listingTypes)}, string.lower(args.group)) -- prepend to copy of listingTypes,  		then 			out[#out + 1] = "[[Category:Has "..string.lower(args.group).." listing]]" 		end 		if geojson ~= '' then 			coordargs = {tagArgs.latitude, tagArgs.longitude, ['title'] = tagArgs.title} 			out[#out + 1] = '<span class="noprint listing-coordinates" style="display:none">' 			out[#out + 1] = '<span class="geo">' 			out[#out + 1] = '<abbr class="latitude">' .. tagArgs.latitude ..'</abbr>' 			out[#out + 1] = '<abbr class="longitude">' .. tagArgs.longitude ..'</abbr>' 			out[#out + 1] = '</span></span>' 			out[#out + 1] = '<span title="Map for this \''.. args.group ..'\' marker">' -- TODO 			out[#out + 1] = frame:extensionTag(tag, geojson, tagArgs) 			out[#out + 1] = '&#32;</span>' 			if mw.title.getCurrentTitle().namespace == 0 then 				out[#out + 1] = "[[Category:Has map markers]]" 			end 		else 			if mw.title.getCurrentTitle().namespace == 0 and 			   has_value(listingTypes, string.lower(args.group)) and 			   (args.latitude ~= 'NA' and args.longitude ~= 'NA') 			   then 				out[#out + 1] = "[[Category:"..string.lower(args.group).." listing with no coordinates]]" 			end 		end 		if mw.title.getCurrentTitle().namespace == 0 and 			   has_value({'city', 'vicinity'}, string.lower(args.group)) and 			   (args.wikidata == nil or args.wikidata == '') and 			   (args.image == nil or args.image == '') then 			out[#out + 1] = "[[Category:Region markers without wikidata]]" 		end 		if tagArgs.title ~= '' then 			title = '<span id="'.. mw.uri.anchorEncode(tagArgs.title) ..'" class="fn org listing-name">\'\'\''.. tagArgs.title ..'\'\'\'</span>' 		else 			title = '' 		end 		if tagArgs.url ~= '' then 			out[#out + 1] = '['.. tagArgs.url ..' '..title..']' 		else 			out[#out + 1] = title 		end 		return table.concat(out, "") 	else 		return frame:extensionTag(tag, geojson, tagArgs) 	end end  return p