「モジュール:Location map」の版間の差分
ナビゲーションに移動
検索に移動
template>Rxy 細 (「モジュール:Location map」を保護しました: 影響が特に大きいテンプレート: 現時点で 22654 ページからの読み込み ([編集=管理者のみに許可] (無期限) [移動=管理者のみに許可] (無...) |
template>Rxy (モジュール:Location map/sandbox 2016-07-24T15:59:36 UTC (←継承元: en:Module:Location map 2016年7月4日 (月) 16:32(UTC)より) の版より。 WP:AN/PE (差分) に基づく。) |
||
12行目: | 12行目: | ||
function p.getMapParams(map, frame) | function p.getMapParams(map, frame) | ||
if not map then | if not map then | ||
error(' | error('Location mapの名前は必ず指定しなければなりません。', 2) | ||
end | end | ||
local moduletitle = mw.title.new('Module:Location map/data/' .. map) | local moduletitle = mw.title.new('Module:Location map/data/' .. map) | ||
if not moduletitle then | if not moduletitle then | ||
error(' | error(string.format('%qはLocation mapの名前として無効です。', map), 2) | ||
elseif moduletitle.exists then | elseif moduletitle.exists then | ||
local mapData = mw.loadData('Module:Location map/data/' .. map) | local mapData = mw.loadData('Module:Location map/data/' .. map) | ||
33行目: | 33行目: | ||
local cache = {} | local cache = {} | ||
if type(frame) ~= 'table' or type(frame.expandTemplate) ~= 'function' then | if type(frame) ~= 'table' or type(frame.expandTemplate) ~= 'function' then | ||
error(' | error('古いLocation mapを使うときにフレームが存在しなければなりません。') | ||
end | end | ||
return function(name, params) | return function(name, params) | ||
48行目: | 48行目: | ||
end | end | ||
else | else | ||
error(' | error('Location mapのモジュール「"Module:Location map/data/' .. map .. '"」もしくはテンプレート「"Template:Location map ' .. map .. '"」が作成されていません。', 2) | ||
end | end | ||
end | end | ||
76行目: | 76行目: | ||
if decimal then | if decimal then | ||
if degrees then | if degrees then | ||
error(' | error(direction .. 'において10進数形式と度分秒は両方指定できません。', 2) | ||
elseif minutes then | elseif minutes then | ||
error(' | error(direction .. 'において分は度分秒方式でのみ指定してください。', 2) | ||
elseif seconds then | elseif seconds then | ||
error(' | error(direction .. 'において秒は度分秒方式でのみ指定してください。', 2) | ||
elseif hemisphere then | elseif hemisphere then | ||
error(' | error(direction .. 'において半球は度分秒方式でのみ指定してください。', 2) | ||
end | end | ||
local retval = tonumber(decimal) | local retval = tonumber(decimal) | ||
88行目: | 88行目: | ||
return retval | return retval | ||
end | end | ||
error(' | error(direction .. 'の値"' .. decimal .. '"は無効です。', 2) | ||
elseif seconds and not minutes then | elseif seconds and not minutes then | ||
error( | error(direction .. 'の秒を指定する時は分も指定してください。', 2) | ||
elseif not degrees then | elseif not degrees then | ||
if minutes then | if minutes then | ||
error( | error(direction .. 'の分を指定する時は度も指定してください。', 2) | ||
elseif hemisphere then | elseif hemisphere then | ||
error( | error(direction .. 'の半球を指定する時は度も指定してください。', 2) | ||
end | end | ||
return nil | return nil | ||
101行目: | 101行目: | ||
decimal = tonumber(degrees) | decimal = tonumber(degrees) | ||
if not decimal then | if not decimal then | ||
error(' | error(direction .. 'の度の値"' .. degrees .. '"は無効です。', 2) | ||
elseif minutes and not tonumber(minutes) then | elseif minutes and not tonumber(minutes) then | ||
error(' | error(direction .. 'の分の値"' .. minutes .. '"は無効です。', 2) | ||
elseif seconds and not tonumber(seconds) then | elseif seconds and not tonumber(seconds) then | ||
error(' | error(direction .. 'の秒の値"' .. seconds .. '"は無効です。', 2) | ||
end | end | ||
decimal = decimal + (minutes or 0)/60 + (seconds or 0)/3600 | decimal = decimal + (minutes or 0)/60 + (seconds or 0)/3600 | ||
111行目: | 111行目: | ||
local multiplier = hemisphereMultipliers[direction][hemisphere] | local multiplier = hemisphereMultipliers[direction][hemisphere] | ||
if not multiplier then | if not multiplier then | ||
error(' | error(direction .. 'の半球である"' .. hemisphere .. '"は無効です。', 2) | ||
end | end | ||
decimal = decimal * multiplier | decimal = decimal * multiplier | ||
234行目: | 234行目: | ||
retval = retval .. '</div></div>' | retval = retval .. '</div></div>' | ||
if args.caption_undefined then | if args.caption_undefined then | ||
mw.log(' | mw.log('caption_undefinedは廃止されたパラメータです。') | ||
local parent = frame:getParent() | local parent = frame:getParent() | ||
if parent then | if parent then | ||
243行目: | 243行目: | ||
end | end | ||
if map('skew') ~= '' or map('lat_skew') ~= '' or map('crosses180') ~= '' or map('type') ~= '' then | if map('skew') ~= '' or map('lat_skew') ~= '' or map('crosses180') ~= '' or map('type') ~= '' then | ||
mw.log( | mw.log(map() .. 'において廃止されたパラメータです。') | ||
retval = retval .. '' | retval = retval .. '' | ||
end | end | ||
if string.find(map('name'), '|', 1, true) then | if string.find(map('name'), '|', 1, true) then | ||
mw.log( | mw.log(map() .. 'のLocation map名でパイプを使用しています。') | ||
retval = retval .. '' | retval = retval .. '' | ||
end | end | ||
346行目: | 346行目: | ||
end | end | ||
if not longitude then | if not longitude then | ||
error(' | error('緯度の値が指定されていません。') | ||
end | end | ||
if not latitude then | if not latitude then | ||
error(' | error('経度の値が指定されていません。') | ||
end | end | ||
local builder = mw.html.create() | local builder = mw.html.create() | ||
364行目: | 364行目: | ||
end | end | ||
if args.skew or args.lon_shift or args.markhigh then | if args.skew or args.lon_shift or args.markhigh then | ||
mw.log(' | mw.log('呼び出しにおいて廃止されたパラメータです。') | ||
local parent = frame:getParent() | local parent = frame:getParent() | ||
if parent then | if parent then | ||
mw.log( | mw.log(parent:getTitle() .. 'の親です。') | ||
end | end | ||
mw.logObject(args, 'args') | mw.logObject(args, 'args') | ||
383行目: | 383行目: | ||
end | end | ||
if (x < 0 or x > 100 or y < 0 or y > 100) and not args.outside then | if (x < 0 or x > 100 or y < 0 or y > 100) and not args.outside then | ||
mw.log(' | mw.log('外部フラグ設定無しに地図外にマークが置かれました。 x = ' .. x .. ', y = ' .. y) | ||
local parent = frame:getParent() | local parent = frame:getParent() | ||
if parent then | if parent then | ||
mw.log( | mw.log(parent:getTitle() .. 'の親です。') | ||
end | end | ||
mw.logObject(args, 'args') | mw.logObject(args, 'args') | ||
402行目: | 402行目: | ||
end | end | ||
return builder:node(markOuterDiv(x, y, imageDiv, labelDiv)) | return builder:node(markOuterDiv(x, y, imageDiv, labelDiv)) | ||
end | |||
local function switcherSeparate(s) | |||
if s == nil then return {} end | |||
local retval = {} | |||
for i in string.gmatch(s .. '#', '([^#]*)#') do | |||
i = mw.text.trim(i) | |||
retval[#retval + 1] = (i ~= '' and i) | |||
end | |||
return retval | |||
end | end | ||
423行目: | 433行目: | ||
end | end | ||
if type(map) == 'table' then | if type(map) == 'table' then | ||
local altmaps = switcherSeparate(args.AlternativeMap) | |||
if #altmaps > #map then | |||
error(string.format('%dのAlternativeMapsが提供されていますが、%dの地図のみの提供になります。', #altmaps, #map)) | |||
end | |||
local overlays = switcherSeparate(args.overlay_image) | |||
if #overlays > #map then | |||
error(string.format('%dのoverlay_imagesが提供されていますが、%dの地図のみの提供になります。', #overlays, #map)) | |||
end | |||
local outputs = {} | local outputs = {} | ||
args.autoSwitcherLabel = true | args.autoSwitcherLabel = true | ||
for k,v in ipairs(map) do | for k,v in ipairs(map) do | ||
args.AlternativeMap = altmaps[k] | |||
args.overlay_image = overlays[k] | |||
outputs[k] = p.main(frame, args, v) | outputs[k] = p.main(frame, args, v) | ||
end | end |
2016年8月11日 (木) 10:41時点における版
このモジュールについての説明文ページを モジュール:Location map/doc に作成できます
require('Module:No globals')
local p = {}
local getArgs = require('Module:Arguments').getArgs
local function round(n, decimals)
local pow = 10^(decimals or 0)
return math.floor(n * pow + 0.5) / pow
end
function p.getMapParams(map, frame)
if not map then
error('Location mapの名前は必ず指定しなければなりません。', 2)
end
local moduletitle = mw.title.new('Module:Location map/data/' .. map)
if not moduletitle then
error(string.format('%qはLocation mapの名前として無効です。', map), 2)
elseif moduletitle.exists then
local mapData = mw.loadData('Module:Location map/data/' .. map)
return function(name, params)
if name == nil then
return 'Module:Location map/data/' .. map
elseif mapData[name] == nil then
return ''
elseif params then
return mw.message.newRawMessage(tostring(mapData[name]), unpack(params)):plain()
else
return mapData[name]
end
end
elseif mw.title.new('Template:Location map ' .. map).exists then
local cache = {}
if type(frame) ~= 'table' or type(frame.expandTemplate) ~= 'function' then
error('古いLocation mapを使うときにフレームが存在しなければなりません。')
end
return function(name, params)
if params then
return frame:expandTemplate{title = 'Location map ' .. map, args = { name, unpack(params) }}
else
if name == nil then
return 'Template:Location map ' .. map
elseif cache[name] == nil then
cache[name] = frame:expandTemplate{title = 'Location map ' .. map, args = { name }}
end
return cache[name]
end
end
else
error('Location mapのモジュール「"Module:Location map/data/' .. map .. '"」もしくはテンプレート「"Template:Location map ' .. map .. '"」が作成されていません。', 2)
end
end
function p.data(frame, args, map)
if not args then
args = getArgs(frame, {frameOnly = true})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local params = {}
for k,v in ipairs(args) do
if k > 2 then
params[k-2] = v
end
end
return map(args[2], #params ~= 0 and params)
end
local hemisphereMultipliers = {
longitude = { W = -1, w = -1, E = 1, e = 1 },
latitude = { S = -1, s = -1, N = 1, n = 1 }
}
local function decdeg(degrees, minutes, seconds, hemisphere, decimal, direction)
if decimal then
if degrees then
error(direction .. 'において10進数形式と度分秒は両方指定できません。', 2)
elseif minutes then
error(direction .. 'において分は度分秒方式でのみ指定してください。', 2)
elseif seconds then
error(direction .. 'において秒は度分秒方式でのみ指定してください。', 2)
elseif hemisphere then
error(direction .. 'において半球は度分秒方式でのみ指定してください。', 2)
end
local retval = tonumber(decimal)
if retval then
return retval
end
error(direction .. 'の値"' .. decimal .. '"は無効です。', 2)
elseif seconds and not minutes then
error(direction .. 'の秒を指定する時は分も指定してください。', 2)
elseif not degrees then
if minutes then
error(direction .. 'の分を指定する時は度も指定してください。', 2)
elseif hemisphere then
error(direction .. 'の半球を指定する時は度も指定してください。', 2)
end
return nil
end
decimal = tonumber(degrees)
if not decimal then
error(direction .. 'の度の値"' .. degrees .. '"は無効です。', 2)
elseif minutes and not tonumber(minutes) then
error(direction .. 'の分の値"' .. minutes .. '"は無効です。', 2)
elseif seconds and not tonumber(seconds) then
error(direction .. 'の秒の値"' .. seconds .. '"は無効です。', 2)
end
decimal = decimal + (minutes or 0)/60 + (seconds or 0)/3600
if hemisphere then
local multiplier = hemisphereMultipliers[direction][hemisphere]
if not multiplier then
error(direction .. 'の半球である"' .. hemisphere .. '"は無効です。', 2)
end
decimal = decimal * multiplier
end
return decimal
end
-- effectively make removeBlanks false for caption and maplink, and true for everything else
-- if useWikidata is present but blank, convert it to false instead of nil
-- p.top, p.bottom, and their callers need to use this
function p.valueFunc(key, value)
if value then
value = mw.text.trim(value)
end
if value ~= '' or key == 'caption' or key == 'maplink' then
return value
elseif key == 'useWikidata' then
return false
end
end
local function getContainerImage(args, map)
if args.AlternativeMap then
return args.AlternativeMap
elseif args.relief and map('image1') ~= '' then
return map('image1')
else
return map('image')
end
end
function p.top(frame, args, map)
if not args then
args = getArgs(frame, {frameOnly = true, valueFunc = p.valueFunc})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local width
if not args.width then
width = round((args.default_width or 240) * (tonumber(map('defaultscale')) or 1))
elseif mw.ustring.sub(args.width, -2) == 'px' then
width = mw.ustring.sub(args.width, 1, -3)
else
width = args.width
end
local retval = args.float == 'center' and '<div class="center">' or ''
if args.caption and args.caption ~= '' and args.border ~= 'infobox' then
retval = retval .. '<div class="noviewer thumb '
if args.float == '"left"' or args.float == 'left' then
retval = retval .. 'tleft'
elseif args.float == '"center"' or args.float == 'center' or args.float == '"none"' or args.float == 'none' then
retval = retval .. 'tnone'
else
retval = retval .. 'tright'
end
retval = retval .. '"><div class="thumbinner" style="width:' .. (width + 2) .. 'px'
if args.border == 'none' then
retval = retval .. ';border:none'
elseif args.border then
retval = retval .. ';border-color:' .. args.border
end
retval = retval .. '"><div style="position:relative;width:' .. width .. 'px' .. (args.border ~= 'none' and ';border:1px solid lightgray">' or '">')
else
retval = retval .. '<div style="width:' .. width .. 'px;'
if args.float == '"left"' or args.float == 'left' then
retval = retval .. 'float:left;clear:left'
elseif args.float == '"center"' or args.float == 'center' then
retval = retval .. 'float:none;clear:both;margin-left:auto;margin-right:auto'
elseif args.float == '"none"' or args.float == 'none' then
retval = retval .. 'float:none;clear:none'
else
retval = retval .. 'float:right;clear:right'
end
retval = retval .. '"><div style="width:' .. width .. 'px;padding:0"><div style="position:relative;width:' .. width .. 'px">'
end
local image = getContainerImage(args, map)
retval = string.format(
'%s[[File:%s|%spx|%s%s]]',
retval,
image,
width,
args.alt or ((args.label or mw.title.getCurrentTitle().text) .. 'の位置(' .. map('name')) .. '内)',
args.maplink and ('|link=' .. args.maplink) or ''
)
if args.overlay_image then
return retval .. '<div style="position:absolute;top:0;left:0">[[File:' .. args.overlay_image .. '|' .. width .. 'px]]</div>'
else
return retval
end
end
function p.bottom(frame, args, map)
if not args then
args = getArgs(frame, {frameOnly = true, valueFunc = p.valueFunc})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local retval = '</div>'
if not args.caption or args.border == 'infobox' then
if args.border then
retval = retval .. '<div>'
else
retval = retval .. '<div style="font-size:90%;padding-top:3px">'
end
retval = retval
.. (args.caption or (args.label or mw.title.getCurrentTitle().text) .. ' (' .. map('name') .. ')')
.. '</div>'
elseif args.caption ~= '' then
-- This is not the pipe trick. We're creating a link with no text on purpose, so that CSS can give us a nice image
retval = retval .. '<div class="thumbcaption"><div class="magnify">[[:File:' .. getContainerImage(args, map) .. '| ]]</div>' .. args.caption .. '</div>'
end
if args.switcherLabel then
retval = retval .. '<span class="switcher-label" style="display:none">' .. args.switcherLabel .. '</span>'
elseif args.autoSwitcherLabel then
retval = retval .. '<span class="switcher-label" style="display:none">' .. map('name') .. 'の地図を表示</span>'
end
retval = retval .. '</div></div>'
if args.caption_undefined then
mw.log('caption_undefinedは廃止されたパラメータです。')
local parent = frame:getParent()
if parent then
mw.log('Parent is ' .. parent:getTitle())
end
mw.logObject(args, 'args')
retval = retval .. ''
end
if map('skew') ~= '' or map('lat_skew') ~= '' or map('crosses180') ~= '' or map('type') ~= '' then
mw.log(map() .. 'において廃止されたパラメータです。')
retval = retval .. ''
end
if string.find(map('name'), '|', 1, true) then
mw.log(map() .. 'のLocation map名でパイプを使用しています。')
retval = retval .. ''
end
if args.float == 'center' then
retval = retval .. '</div>'
end
return retval
end
local function markOuterDiv(x, y, imageDiv, labelDiv)
return mw.html.create('div')
:cssText('position:absolute;z-index:1;top:' .. round(y, 3) .. '%;left:' .. round(x, 3) .. '%')
:node(imageDiv)
:node(labelDiv)
end
local function markImageDiv(mark, marksize, label, link, alt, title)
local builder = mw.html.create('div')
:cssText('position:absolute;left:-' .. round(marksize / 2) .. 'px;top:-' .. round(marksize / 2) .. 'px;line-height:0')
:attr('title', title)
if marksize ~= 0 then
builder:wikitext(string.format(
'[[File:%s|%dx%dpx|%s|link=%s%s]]',
mark,
marksize,
marksize,
label,
link,
alt and ('|alt=' .. alt) or ''
))
end
return builder
end
local function markLabelDiv(label, label_size, label_width, position, background, x, marksize)
if tonumber(label_size) == 0 then
return mw.html.create('div'):cssText('font-size:0%;position:absolute'):wikitext(label)
end
local builder = mw.html.create('div')
:cssText('font-size:' .. label_size .. '%;line-height:110%;position:absolute;width:' .. label_width .. 'em')
local distance = round(marksize / 2 + 1)
local spanCss
if position == 'top' then -- specified top
builder:cssText('bottom:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em;text-align:center')
elseif position == 'bottom' then -- specified bottom
builder:cssText('top:' .. distance .. 'px;left:' .. (-label_width / 2) .. 'em;text-align:center')
elseif position == 'left' or (tonumber(x) > 70 and position ~= 'right') then -- specified left or autodetected to left
builder:cssText('top:-0.75em;right:' .. distance .. 'px;text-align:right')
spanCss = 'float:right'
else -- specified right or autodetected to right
builder:cssText('top:-0.75em;left:' .. distance .. 'px;text-align:left')
spanCss = 'float:left'
end
builder = builder:tag('span')
:cssText('padding:1px')
:cssText(spanCss)
:wikitext(label)
if background then
builder:cssText('background-color:' .. background)
end
return builder:done()
end
local function getX(longitude, left, right)
local width = (right - left) % 360
if width == 0 then
width = 360
end
local distanceFromLeft = (longitude - left) % 360
-- the distance needed past the map to the right equals distanceFromLeft - width. the distance needed past the map to the left equals 360 - distanceFromLeft. to minimize page stretching, go whichever way is shorter
if distanceFromLeft - width / 2 >= 180 then
distanceFromLeft = distanceFromLeft - 360
end
return 100 * distanceFromLeft / width
end
local function getY(latitude, top, bottom)
return 100 * (top - latitude) / (top - bottom)
end
function p.mark(frame, args, map)
if not args then
args = getArgs(frame, {wrappers = 'Template:Location map~'})
end
if not map then
map = p.getMapParams(args[1], frame)
end
local x, y, longitude, latitude
longitude = decdeg(args.lon_deg, args.lon_min, args.lon_sec, args.lon_dir, args.long, 'longitude')
latitude = decdeg(args.lat_deg, args.lat_min, args.lat_sec, args.lat_dir, args.lat, 'latitude')
if not longitude and not latitude and args.useWikidata then
-- If they didn't provide either coordinate, try Wikidata. If they provided one but not the other, don't.
local entity = mw.wikibase.getEntity()
if entity and entity.claims and entity.claims.P625 and entity.claims.P625[1].mainsnak.snaktype == 'value' then
local value = entity.claims.P625[1].mainsnak.datavalue.value
longitude, latitude = value.longitude, value.latitude
end
end
if not longitude then
error('緯度の値が指定されていません。')
end
if not latitude then
error('経度の値が指定されていません。')
end
local builder = mw.html.create()
if (not args.lon_deg) ~= (not args.lat_deg) then
builder:wikitext('')
elseif (not args.lon_min) ~= (not args.lat_min) then
builder:wikitext('')
elseif (not args.lon_sec) ~= (not args.lat_sec) then
builder:wikitext('')
elseif (not args.lon_dir) ~= (not args.lat_dir) then
builder:wikitext('')
elseif (not args.long) ~= (not args.lat) then
builder:wikitext('')
end
if args.skew or args.lon_shift or args.markhigh then
mw.log('呼び出しにおいて廃止されたパラメータです。')
local parent = frame:getParent()
if parent then
mw.log(parent:getTitle() .. 'の親です。')
end
mw.logObject(args, 'args')
builder:wikitext('')
end
if map('x') ~= '' then
x = tonumber(mw.ext.ParserFunctions.expr(map('x', { latitude, longitude })))
else
x = tonumber(getX(longitude, map('left'), map('right')))
end
if map('y') ~= '' then
y = tonumber(mw.ext.ParserFunctions.expr(map('y', { latitude, longitude })))
else
y = tonumber(getY(latitude, map('top'), map('bottom')))
end
if (x < 0 or x > 100 or y < 0 or y > 100) and not args.outside then
mw.log('外部フラグ設定無しに地図外にマークが置かれました。 x = ' .. x .. ', y = ' .. y)
local parent = frame:getParent()
if parent then
mw.log(parent:getTitle() .. 'の親です。')
end
mw.logObject(args, 'args')
builder:wikitext('')
end
local mark = args.mark or map('mark')
if mark == '' then
mark = 'Red pog.svg'
end
local marksize = tonumber(args.marksize) or tonumber(map('marksize')) or 8
local imageDiv = markImageDiv(mark, marksize, args.label or mw.title.getCurrentTitle().text, args.link or '', args.alt, args[2])
local labelDiv
if args.label and args.position ~= 'none' then
labelDiv = markLabelDiv(args.label, args.label_size or 90, args.label_width or 6, args.position, args.background, x, marksize)
end
return builder:node(markOuterDiv(x, y, imageDiv, labelDiv))
end
local function switcherSeparate(s)
if s == nil then return {} end
local retval = {}
for i in string.gmatch(s .. '#', '([^#]*)#') do
i = mw.text.trim(i)
retval[#retval + 1] = (i ~= '' and i)
end
return retval
end
function p.main(frame, args, map)
if not args then
args = getArgs(frame, {wrappers = 'Template:Location map', valueFunc = p.valueFunc})
end
if args.useWikidata == nil then
args.useWikidata = true
end
if not map then
if args[1] then
map = {}
for mapname in string.gmatch(args[1], '[^#]+') do
map[#map + 1] = p.getMapParams(mapname, frame)
end
if #map == 1 then map = map[1] end
else
map = p.getMapParams('World', frame)
end
end
if type(map) == 'table' then
local altmaps = switcherSeparate(args.AlternativeMap)
if #altmaps > #map then
error(string.format('%dのAlternativeMapsが提供されていますが、%dの地図のみの提供になります。', #altmaps, #map))
end
local overlays = switcherSeparate(args.overlay_image)
if #overlays > #map then
error(string.format('%dのoverlay_imagesが提供されていますが、%dの地図のみの提供になります。', #overlays, #map))
end
local outputs = {}
args.autoSwitcherLabel = true
for k,v in ipairs(map) do
args.AlternativeMap = altmaps[k]
args.overlay_image = overlays[k]
outputs[k] = p.main(frame, args, v)
end
return '<div class="switcher-container">' .. table.concat(outputs) .. '</div>'
else
return p.top(frame, args, map) .. tostring( p.mark(frame, args, map) ) .. p.bottom(frame, args, map)
end
end
return p