Mòdul:Map/utilities
Aparença
Podeu crear la pàgina d'ús per documentar aquest mòdul Lua. |
-- Versió incial en proves
local p = {}
-- Convert coordinates input format to geojson table
local function parseGeoSequence(data, geotype)
local coordsGeo = {}
for line_coord in mw.text.gsplit(data, ':', true) do -- Polygon - linearRing:linearRing...
local coordsLine = {}
for point_coord in mw.text.gsplit(line_coord, ';', true) do -- LineString or MultiPoint - point;point...
local valid = false
local val = mw.text.split(point_coord, ',', true) -- Point - lat,lon
-- allow for elevation
if #val >= 2 and #val <= 3 then
local lat = tonumber(val[1])
local lon = tonumber(val[2])
if lat ~= nil and lon ~= nil then
table.insert(coordsLine, {lon, lat})
valid = true
end
end
end
table.insert(coordsGeo, coordsLine)
end
if geotype == 'Point' then
coordsGeo = coordsGeo[1][1]
elseif geotype == "LineString" or geotype == "MultiPoint" then
coordsGeo = coordsGeo[1]
end
return coordsGeo
end
-- data Point - {lon,lat}
-- data LineString - { {lon,lat}, {lon,lat}, ... }
-- data Polygon - { { {lon,lat}, {lon,lat} }, { {lon,lat}, {lon,lat} }, ... }
-- output as LineString format
local function mergePoints(stack, merger)
if merger == nil then return stack end
for _, val in ipairs(merger) do
if type(val) == "number" then -- Point format
stack[#stack + 1] = merger
break
elseif type(val[1]) == "table" then -- Polygon format
for _, val2 in ipairs(val) do
stack[#stack + 1] = val2
end
else -- LineString format
stack[#stack + 1] = val
end
end
return stack
end
local function getCoordBounds(data)
local latN, latS = -90, 90
local lonE, lonW = -180, 180
for i, val in ipairs(data) do
latN = math.max(val[2], latN)
latS = math.min(val[2], latS)
lonE = math.max(val[1], lonE)
lonW = math.min(val[1], lonW)
end
return latN, latS, lonE, lonW
end
local function getCoordCenter(data)
local latN, latS, lonE, lonW = getCoordBounds(data)
local latCenter = latS + (latN - latS) / 2
local lonCenter = lonW + (lonE - lonW) / 2
return lonCenter, latCenter
end
-- meters per degree by latitude
local function mxdByLat(lat)
local latRad = math.rad(lat)
-- see [[Geographic coordinate system#Expressing latitude and longitude as linear units]], by CSGNetwork
local mxdLat = 111132.92 - 559.82 * math.cos(2 * latRad) + 1.175 * math.cos(4 * latRad) - 0.023 * math.cos(6 * latRad)
local mxdLon = 111412.84 * math.cos(latRad) - 93.5 * math.cos(3 * latRad) + 0.118 * math.cos(5 * latRad)
return mxdLat, mxdLon
end
-- Calculate zoom to fit coordinate bounds into height and width of frame
local function getZoom(data, height, width)
local lat1, lat2, lon1, lon2 = getCoordBounds(data)
local latMid = (lat1 + lat2) / 2 -- mid latitude
local mxdLat, mxdLon = mxdByLat(latMid)
-- distances in meters
local distLat = math.abs((lat1 - lat2) * mxdLat)
local distLon = math.abs((lon1 - lon2) * mxdLon)
-- margin 100px in height and width, right upper icon is about 50x50px
local validHeight = math.max(height - 100, 100)
local validWidth = math.max(width - 100, 100)
-- maximum zoom fitting all points
local latRad = math.rad(latMid)
for zoom = 19, 0, -1 do
-- see https://wiki.openstreetmap.org/wiki/Zoom_levels#Metres_per_pixel_math
-- equatorial circumference 40 075 036 m: [[Equator#Exact length]]
local distLatFrame = 40075036 * validHeight * math.cos(latRad) / (2 ^ (zoom + 8))
local distLonFrame = 40075036 * validWidth * math.cos(latRad) / (2 ^ (zoom + 8))
if distLatFrame > distLat and distLonFrame > distLon then
return zoom
end
end
return 0
end
local function fetchWikidata(id, snak)
-- snak is a table like {'claims', 'P625', 1, 'mainsnak', 'datavalue', 'value'}
-- see function ViewSomething on Module:Wikidata
local value
id = mw.text.trim(id)
value = mw.wikibase.getEntityObject(id)
for i in ipairs(snak) do
if value == nil then break end
value = value[snak[i]]
end
return value
end
-- Fetch coordinates from Wikidata for a list of comma separated ids
local function getCoordinatesById(ids)
if ids == nil then return end
local coord = {}
local snak = {'claims', 'P625', 1, 'mainsnak', 'datavalue', 'value'}
for idx in mw.text.gsplit(ids, '%s*,%s*') do
local value = fetchWikidata(idx, snak)
if value then
coord[#coord+1] = value.latitude .. ',' .. value.longitude
end
end
return #coord > 0 and table.concat(coord, ';') or nil
end
function p.item2geojson(frame)
local getArgs = require('Module:Arguments').getArgs
local args = getArgs(frame)
local tagname = args.type or 'mapframe'
local geojson
local tagArgs = {
text = args.text,
zoom = tonumber(args.zoom),
latitude = tonumber(args.latitude),
longitude = tonumber(args.longitude)
}
local defaultzoom = tonumber(args.default_zoom)
if tagname == 'mapframe' then
tagArgs.width = args.width or 300
tagArgs.height = args.height or 300
tagArgs.align = args.align or 'right'
if args.frameless ~= nil and tagArgs.text == nil then tagArgs.frameless = true end
else
tagArgs.class = args.class
end
local myfeatures, allpoints = {}, {}
local i, j = 1, 1
while args[i] do
j = #myfeatures + 1
myfeatures[j] = {}
myfeatures[j]['type'] = "Feature"
myfeatures[j]['geometry'] = {}
myfeatures[j]['geometry']['type'] = 'Point'
myfeatures[j]['geometry']['coordinates'] = parseGeoSequence(getCoordinatesById(args[i]), 'Point')
allpoints = mergePoints(allpoints, myfeatures[j]['geometry']['coordinates'])
myfeatures[j]['properties'] = {}
myfeatures[j]['properties']['title'] = mw.wikibase.getLabel(args[i])
myfeatures[j]['properties']['marker-size'] = args['marker-size'..i] or args['marker-size']
myfeatures[j]['properties']['marker-symbol'] = args['marker-symbol'..i] or args['marker-symbol']
myfeatures[j]['properties']['marker-color'] = args['marker-color'..i] or args['marker-color']
i = i + 1
end
-- calculate defaults for static mapframe; maplink is dynamic
if (tagArgs.latitude == nil or tagArgs.longitude == nil) and #allpoints > 0 then
if tagname == "mapframe" or tagArgs.text == nil then -- coordinates needed for text in maplink
tagArgs.longitude, tagArgs.latitude = getCoordCenter(allpoints)
end
end
if tagArgs.zoom == nil then
if tagname == "mapframe" then
if #allpoints <= 1 then
tagArgs.zoom = defaultzoom or 9
else
tagArgs.zoom = getZoom(allpoints, tagArgs.height, tagArgs.width)
end
else
tagArgs.zoom = defaultzoom
end
end
local geojson = {}
if #myfeatures > 0 then
geojson[#geojson + 1] = {type = "FeatureCollection", features = myfeatures}
end
if args.debug ~= nil then
local html = mw.text.tag{name = tagname, attrs = tagArgs, content = mw.text.jsonEncode(geojson, mw.text.JSON_PRETTY)}
return frame:extensionTag('syntaxhighlight', tostring(html), {lang = 'json'})
end
return frame:extensionTag(tagname, mw.text.jsonEncode(geojson), tagArgs)
end
return p