Módulo:Mapframe
Version check
Designación de la versión en Wikidata: 2024-01-26
Uso
{{Mapframe}}
.
Esta documentación está transcluida desde Módulo:Mapframe/doc.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.
Los editores pueden experimentar en la zona de pruebas (crear) y en los casos de prueba (crear) del módulo.
Por favor, añade las categorías en la subpágina de documentación. Subpáginas de este módulo.
-- 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 = '2024-01-26',
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
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