Documentation for this module may be created at Module:RouteSection/doc
local p = {}
-- ─────────────────────────────────────────────────────────────────────────────
-- Helpers
-- ─────────────────────────────────────────────────────────────────────────────
local unitMap = {
["Q828224"] = { symbol = "km", type = "length" }, -- kilometre
["Q11573"] = { symbol = "m", type = "length" }, -- metre
["Q253276"] = { symbol = "mi", type = "length" }, -- mile
["Q3710"] = { symbol = "ft", type = "length" }, -- foot
["Q25235"] = { symbol = "hr", type = "duration" }, -- hour
["Q7727"] = { symbol = "min", type = "duration" }, -- minute
}
local function trim(s) return mw.text.trim(s or "") end
-- Prefer a "preferred" statement if present; otherwise first non-deprecated.
local function pickStmt(statements)
if not statements then return nil end
for _, s in ipairs(statements) do
if s.rank == "preferred" then return s end
end
for _, s in ipairs(statements) do
if s.rank ~= "deprecated" then return s end
end
end
local function getSnakValue(statement)
local snak = statement and statement.mainsnak
if not snak or snak.snaktype ~= "value" then return nil end
return snak.datavalue and snak.datavalue.value or nil
end
local function getLabel(id, lang)
if not id or id == "" then return "" end
return mw.wikibase.getLabelByLang(id, lang:getCode())
or mw.wikibase.getLabel(id) or ""
end
local function itemId(entity, pid)
if not entity or not entity.claims then return nil end
local v = getSnakValue(pickStmt(entity.claims[pid]))
return v and v.id or nil
end
local function quantity(entity, pid)
if not entity or not entity.claims then return nil end
local v = getSnakValue(pickStmt(entity.claims[pid]))
if not v or not v.amount then return nil end
local n = tonumber(v.amount); if not n then return nil end
local unitQID = v.unit and v.unit:match("Q%d+") or ""
local info = unitMap[unitQID]
return {
amount = n,
symbol = info and info.symbol or "",
type = info and info.type or "unknown",
unitQID = unitQID,
}
end
local function convertAmount(amount, fromSymbol, toSymbol)
if not amount or not fromSymbol or not toSymbol or fromSymbol == toSymbol then
return amount
end
if fromSymbol == "m" and toSymbol == "ft" then return amount * 3.280839895 end
if fromSymbol == "ft" and toSymbol == "m" then return amount / 3.280839895 end
return nil -- only convert m↔ft
end
local function fmtQuantity(frame, q, doConvert)
if not q then return nil end
if doConvert and q.type == "length" then
return frame:preprocess(string.format("{{convert|%s|%s|abbr=on}}", q.amount, q.symbol))
end
local unit = q.symbol ~= "" and (" " .. q.symbol) or ""
return mw.language.getContentLanguage():formatNum(q.amount) .. unit
end
local function firstNonEmpty(args, keys)
for _, k in ipairs(keys) do
local v = trim(args[k])
if v ~= "" then return v end
end
return ""
end
local function truthy(s)
s = trim(s):lower()
return s == "yes" or s == "true" or s == "on" or s == "1"
end
local function compute(frame)
local args = frame.args
local lang = mw.language.getContentLanguage()
local convertFlag = truthy(args.convert)
-- Only use Wikidata if an explicit, non-empty ID is provided
local wikidataId = trim(args.wikidata)
local entity = (wikidataId ~= "" and mw.wikibase.getEntityObject(wikidataId)) or nil
-- Names ----------------------------------------------------------
local name = firstNonEmpty(args, {"name"})
if name == "" and entity then
name = getLabel(entity.id, lang) or ""
end
-- Endpoints ------------------------------------------------------
local start_id = entity and itemId(entity, "P1427") or nil -- start point
local dest_id = entity and itemId(entity, "P1444") or nil -- end point
local from_str = firstNonEmpty(args, {"from"})
if from_str == "" and start_id then from_str = getLabel(start_id, lang) end
local to_str = firstNonEmpty(args, {"to"})
if to_str == "" and dest_id then to_str = getLabel(dest_id, lang) end
-- Quantities -----------------------------------------------------
local length_str = firstNonEmpty(args, {"length"})
if length_str == "" and entity then
length_str = fmtQuantity(frame, quantity(entity, "P2043"), convertFlag) or ""
end
local duration_str = firstNonEmpty(args, {"duration"})
if duration_str == "" and entity then
duration_str = fmtQuantity(frame, quantity(entity, "P2047"), false) or ""
end
local ascent_str, ascent_data
ascent_str = firstNonEmpty(args, {"ascent"})
if ascent_str == "" and entity then
ascent_data = quantity(entity, "P7297")
ascent_str = fmtQuantity(frame, ascent_data, convertFlag) or ""
end
-- Descent (derived) ---------------------------------------------
local function toAscentUnits(q)
if not q or not ascent_data or q.type ~= "length" then return nil end
if q.symbol == ascent_data.symbol then return q.amount end
return convertAmount(q.amount, q.symbol, ascent_data.symbol)
end
local descent_str = ""
if ascent_data and start_id and dest_id then
local start_entity = mw.wikibase.getEntityObject(start_id)
local dest_entity = mw.wikibase.getEntityObject(dest_id)
local start_elev = start_entity and quantity(start_entity, "P2044") or nil
local dest_elev = dest_entity and quantity(dest_entity, "P2044") or nil
local s, d = toAscentUnits(start_elev), toAscentUnits(dest_elev)
if s and d then
local net_gain = d - s
local descent_amount = ascent_data.amount - net_gain
if descent_amount < 0 then descent_amount = 0 end
descent_str = fmtQuantity(frame,
{ amount = descent_amount, symbol = ascent_data.symbol, type = "length" },
convertFlag) or ""
end
end
return {
name = name,
from = from_str,
to = to_str,
length = length_str,
duration = duration_str,
ascent = ascent_str,
descent = descent_str,
wikidata = wikidataId
}
end
function p.show(frame)
local data = compute(frame)
return frame:expandTemplate{
title = 'Template:RouteSection/table',
args = data
}
end
return p