Modul:GraphHelfer
aus Wikipedia, der freien Enzyklopädie
Die Dokumentation für dieses Modul kann unter Modul:GraphHelfer/Doku erstellt werden
local p = {} --p stands for package
--[======[
Für Graph-Templates, welche MapData Eingabedaten skalieren
wollen, bevor sie von der graph-Erweiterung weiterverarbeitet
werden. Nützlich um z.B. eine equirektanguläre Projektion um
einen Faktor, hier latScale bzw. lonScale, zu strecken oder zu stauchen.
Beispiel-Aufrufe:
{{#invoke:GraphHelfer|scaleMapData|map=Germany.map|latScale=1.5}} oder
{{#invoke:GraphHelfer|scaleMapData|map=Germany.map|latScale=1.5|latCen=51.179342979289}}
(ist latCen bzw. lonCen nicht angegeben, wird das Feld latitude bzw. longitude
der Eingabedaten verwendet, in der Annahme, das diese den Mittelpunkt angeben)
named args: (one of map or maps must be passed, but not both)
--------------------------------------------------------------
map - name of a map data page on commons, the source data
maps - to supply more than one map data page, separated by colon ','
latScale - float scale factor for latitude values
latCen - the latitude center around which lat scaling is performed
latCen is mandatory, if maps and latScale are supplied
latCen is optional, if map and latScale is supplied
lonScale - float scale factor for longitude values
lonCen - the longitude center around which lon scaling is performed
lonCen is mandatory, if maps and lonScale are supplied
lonCen is optional, if map and lonScale is supplied
--]======]
function p.scaleMapData( f )
local ret = {}
local maps = f.maps or mw.text.split(f.args.maps or f.args.map, ", *")
for _, map in pairs(maps) do
--local t = mw.title.new( map, "Data" )
--local json = mw.text.jsonDecode(t:getContent())
local json = mw.ext.data.get(map)
local feats = json.data.features
local scaleLL = function (ll)
local lC
if f.args.latScale then
lC = f.args.latCen or json.latitude
-- 1-based using jsonDecode, 0-based using mw.ext.data.get
--v2[2] = f.args.latScale*(v2[2]-lC) + lC
ll[1] = f.args.latScale*(ll[1]-lC) + lC
end
if f.args.lonScale then
lC = f.args.lonCen or json.longitude
-- 1-based using jsonDecode, 0-based using mw.ext.data.get
--v2[1] = f.args.lonScale*(v2[1]-lC) + lC
ll[0] = f.args.lonScale*(ll[0]-lC) + lC
end
end
for _, feat in pairs(feats) do
if feat and feat.geometry then
for _, v in pairs(feat.geometry.coordinates) do
for _, v1 in pairs(v) do
if feat.geometry.type == "MultiPolygon" then
for _, v2 in pairs(v1) do
scaleLL(v2)
end
end
if feat.geometry.type == "Polygon" then
scaleLL(v1)
end
end
end
ret[#ret+1] = feat
end
end
end
return mw.text.jsonEncode(ret)
end
--[======[
This function sets orientation values of place labels roughly
as documented in Vorlage:Positionskarte+/Doku#Position
--]======]
function PositionskartePlaces( p, bm )
local places = (p:gsub("{{","(("):gsub("}}","))")
:gsub("(%[%[[^]]+)|","%1[["):gsub("|",","):gsub("(%[%[[^[]+)%[%[","%1|")
)
local convs = {
label = function ( v ) return (
v:gsub("^%[%[?", ""):gsub("%]?%]$", ""):gsub("^.*|", "")
) end,
position = function ( v ) return tonumber(
({ right= 3, bottom= 6, left= 9, top= 12, center= -1 })[v] or v
) end,
lat = function ( v ) return
require("Modul:Coordinate").CoordinateLAT( v )
end,
long = function ( v ) return
require("Modul:Coordinate").CoordinateLONG( v )
end
}
local setdef = function ( t )
local _t = {
[ 1] = { align= "center", baseline= "bottom", dy= -4, dx= 12 },
[ 2] = { align= "left", baseline= "bottom", dy= -3, dx= -12 },
[ 3] = { align= "left", baseline= "middle", dy= 0, dx= 6 },
[ 4] = { align= "left", baseline= "top", dy= 3, dx= -12 },
[ 5] = { align= "center", baseline= "top", dy= 4, dx= 12 },
[ 6] = { align= "center", baseline= "top", dy= 4, dx= 0 },
[ 7] = { align= "center", baseline= "top", dy= 4, dx= -12 },
[ 8] = { align= "right", baseline= "top", dy= 3, dx= 12 },
[ 9] = { align= "right", baseline= "middle", dy= 0, dx= -6 },
[10] = { align= "right", baseline= "bottom", dy= -3, dx= 12 },
[11] = { align= "center", baseline= "bottom", dy= -4, dx= -12},
[12] = { align= "center", baseline= "bottom", dy= -6, dx= 0 },
[ 0] = { align= "center", baseline= "bottom", dy= -6, dx= 0 },
[-1] = { align= "center", baseline= "middle", dy= 0, dx= 0 }
}
local inUpprHalf = (bm.y > t.lat and t.lat > (bm.y + (bm.y2-bm.y)/2))
local inLeftHalf = (bm.x < t.long and t.long < (bm.x + (bm.x2-bm.x)/2))
local _pn = inUpprHalf and (inLeftHalf and 4 or 8)
or (inLeftHalf and 2 or 10)
for k, v in pairs(_t[t.position or _pn]) do
t[k] = type(v) == "number" and v + (tonumber(t[k]) or 0) or v
end
t.label_color = t.label_color or "blue"
t.label_size = math.floor(11 * (tonumber(t.label_size) or 100)/100)
t.angle = t.angle or 0
return t
end
local ovl = {}
for place in mw.text.gsplit(places, "((", true) do
local p, n = place:gsub("\n", ""):gsub("%)%),? *$", "")
if n > 0 then
local t = {}
for arg in mw.text.gsplit(p, ",", true) do
local k, v = unpack(mw.text.split(arg, "=", true))
if v then
k = mw.text.trim(k)
v = mw.text.trim(v)
t[k] = convs[k] and convs[k](v) or v
end
end
ovl[#ovl+1] = setdef(t)
end
end
local ovlbr = {}
for k, place in pairs(ovl) do
if place.label:match("<br[^>]*>") then
local fontsz = place.label_size
local t = {}
-- assume place.baseline == "top"
for l in mw.text.gsplit(place.label, "<br[^>]*>") do
t[#t+1] = {}
for k, v in pairs(place) do
t[#t][k] = v
end
t[#t].label = l
t[#t].dy = t[#t].dy + fontsz * (#t-1)
end
-- offset if assumption was wrong
local off = place.baseline == "bottom" and fontsz * (#t-1) or
(place.baseline == "middle" and (fontsz * (#t-1)) / 2 or 0)
for _, v in pairs(t) do
v.dy = v.dy - off
ovlbr[#ovlbr+1] = v -- record wrapped label fragment
end
ovl[k] = nil -- delete unwrapped label
end
end
if #ovlbr > 0 then
local t = {}
for _, v in pairs(ovl) do t[#t+1] = v end
for _, v in ipairs(ovlbr) do t[#t+1] = v end
ovl = t
end
return ovl
end
--[======[
Work-in-Progress, do NOT use productively yet
Example call(s) to place on a wiki page for testing:
{{#tag:graph|{{#invoke:GraphHelfer|Positionskarte|Deutschland|width=400|places=
((Deutschland,label=[[TSV Bad Königshofen|Bad Königshofen]],position=6,long=10/25//E,lat=50/18//N,region=DE-BY,marktarget=TSV Bad Königshofen))
((Deutschland,label=[[Werder Bremen|Bremen]],position=right,long=8/50/34.3/E,lat=53/3/49/N,region=DE-HB,marktarget=Werder Bremen))
}}}}
{{#tag:graph|{{#invoke:GraphHelfer|Positionskarte|Frankreich|width=600|maptype=relief|places=
((label=Paris,position=bottom,long=2/21/6/E,lat=48/51/24/N))
((label=Metz,position=top,long=06/10/37/O,lat=49/07/11/N))
((label=Toulouse,position=left,long=01/26/31/O,lat=43/36/16/N))
((label=Brando,position=left,long=9/28/34/O,lat=42/46/34/N))
}}}}
--]======]
function p.Positionskarte( f )
local d = mw.loadData( "Modul:Positionskarte/Data" )
local k, ktyp = f.args.karte or f.args[1], f.args.maptype or "default"
local bm
if type(d) == "table" and d[k] then
d = d[k]
bm = { x= d.left, y= d.top, x2= d.right, y2= d.bottom,
img = d.image and d.image[ktyp] }
else
d = "Vorlage:Positionskarte " .. k
bm = {
x= tonumber(f:expandTemplate{ title = d, args = { "left" } }),
y= tonumber(f:expandTemplate{ title = d, args = { "top" } }),
x2= tonumber(f:expandTemplate{ title = d, args = { "right" } }),
y2= tonumber(f:expandTemplate{ title = d, args = { "bottom" } }),
img= f:expandTemplate{ title = d, args = { "image", ktyp } }
}
end
bm.xdiff = math.abs(bm.x2-bm.x)
bm.ydiff = math.abs(bm.y2-bm.y)
local imgt = mw.title.new(bm.img, "File")
local imgw = imgt.file and imgt.file.width
local imgh = imgt.file and imgt.file.height
local imgaspect = (imgw and imgh and imgw/imgh) or 1.0
local gw = tonumber( f.args.width or 250 )
local gh = math.floor( (gw / imgaspect) + 0.5 )
bm.img = "wikifile:///File:" .. bm.img .. "?width=" .. gw .. "&height=" .. gh
local graph =
{
width= gw,
height= gh,
padding= { left= 0, top= 0, bottom= 0, right= 0 },
data= {
{
name= "basemap",
values= { bm }
},
{
name= "overlay",
values= PositionskartePlaces( f.args.places or "", bm )
}
},
scales= {
{
name= "x",
type= "linear",
domain= {bm.x, bm.x2},
range= "width",
zero= false
},
{
name= "y",
type= "linear",
domain= {bm.y2, bm.y},
range= "height",
zero= false
}
},
marks= {
{
type= "image",
from= {data= "basemap"},
properties= {
enter= {
url= {field= "img"},
aspect= false, --always extend to group size
x= {scale= "x", field= "x"},
y= {scale= "y", field= "y"},
x2= {scale= "x", field= "x2"},
y2= {scale= "y", field= "y2"},
align= {value= "left"},
baseline= {value= "top"}
}
}
},
{
type= "symbol",
from= {data= "overlay"},
properties= {
enter= {
x= {scale= "x",field= "long"},
y= {scale= "y",field= "lat"},
stroke= {value= "red"},
fill= {value= "red"},
size= {value= 30}
}
}
},
{
type= "text",
from= {data= "overlay"},
properties= {
enter= {
x= {scale= "x",field= "long"},
y= {scale= "y",field= "lat"},
text= {field= "label"},
fill= {field= "label_color"},
fontSize= {field = "label_size"},
fontWeight= {value= "normal"},
align= {field= "align"},
baseline= {field= "baseline"},
dx= {field= "dx"},
dy= {field= "dy"},
angle= {field= "angle"}
}
}
}
}
}
if f.args.axes then
graph.axes = {
{type= "x", scale= "x"},
{type= "y", scale= "y"}
}
graph.padding.left = 25
graph.padding.bottom = 25
if f.args.axes == "inset" then
graph.width = graph.width - 25
graph.height = graph.height - 25
end
end
if f.args.mark then
local marksz = tonumber(f.args.marksize) or 8
local mark = #f.args.mark < 4 and "Red_pog.svg" or
(f.args.mark:gsub("^File:",""):gsub("^Datei:",""):gsub(" ", "_"))
mark = "wikifile:///File:"..mark.."?width="..marksz.."&height="..marksz
local sym
for _, v in ipairs(graph.marks) do
if v.type == "symbol" then sym = v end
end
sym.type = "image"
sym.properties.enter = {
url= {value= mark}, --url= {field= "mark"},
x= {scale= "x",field= "long"},
y= {scale= "y",field= "lat"},
width= {value= marksz}, --width= {field= "marksize"},
height= {value= marksz}, --height= {field= "marksize"},
align= {value= "center"},
baseline= {value= "middle"}
}
end
if f.args.mapdata and type(d) == "table" then
local data = graph.data
local marks = {}
local _sty = {
seas = { fill= "#C7EDFF" },
states = { strokeWidth= "0.6" },
neighs = { strokeWidth= "1.5", fill= "#ddd" }
}
setmetatable(_sty, { __index = function () return {} end })
for md in mw.text.gsplit(f.args.mapdata, ',') do
if d[md] then
data[#data + 1] = {
name = md,
values = p.scaleMapData{ maps = d[md], args = {
latScale = gh/gw * bm.xdiff/bm.ydiff,
latCen = bm.y + (bm.y2-bm.y)/2,
}},
transform= {
{
type= "geopath",
projection= "equirectangular",
translate= { gw/2, gh/2 },
center= {
bm.x + (bm.x2-bm.x)/2,
bm.y + (bm.y2-bm.y)/2
},
scale= 56.789 * math.min(gw/bm.xdiff, gh/bm.ydiff)
}
}
}
marks[#marks + 1] = {
type= "path",
from= {data= md},
properties= {
enter= {
stroke= {value=
d[md].stroke or _sty[md].stroke or "#aaa"},
strokeWidth= {value=
d[md].strokeWidth or _sty[md].strokeWidth or 1.0},
fill= {value=
d[md].fill or _sty[md].fill or "#FFFFEA"},
path= {field= "layout_path"}
}
}
}
else
if md == "seas" then
marks[#marks + 1] = {
type= "rect",
properties= {
enter= {
x = {value= 0},
y = {value= 0},
x2 = {field= {group= "width"}},
y2 = {field= {group= "height"}},
strokeWidth= {value= 0},
fill= {value= _sty[md].fill}
}
}
}
end
end
end
for i = 2, #graph.marks do
marks[#marks+1] = graph.marks[i]
end
graph.marks = {
{
type= "group",
properties= {
enter= {
x= {value= 0},
y= {value= 0},
x2= {value= gw},
y2= {value= gh},
clip= {value= true}
}
},
scales = graph.scales,
axes = graph.axes,
marks = marks
}
}
graph.scales = nil
graph.axes = nil
end
return mw.text.jsonEncode(graph)
end
return p