Modul:Mapframe
Die Dokumentation für dieses Modul kann unter Modul:Mapframe/doc erstellt werden
-- inserting a mapframe map -- This edition was made for the special needs of Wikivoyage. For a use at a -- Wikipedia please use https://en.wikipedia.org/wiki/Module:Mapframe -- documentation local Mapframe = { suite = 'Mapframe', serial = '2023-01-14', item = 52554979 } -- module import -- require( 'strict' ) local cd = require( 'Module:Coordinates' ) local mg = mw.loadData( 'Module:Marker utilities/Groups' ) local mi = require( 'Module:Mapshape utilities/i18n' ) local mp = require( 'Module:Mapframe/Params' ) local mu = require( 'Module:Mapshape utilities' ) local wu = require( 'Module:Wikidata utilities' ) local yn = require( 'Module:Yesno' ) -- module variable and administration local mf = { content = {}, entityId = nil, wikilang = nil, moduleInterface = Mapframe } -- return decimal coordinate if possible local function toDec( coord, dir ) if mu.isSet( coord ) then local t = cd.toDec( coord, dir, 6 ) if t.error == 0 then return t.dec end end return nil end -- split coordinates to latitude and longitude local function _parseCoords( coords ) local lat = nil local long = nil if not mu.isSet( coords ) then return nil, nil end coords = coords:upper() local count if not coords:find( '[,]' ) and coords:find( '[NS]' ) then coords, count = coords:gsub( '([NS])', '%1,' ) -- adding separator end local parts = mw.text.split( coords, ',', true ) if #parts == 2 or #parts == 3 then -- 3: including elevation lat = mw.text.trim( parts[ 1 ] ) long = mw.text.trim( parts[ 2 ] ) -- check for mask borders if lat:find( '36000', 1, true ) and long:find( '180', 1, true ) then lat = tonumber( lat ) long = tonumber( long ) else lat = toDec( lat, 'lat' ) long = toDec( long, 'long' ) end if not lat or not long then return nil, nil end end return lat, long end -- parse set of coordinates -- programmer of function: user Yurik (Yuri Astrakhan), see Modul:Map local function parseCoords( geoType, coords ) local geoTypes = { Point = { levels = 1, min = 1 }, MultiPoint = { levels = 1, min = 2 }, LineString = { levels = 1, min = 2 }, MultiLineString = { levels = 2, min = 2 }, Polygon = { levels = 2, min = 4 }, MultiPolygon = { levels = 3, min = 4 } } local levels = geoTypes[ geoType ].levels local min = geoTypes[ geoType ].min local results = {} for i = 1, levels, 1 do results[ i ] = {} end local gap = 0 local errors = '' local closeArrays = function( gap ) if #results[ levels ] < min then errors = errors .. mw.ustring.format( mi.mfMinValues, min ) .. ' ' elseif min == 1 and #results[ levels ] ~= 1 then -- Point errors = errors .. mi.mfExactlyOne .. ' ' end for i = levels, levels-gap+1, -1 do table.insert( results[ i-1 ], results[ i ] ) results[ i ] = {} end return 0 -- reset gap end local points = mw.text.split( coords, ';', true ) local lat, long, val for i = 1, #points, 1 do val = mw.text.trim( points[ i ] ) if val == '' then gap = gap + 1 if gap >= levels then errors = errors .. mi.mfTooManyLevels .. ' ' end else lat, long = _parseCoords( val ) if lat and long then if gap > 0 then gap = closeArrays( gap ) end table.insert( results[ levels ], { long, lat } ) else errors = errors .. mi.mfBadData .. ' ' end end end closeArrays( levels - 1 ) if errors == '' then return geoType == 'Point' and results[ 1 ][ 1 ] or results[ 1 ], errors else return nil, errors end end -- get individual coordinate from Wikidata local function getWdCoords( id ) if mu.isSet( id ) then local c = wu.getValue( id, 'P625' ) if c ~= '' then return c.latitude, c.longitude end end return nil, nil end -- preparing title and description for geoJSON object local function getTitle( title, description, image, firstId, ids, service ) local function geomaskTitle() if service == 'geomask' then title = mw.ustring.format( mi.geomask, title ) end end -- getting title if only one id if title == '' then title = nil end if mu.isSet( firstId ) and firstId == ids then title = mu.addLink( title or mu.getTitle( firstId ), firstId, mf.entityId, mf.wikiLang ) geomaskTitle() if not mu.isSet( description ) and not mu.isSet( image ) then image = mu.getImage( firstId ) end else title = title or mw.title.getCurrentTitle().subpageText geomaskTitle() end if not mu.isSet( description ) and mu.isSet( image ) then description = '[[File:' .. image .. '|100x100px]]' end return title, description end -- check for mapshapes Wikidata ids local function idMatch( only, exclude, id ) only = only or '' exclude = exclude or '' if only == '' and exclude == '' then return true end local function isIn( list ) local parts = mw.text.split( list, ',', true ) for i = 1, #parts, 1 do if mw.text.trim( id ) == mw.text.trim( parts[ i ] ) then return true end end return false end if only ~= '' then return isIn( only ) else return not isIn( exclude ) end end -- make GeoJSON object for tag call local function makeGeoJSON( args, argIndex ) local service = '' for key, value in pairs( mp.services ) do for key2, value2 in ipairs( value ) do if args.service == value2 then service = key break end end if service ~= '' then break end end if service == '' then return mi.mfNoService end local world = '36000,-180;36000,180;-36000,180;-36000,-180;36000,-180;;' local coordinates, description, errors, firstId, geojson, geoType, i, id local ids, image, lat, link, long, properties, rgb, stroke, title, values -- default title and description if service ~= 'page' then title = args.title description = args.description image = args.image end -- processing coordinate data instead of Wikidata IDs args.coord = mw.ustring.gsub( args.coord or '', ';*$', '' ) if mu.isSet( args.coord ) and service ~= 'page' and service ~= 'shapes' then if mu.isSet( args.wikidata ) then return mi.mfTogether end if service == 'point' then args.coord = mw.ustring.gsub( args.coord, ';;*', ';' ) if mw.ustring.find( args.coord, ';', 1, true ) then geoType = 'MultiPoint' else geoType = 'Point' end elseif service == 'geoline' then args.coord = mw.ustring.gsub( args.coord, ';;;*', ';;' ) if mw.ustring.find( args.coord, ';;', 1, true ) then geoType = 'MultiLineString' else geoType = 'LineString' end elseif service == 'geoshape' or service == 'geomask' then if service == 'geomask' then args.coord = world .. args.coord end args.coord = mw.ustring.gsub( args.coord, ';;;;*', ';;;' ) if mw.ustring.find( args.coord, ';;', 1, true ) then geoType = 'MultiPolygon' else geoType = 'Polygon' end end coordinates, errors = parseCoords( geoType, args.coord ) if not coordinates then return errors end title, description = getTitle( title, description, image, nil, nil, service ) if geoType == 'Point' or geoType == 'MultiPoint' then properties = { title = title, description = description, [ 'marker-symbol' ] = mu.getParameter( args.marker, nil ), [ 'marker-color' ] = mu.getParameter( args.markerColor, mi.defaultMarkerColor ) } else stroke = args.stroke or '' if stroke == '' then stroke = mi.defaultStroke end properties = { title = title, description = description, fill = mu.getParameter( args.fill, mi.defaultFill ), [ 'fill-opacity' ] = mu.getNumber( args.fillOpacity, mi.defaultFillOpacity ), stroke = stroke, [ 'stroke-width' ] = mu.getNumber( args.strokeWidth, mi.defaultStrokeWidth ), [ 'stroke-opacity' ] = mu.getNumber( args.strokeOpacity, mi.defaultStrokeOpacity ) } end geojson = { type = 'Feature', geometry = { type = geoType, coordinates = coordinates }, properties = properties } table.insert( mf.content, mw.text.jsonEncode( geojson ) ) return '' -- no errors end -- processing Wikidata and Wikimedia Commons data if service == 'shapes' and not mi.excludeOSM then -- in case of shapes multiple GeoJSON objects are returned if not mu.isSet( args.wikidata ) then return mi.mfNoWikidata end values = mu.getMapshapes( args.wikidata ) if #values == 0 then return mi.mfNoParts end args.defaultType = mu.getParameter( args.defaultType, 'geoline' ) stroke = args.stroke or '' if stroke == '' then stroke = args.defaultColor or '' end if stroke == '' then stroke = mi.defaultStroke end if not string.find( stroke, '#', 1, true ) then stroke = '#' .. stroke end for i = 1, #values, 1 do id = values[ i ].id if idMatch( args.only, args.exclude, id ) then title = mu.addLink( mw.wikibase.label( id ) or id, id, mf.entityId, mf.wikiLang ) description = mu.getImage( id ) if description == '' then description = nil -- else -- description = '[[File:' .. description .. '|141px]]' end rgb = mu.getColor( id ) if rgb == '' then rgb = stroke end geojson = { type = 'ExternalData', service = args.defaultType, ids = id, properties = { title = title, description = description, fill = mu.getParameter( args.fill, mi.defaultFill ), [ 'fill-opacity' ] = mu.getNumber( args.fillOpacity, mi.defaultFillOpacity ), stroke = rgb, [ 'stroke-width' ] = mu.getNumber( args.strokeWidth, mi.defaultShapesWidth ), [ 'stroke-opacity' ] = mu.getNumber( args.strokeOpacity, mi.defaultShapesOpacity ) } } -- collecting multiple geojson codes table.insert( mf.content, mw.text.jsonEncode( geojson ) ) end end return '' -- objects already inserted, no errors elseif service == 'page' then -- data from Wikimedia Commons if args.commons then geojson = { type = 'ExternalData', service = 'page', title = mw.ustring.gsub( args.commons, '[Dd]ata:', '' ) } else return mi.mfNoCommons end elseif service == 'point' then ids = mw.text.split( args.wikidata, ',', true ) coordinates = {} for i = 1, #ids, 1 do id = mw.text.trim( ids[ i ] ) if id ~= '' and mw.wikibase.isValidEntityId( id ) then lat, long = getWdCoords( id ) if lat and long then table.insert( coordinates, { long, lat } ) if #coordinates == 1 then title, description = getTitle( title, description, image, id, id, service ) end end end end if #coordinates == 0 then return mi.mfNoWdCoord end i = #coordinates == 1 geojson = { type = 'Feature', geometry = { type = i and 'Point' or 'MultiPoint', coordinates = i and coordinates[ 1 ] or coordinates }, properties = { title = title, description = description, [ 'marker-symbol' ] = mu.getParameter( args.marker, nil ), [ 'marker-color' ] = mu.getParameter( args.markerColor, mi.defaultMarkerColor ) } } -- geoline or geoshape/geomask elseif not mi.excludeOSM then if mu.isSet( args.wikidata ) then ids = args.wikidata else ids = mf.entityId end if not mu.isSet( ids ) then return mi.mfNoWikidata end -- getting first id firstId = mu.getFirstId( ids ) title, description = getTitle( title, description, image, firstId, ids, service ) -- getting color from first id stroke = args.stroke or '' if stroke == '' then stroke = mu.getColor( firstId ) if stroke == '' then stroke = mi.defaultStroke end end if service == 'geoshape' and argIndex > 0 then args.fill = mu.getParameter( args.fill, mi.defaultColors[ argIndex ] ) end geojson = { type = 'ExternalData', service = service, ids = ids, properties = { title = title, description = description, fill = mu.getParameter( args.fill, mi.defaultFill ), [ 'fill-opacity' ] = mu.getNumber( args.fillOpacity, mi.defaultFillOpacity ), stroke = stroke, [ 'stroke-width' ] = mu.getNumber( args.strokeWidth, mi.defaultStrokeWidth ), [ 'stroke-opacity' ] = mu.getNumber( args.strokeOpacity, mi.defaultStrokeOpacity ) } } end table.insert( mf.content, mw.text.jsonEncode( geojson ) ) return '' end -- processing multiple shape definitions local function makeTagContent( args ) local errors = '' local r = mu.getParameter( args.raw, nil ) if r then return r, false, errors end local err = false local commons, service, single, wikidata local function mergeArgs( indx ) service = args[ 'type' .. indx ] or '' commons = args[ 'page' .. indx ] or '' if commons ~= '' then service = 'page' end wikidata = args[ 'wikidata' .. indx ] or '' if service == '' and wikidata ~= '' then service = 'geomask' end end -- mapgroup parameters if args.groupWikidata ~= '' then single = { service = 'geomask', wikidata = args.groupWikidata, fill = args.fillMask } errors = errors .. makeGeoJSON( single, 0 ) end if args.highlightWikidata ~= '' then single = { service = 'geoshape', wikidata = args.highlightWikidata, fill = mu.getParameter( args.fill, mi.defaultHighlight ) } errors = errors .. makeGeoJSON( single, 0 ) end local argsIndex = '' mergeArgs( argsIndex ) while service ~= '' do -- remove index from args parameters and copy them to single single = { commons = commons, wikidata = wikidata, service = service } if commons ~= '' and wikidata ~= '' then err = true end for k, v in pairs( args ) do if v == '' then v = nil end if string.match( k, '^[%a\-]+' .. argsIndex .. '$' ) then single[ string.gsub( k, argsIndex, '' ) ] = v end end if argsIndex == '' then argsIndex = 1 end errors = errors .. makeGeoJSON( single, argsIndex ) argsIndex = argsIndex + 1 mergeArgs( argsIndex ) -- stop if there is no service anymore end if #mf.content == 0 then return nil, err, errors elseif #mf.content == 1 then return mf.content[ 1 ], err, errors else return '[' .. table.concat( mf.content, ',') .. ']', err, errors end end -- calling mapframe/maplink tag; addings shapes local function _mapframe( args, frame ) local tagArgs = {} mf.entityId = mw.wikibase.getEntityIdForCurrentPage() mf.wikiLang = mw.getContentLanguage():getCode() -- auto-center if tagArgs.latitude = nil or tagArgs.longitude = nil tagArgs.latitude = toDec( args.lat, 'lat' ) tagArgs.longitude = toDec( args.long, 'long' ) if not tagArgs.latitude or not tagArgs.longitude then if args.coords ~= '' then tagArgs.latitude, tagArgs.longitude = _parseCoords( args.coords ) else tagArgs.latitude = nil tagArgs.longitude = nil end end tagArgs.zoom = tonumber( args.zoom ) -- auto-zoom if tagArgs.zoom = nil if tagArgs.zoom then tagArgs.zoom = math.floor( tagArgs.zoom ) if tagArgs.zoom < 1 or tagArgs.zoom > mi.maxZoomLevel then tagArgs.zoom = nil end end if tagArgs.latitude and tagArgs.longitude and not tagArgs.zoom then tagArgs.zoom = mi.defaultZoom end if args.tagName == 'mapframe' then tagArgs.align = mu.getParameter( args.align, 'right' ) if args.width == 'full' then tagArgs.width = 'full' tagArgs.align = 'center' else tagArgs.width = mu.getSize( args.width, mi.defaultWidth ) + mi.borderAdjustment -- 2px: inside borders end tagArgs.height = mu.getSize( args.height, mi.defaultHeight ) end tagArgs.show = args.show if tagArgs.show ~= '' then if args.group ~= '' then tagArgs.group = args.group else if not tagArgs.show:find( ',' ) then tagArgs.group = tagArgs.show else tagArgs.group = mi.defaultGroup end end else tagArgs.show = mg.showAll tagArgs.group = mu.checkGroup( args.group ) end tagArgs.group = mu.translateGroup( tagArgs.group ) if not mw.ustring.find( tagArgs.show, tagArgs.group ) then tagArgs.show = tagArgs.show .. ',' .. tagArgs.group end if yn( args.plain, false ) then tagArgs.frameless = '1' else tagArgs.text = args.name if tagArgs.text == '' and args.tagName == 'mapframe' then tagArgs.text = string.format( mi.mapOf, mw.title.getCurrentTitle().subpageText ) end end tagArgs.class = args.class if args.tagName == 'maplink' then if tagArgs.class == '' and ( tagArgs.text == '' or tagArgs.text == '""' ) then -- Hide pushpin icon in front of an empty text link tagArgs.class = 'no-icon' end end local tagContent, err, errors = makeTagContent( args ) local result = frame:extensionTag( args.tagName, tagContent, tagArgs ) if err then result = result .. mi.mfTogether2 end if mu.isSet( errors ) then result = result .. '<span class="error">' .. errors .. '</span>' .. mi.mfErrorCateg end -- adding maintenance categories if mw.title.getCurrentTitle().namespace == 0 then if mi.usePropertyCategs then result = result .. wu.getCategories( mi.properties ) .. mu.getCategories( mi.properties ) end if tagContent then result = result .. mi.mfWithShapes end if args.tagName == 'mapframe' and ( tagArgs.width == 'full' or tagArgs.width ~= mi.defaultWidth + mi.borderAdjustment or tagArgs.height ~= mi.defaultHeight ) then result = result .. mi.mfWithSize end end return result end -- for Mapframe template function mf.mapframe( frame ) local args, errors = mu.checkParams( frame:getParent().args, mp.params, 'Mapframe', mi.mfUnknown ) args.tagName = 'mapframe' return _mapframe( args, frame ) .. errors end -- for Maplink template function mf.maplink( frame ) local args, errors = mu.checkParams( frame:getParent().args, mp.params, 'Mapframe', mi.mfUnknown ) local isMapframe = yn( args.frame, false ) -- wp compatibility if isMapframe then args.tagName = 'mapframe' else args.tagName = 'maplink' end return _mapframe( args, frame ) .. errors end return mf