For faster navigation, this Iframe is preloading the Wikiwand page for Modulo:Map.

Modulo:Map

Dokumentado Dokumentado


Ŝablona programado Diskutoj Lua Testoj Subpaĝoj
Modulo Esperanto English

Modulo: Dokumentado


Se vi havas demandon pri ĉi tiu Lua-modulo, tiam vi povas demandi en la diskutejo pri Lua-moduloj. La Intervikiaj ligiloj estu metataj al Vikidatumoj. (Vidu Helpopaĝon pri tio.)
local getArgs = require('Module:Arguments').getArgs
local p = {}

function dbg(v, msg)
    mw.log((msg or '') .. mw.text.jsonEncode(v))
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),
        class = args.class,
    }
    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 args.image then
		args.description = (args.description or '') .. '[[file:' .. args.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' }
    end

    return tagname, geojson and mw.text.jsonEncode(geojson) or '', tagArgs
end

function p.tag(frame)
    local args = getArgs(frame)
    local tag, geojson, tagArgs = p._tag(args)
    return frame:extensionTag(tag, geojson, tagArgs)
end

function p.tagForGeoshapeUsingWikidataItemId(frame)
    local args = getArgs(frame)
    local tagname = args.type or 'maplink'
    if tagname ~= 'maplink' and tagname ~= 'mapframe' then error('unknown type "' .. tagname .. '"') end
 	local tagArgs = {
        text = args.text,
        class = args.class
    }
    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
    end
 
    local qid = args.qid
    if not qid then
    	qid = mw.wikibase.getEntityIdForCurrentPage()
    end
    if qid then
    	--TODO: récupérer latitude et longitude depuis Wikidata ?
    	return frame:extensionTag(tagname, mw.text.jsonEncode({
    		{['type'] = 'ExternalData', ['service'] = 'geoshape', ['ids'] = mw.text.trim(qid)},
    		{['type'] = 'ExternalData', ['service'] = 'geoline', ['ids'] = mw.text.trim(qid)}
    	}), tagArgs)
    else
    	return error('No Wikidata id found')
    end
end

return p
{{bottomLinkPreText}} {{bottomLinkText}}
Modulo:Map
Listen to this article

This browser is not supported by Wikiwand :(
Wikiwand requires a browser with modern capabilities in order to provide you with the best reading experience.
Please download and use one of the following browsers:

This article was just edited, click to reload
This article has been deleted on Wikipedia (Why?)

Back to homepage

Please click Add in the dialog above
Please click Allow in the top-left corner,
then click Install Now in the dialog
Please click Open in the download dialog,
then click Install
Please click the "Downloads" icon in the Safari toolbar, open the first download in the list,
then click Install
{{::$root.activation.text}}

Install Wikiwand

Install on Chrome Install on Firefox
Don't forget to rate us

Tell your friends about Wikiwand!

Gmail Facebook Twitter Link

Enjoying Wikiwand?

Tell your friends and spread the love:
Share on Gmail Share on Facebook Share on Twitter Share on Buffer

Our magic isn't perfect

You can help our automatic cover photo selection by reporting an unsuitable photo.

This photo is visually disturbing This photo is not a good choice

Thank you for helping!


Your input will affect cover photo selection, along with input from other users.

X

Get ready for Wikiwand 2.0 🎉! the new version arrives on September 1st! Don't want to wait?