Modul:Mapframe
Dokumentaci tohoto modulu lze vytvořit na stránce Modul:Mapframe/Dokumentace
-- 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
-- kopie z Německých wikicest: https://de.wikivoyage.org/w/index.php?title=Modul:Mapframe&oldid=1594994
-- copy from dewikivoyage: https://de.wikivoyage.org/w/index.php?title=Modul:Mapframe&oldid=1594994
-- documentation
local Mapframe = {
suite = 'Mapframe',
serial = '2024-01-26',
item = 52554979
}
-- module import
-- require( 'strict' )
local cd = require( 'Module:Coordinates2' )
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
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