বিষয়বস্তুতে চলুন

মডিউল:মানচিত্র

উইকিভ্রমণ থেকে

এই মডিউলটি এক্সটেনশন:কার্টোগ্রাফার দ্বারা সমর্থিত মানচিত্রের কাঠামো এবং মানচিত্রের সংযোগ ফাংশনের জন্য ইনপুট প্যারামিটার প্রদান করে।

ব্যবহার: {{#invoke:map|tag|type=maplink|geotype=Point|title=Example|latitude=59.0|longitude=29.0}}

মডিউল: মানচিত্রের প্যারামিটার
প্যারামিটার ব্যবহার
type maplink অথবা mapframe, কোন ফাংশনটি আহ্বান করা হবে তার উপর নির্ভর করে
geotype Point একক পয়েন্টের জন্য, Polygon বহুভুজের জন্য
title বিষয়ের নাম
latitude and longitude স্থানাঙ্ক নিষ্ক্রিয় করার জন্য 'NA' ব্যবহার করুন, এর মধ্যে উইকিউপাত্তের স্থানাঙ্কও অন্তর্ভুক্ত থাকবে
zoom মানচিত্র ছোট বা বড় করে দেখার স্তর
marker-symbol মানচিত্রে মার্কার হিসেবে প্রদর্শনের জন্য চিহ্ন, অক্ষর বা সংখ্যা
marker-color মানচিত্রে মার্কারের রং
group মার্কারের শ্রেণি (যেমন, দেখুন, আহার, পানীয় ইত্যাদি)
show কোন মার্কার শ্রেণিগুলো দেখানো হবে (পূর্বনির্ধারিতভাবে সবচেয়ে সাধারণ শ্রেণিগুলো দেখানো হয় যেমন দেখুন, আহার, পানীয় ...)
data data=values বহুভুজটি পূর্ণ করে যা data দ্বারা নির্ধারিত
data=world;;values বহুভুজের বাইরের এলাকা পূর্ণ করে
image থাম্বনেইলে প্রদর্শিত চিত্রের নাম
latitude and longitude পর্দার প্রস্থের পিক্সেল বা % অনুসারে মানচিত্রের প্রস্থ এবং উচ্চতা, শুধুমাত্র মানচিত্রের কাঠামোর জন্য
wikidata যদি নির্দিষ্ট করা হয়, তবে অনুপস্থিত title/lat/long/image ক্ষেত্রগুলো সংশ্লিষ্ট উইকিউপাত্তের আইটেম থেকে আমদানি করা হবে

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.bn ~= nil then
		    	-- always try to fetch title, to get a reference in 'Wikidata entities used in this page'
	    		title = e.labels.bn.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)
	if args.ismarker == 'yes' then
		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="এই \''.. args.group ..'\' নির্দেশকের জন্য মানচিত্র">' -- TODO
			out[#out + 1] = frame:extensionTag(tag, geojson, tagArgs)
			out[#out + 1] = '&#32;</span>'
			if mw.title.getCurrentTitle().namespace == 0 then
				out[#out + 1] = "[[বিষয়শ্রেণী:মানচিত্র নির্দেশক আছে]]"
			end
		else
			if mw.title.getCurrentTitle().namespace == 0 and
			   has_value({'see', 'eat', 'buy', 'drink', 'sleep'}, string.lower(args.group)) and
			   (args.latitude ~= 'NA' and args.longitude ~= 'NA')
			   then
				out[#out + 1] = "[[Category:স্থানাঙ্কহীন "..string.lower(args.group).." তালিকাভুক্তকরণ]]"
			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:উইকিউপাত্ত ছাড়া অঞ্চলের নির্দেশক]]"
		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