Toggle search
Search
Toggle menu
notifications
Toggle personal menu
Editing
Module:Map
From Dive Atlas
Views
Read
Edit source
View history
associated-pages
Module
Discussion
More actions
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
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] = ' </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
Summary:
Please note that all contributions to Dive Atlas may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Meta:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Template used on this page:
Module:Map/doc
(
edit
)