Aller au contenu

Module:Map

De Wikivoyage

Ce module fournit des paramètres d'entrée pour les fonctions map et maplink prises en charge par Extension:Kartographer.

Utilisation

[modifier]
{{#invoke:map|tag|type=|text=|geotype=|title=|latitude=|longitude=|zoom=|marker-symbol=|marker-size=|marker-color=|group=|show=|data=|image=|width=|height=}}
Paramètre Utilisation Syntaxe
type maplink ou mapframe selon la fonction qui doit être invoquée
text Texte qui apparaîtra dans le coin inférieur gauche de la carte ou en lieu et place des coordonnées
geotype Point pour les points individuels, Polygon pour les polygones
title Nom de l'objet
latitude et longitude
zoom Niveau de zoom de la carte Le niveau par défaut est de « 13 »
marker-symbol Symbole, lettre ou numéro à afficher sur la carte comme marqueur
  • Symboles les plus courants : town-hall hôtel de ville, information bureau d'information, aiport aéroport, harbor, rail, bus bus, rail-metro, museum , castle, restaurant restaurant, lodging, campsite, bar bar, theatre théâtre, bank banque, hospital hôpital, embassy ambassade (liste complète des icônes dans la section « Voir aussi »)
  • Lettres : -letter
  • Numéros : -number
marker-size Taille du symbole, lettre ou numéro à afficher sur la carte comme marqueur
  • Vide par défaut
  • large pour une plus grande taille
marker-color Couleur du marqueur de carte
  • 808080 couleur par défaut si le paramètre est vide
  • 008000 pour le symbole « town-hall » (ou 4682B4 s'il est dans la section « Voir ») ainsi que les symboles « information », « bank » et « hospital »
  • 8B0000 pour les symboles « airport », « harbor », « rail » et « bus » ainsi que les lettres et chiffres de la section « Aller »
  • 800080 pour les symboles « bus » et « rail-metro » ainsi que les lettres et chiffres de la section « Circuler »
  • 4682B4 pour les symboles « museum » et « castle » ainsi que les lettres et chiffres de la section « Voir »
  • FF4500 pour le symbole « restaurant » et les lettres et chiffres de la section « Manger »
  • 000000 pour les symboles « bar » et « theatre » ainsi que les lettres et chiffres de la section « Boire un verre / Sortir »
  • 000080 pour les symboles « lodging » et « campsite » ainsi que les lettres et chiffres de la section « Dormir »
  • FFD700 pour le symbole « embassy » ainsi que les lettres et chiffres de la section « Ambassades et consulats »
  • A0522D pour les lettres et chiffres des sections « Autres destinations » et « Aux environs »
group Groupe du marqueur (voir, manger, boire, etc.)
show Quels groupes de marqueurs afficher (par défaut, les groupes les plus courants comme voir, manger, boire, etc.)
data data=values remplit le polygone donné par data
data=world;;values remplit la zone à l'extérieur du polygone
image Nom de l'image affichée dans la vignette
width et height Largeur et hauteur de la carte en px ou % de la largeur de l'écran (uniquement pour mapframe)

Exemple

[modifier]

Avec maplink :

Avec mapframe :

{{#invoke:map|tag|type=mapframe|text=Carte de [[Liège]]|geotype=Point|title=Musée du Grand Curtius|latitude=50.64709 |longitude=5.58370 |zoom=15|image=LIEGE Palais Curtius - actuel Musée Curtius quai de Maestricht 13 (13-2013).JPG|marker-symbol=museum|marker-size=large|marker-color=4682B4|group=voir}}
Carte
Carte de Liège

Voir aussi

[modifier]

require('strict')
local getArgs = require('Module:Arguments').getArgs
local p = {}
local insert = table.insert
local concat = table.concat
local split = mw.text.split

local function dbg(v, msg)
    mw.log((msg or '') .. mw.text.jsonEncode(v))
end

-- See http://geojson.org/geojson-spec.html
-- Each geotype expects a certain array depth:
--   Point           - array of 2 or 3 numbers: [lon, lat] or [lon, lat, elevation]
--                     all other types use Point as the basic type
--   MultiPoint      - array of 0 or more Points: [Point, ... ]
--   LineString      - array of 2 or more Points: [Point, Point, ... ]
--   MultiLineString - array of 1 or more LineStrings: [[Point, Point, ...], ...]
--   Polygon         - array of 1 or more LinearRings: [[Point, Point, Point, Point, ... ], ...]
--                     each LinearRing is an array of 4 or more Points, where the first and last Points must be the same
--                     first LinearRing is the exterior ring, subsequent rings are holes in it
--   MultiPolygon    - array of 1 or more Polygons: [[[Point, Point, Point, Point, ...], ...], ...]
local allGeotypes = {
	-- 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},
}

-- Parse all unnamed string parameters in a form of “latitude,longitude” (or “latitude,longitude,elevation”) into the real number pairs
local function getSequence(args)
    local coords = {}
    for ind, val in pairs(args) do
        if type(ind) == 'number' then
            local valid = false
            local val2 = split(val, ',', true)
            if #val2 >= 2 and #val2 <= 3 then -- allow for elevation (ignored here)
                local lat = tonumber(val2[1])
                local lon = tonumber(val2[2])
                if lat ~= nil and lon ~= nil then
                    insert(coords, {lon, lat})
                    valid = true
                end
            end
            if not valid then
                error('Le paramètre non nommée #' .. ind .. ' « ' .. val .. ' » n’est pas reconnu en tant que valeur « latitude,longitude » valide')
            end
        end
    end
    return coords
end

-- Convert a comma and semicolon separated numbers into geojson coordinate arrays
-- 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)
    if not allGeotypes[args.geotype] then
        return 'Géotype « ' .. args.geotype .. ' » inconnu.'
    end
    local levels, min = unpack(allGeotypes[args.geotype])
    local result = {}

    -- 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 function closeArrays(gap)
        if #result[levels] < min then
            error('Chaque tableau de points doit contenir au moins ' .. min .. ' points.')
        elseif min == 1 and #result[levels] ~= 1 then -- Point
            error('Un point doit indiquer exactement un point de données')
        end
        -- attach arrays in reverse order to the higher order ones
        for i = levels, levels - gap + 1, -1 do
            insert(result[i - 1], result[i])
            result[i] = {}
        end
        return 0
    end

    for i = 1, levels do result[i] = {} end
    local gap = 0
    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 'Valeur de données « ' .. val .. ' » erronée.'
             end
            insert(result[levels], {lon, lat})
        else
            val = mw.text.trim(val)
            if val == '' then
                usedSequence = false
                gap = gap + 1
                if gap >= levels then
                    return 'Les données ne doivent pas sauter plus de ' .. (levels - 1) .. ' valeurs.'
                end
            elseif usedSequence then
                return 'Les coordonnées ne doivent pas être ajoutées juste après la séquence nommée.'
            else
                if gap > 0 then
                    gap = closeArrays(gap)
                elseif #result[levels] > 0 then
                    return 'La séquence nommée « ' .. val .. ' » ne peut pas être utilisée au milieu de la séquence.'
                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 = {{360, -180}, {360, 180}, {-360, 180}, {-360, -180}, {360, -180}}
                elseif tonumber(val) ~= nil then
                    return 'Pas une coordonnée valide ni un nom de séquence : « ' .. val .. ' ».'
                else
                    return 'La séquence « ' .. val .. '» est inconnue. Essayez « values » ou « world » (pour les polygones) ou spécifiez les valeurs comme une liste de paires « lat,lon;lat,lon;... »'
                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 'Les données ne doivent pas avoir de blancs multiples à la fin'
    end
    closeArrays(levels-1)
    return args.geotype == 'Point' and result[1][1] or result[1]
end

function p.parseGeoSequence(args)
    local result = p._parseGeoSequence(args)
    if type(result) == 'string' then
        error(result)
    end
    return result
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
             result = mw.text.jsonEncode(result)
        end
        return result == expected and '' or
            'Erreur :\n données = « ' .. mw.text.jsonEncode(data) .. ' »,\n résultat = « ' .. result .. ' »,\n attendu = « ' .. expected .. ' ».'
    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
            result = mw.text.jsonEncode(result)
        end
        return result == expected and '' or
            'Erreur :\n geotype = « ' .. geotype .. ' », données = « ' .. mw.text.jsonEncode(data) .. ' »,\n résultat = « ' .. result .. ' »,\n attendu = « ' .. expected .. ' ».'
    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', '[[[360,-180],[360,180],[-360,180],[-360,-180],[360,-180]],[[360,-180],[360,180],[-360,180],[-360,-180],[360,-180]]]')
    return result ~= '' and result or 'Tests réussis'
end

function p._tag(args)
    local tagname = args.type or 'maplink'
    if tagname ~= 'maplink' and tagname ~= 'mapframe' then
        error('Type de balise « ' .. tagname .. ' » inconnu (essayez « maplink » ou « mapframe »)')
    end
    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,
        alt = args.alt,
        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 not args.title then
                -- always try to fetch title, to get a reference in 'Wikidata entities used in this page'
                if e.labels.fr ~= nil then
                    args.title = e.labels.fr.value
                elseif e.labels.en ~= nil then
                    args.title = e.labels.en.value
                end
            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
    args.title = args.title or ''
    tagArgs.title = args.title
    tagArgs.alt = tagArgs.alt or ''
    tagArgs.url = tagArgs.url or ''
    tagArgs.image = tagArgs.image or ''
    if tagArgs.image ~= '' then
        args.description = '[[File:' .. tagArgs.image .. '|300px|]]'
            .. (args.description and '<br /> <small>' .. args.description .. '</small>' or '')
    end
    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 or 420
        tagArgs.height = args.height or 420
        tagArgs.align = args.align or 'right'
    elseif not args.class and (args.text == '' or args.text == '""') then
        tagArgs.class = 'no-icon' -- Hide pushpin icon in front of an empty text link
    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('Le paramètre « data » est fourni, mais « geotype » ne l’est pas (utilisez un de ces types : 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('Le paramètre « data » doit être défini. Utiliser « values » pour utiliser tous les paramètres non nommés en tant que coordonnées (lat,lon|lat,lon|...), « world » pour le monde entier, une combinaison comme  « world;values » pour créer un masque, ou bien directement des coordonnées « lat,lon;lat,lon... » avec « ; » comme séparateur de points')
        end
    end
    local geojson
    -- Kartographer can now automatically calculate the needed latitude/longitude/zoom, 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.latitude = tagArgs.latitude or 0
    --tagArgs.longitude = tagArgs.longitude or 0
    --tagArgs.zoom = tagArgs.zoom or 14
    if args.geotype then
        geojson = {
            type = 'Feature',
            properties = {
                title = args.title,
                description = args.description,
                alt = args.alt,
                ['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

local listingMarkerTypes = {'see', 'eat', 'buy', 'drink', 'sleep'}
local regionMarkerTypes = {'city', 'vicinity'}

function p.tag(frame)
    local args = getArgs(frame)
    local tag, geojson, tagArgs = p._tag(args)
    local out = {}

    local function has_value(tab, val)
        for index, value in ipairs(tab) do
            if value == val then
                return true
            end
        end
        return false
    end

    if args.ismarker == 'yes' then
        if mw.title.getCurrentTitle().namespace == 0
        and has_value({'do', unpack(listingMarkerTypes)}, string.lower(args.group)) -- prepend to copy of listingMarkerTypes
        then
           insert(out, '[[Category:Has ' .. string.lower(args.group) .. ' listing]]')
        end
        if geojson ~= '' then
           -- local coordargs = { tagArgs.latitude, tagArgs.longitude, ['title'] = tagArgs.title }
           insert(out, '<span class="noprint listing-coordinates" style="display:none">'
                ..   '<span class="geo">'
                ..     '<abbr class="latitude">' .. tagArgs.latitude .. '</abbr>'
                ..     '<abbr class="longitude">' .. tagArgs.longitude .. '</abbr>'
                ..   '</span>'
                .. '</span>'
                .. '<span title="Carte pour le marqueur « '.. args.group ..' »">' -- TODO
                .. frame:extensionTag(tag, geojson, tagArgs)
                .. '&#32;</span>')
            if mw.title.getCurrentTitle().namespace == 0 then
                insert(out, '[[Category:Article avec marqueur]]')
            end
        else
            if mw.title.getCurrentTitle().namespace == 0 and has_value(listingMarkerTypes, string.lower(args.group))
            and (args.latitude ~= 'NA' and args.longitude ~= 'NA') then
                insert(out, '[[Category:' .. string.lower(args.group).. ' listing with no coordinates]]')
            end
        end
        if mw.title.getCurrentTitle().namespace == 0 and has_value(regionMarkerTypes, string.lower(args.group))
        then
             if args.wikidata == nil or args.wikidata == '' then
                 insert(out, '[[Category:Region markers without wikidata]]')
             end
             if args.image == nil or args.image == '' then
                insert(out, '[[Category:Region markers without image]]')
            end
        end
        if tagArgs.title ~= '' then
            --title = '<b id="' .. mw.uri.anchorEncode(tagArgs.title) .. '" class="fn org listing-name">' .. tagArgs.title .. '</b>'
            insert(out, '<b class="fn org listing-name">' .. tagArgs.title .. '</b>')
        end
        if tagArgs.alt ~= '' then
            insert(out, ' <em>(' .. tagArgs.alt .. ')</em>')
        end
        return concat(out, '')
    else
        return frame:extensionTag(tag, geojson, tagArgs)
    end
end

return p