Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Helpers for {{Infobox trade route}}. String functions are not available on this wiki, so the place lists are read here.

Place lists

The infobox's origin, via, destination and commodity take bare page titles separated by semicolons. A place may carry its point after an @, in decimal degrees: Leh @ 34.15, 77.58. The point is rounded to two decimal places and is not shown. In via, the semicolons give the order the places were passed, and a slash separates places that were alternatives at the same stage: Karakoram Pass; Leh; Kulu / Kashmir.

Functions

{{#invoke:Trade route|show|1=…}}
The places linked, separated by commas. With mode=via, the stages in order joined by arrows, the alternatives of one stage joined by "or". A link or other markup in a title, or a point that is not decimal degrees, shows an error beside the place.
{{#invoke:Trade route|names|1=…}}
The bare titles separated by semicolons, without the points, for #set with +sep=;.
{{#invoke:Trade route|waypoints|origin=…|via=…|destination=…}}
One waypoint subobject per place, linked back to its page by Property:Has route page. Each stores Property:Has waypoint place, Property:Has waypoint step (0 for the origin, then 1, 2 … for the places by way of, then the destination), Property:Has waypoint role and, when the place has a point, Property:Has coordinates. A place with no point is stored but not drawn on a map.
{{#invoke:Trade route|period|from=…|to=…|status=…|active=…}}
The Active row: active when it is given, otherwise the years (1850–1935; 1850–present for a route still in use with no last year). Years are read by Module:Timeline: a single year, negative before the Common Era. Anything else shows an error.

Querying the waypoints

{{#ask: [[Has route page::Yarkand charas trade]]
 |?Has waypoint step |?Has waypoint place |?Has coordinates
 |sort=Has waypoint step
}}

-- Helpers for {{Infobox trade route}}: the place lists (origin, via, destination, commodity), the
-- waypoint subobjects the History map draws a route from, and the Active row.
local p = {}

local Timeline = require('Module:Timeline')

local COORD_ERROR = 'Coordinates must be decimal degrees, for example 34.15, 77.58'
local NAME_ERROR = 'Give the bare page title: the template makes the link'
local EMPTY_ERROR = 'A point needs a place name before it'
local YEAR_ERROR = 'Give a single year, negative before the Common Era'
local ORDER_ERROR = 'The last year is before the first'

local function err(msg)
	return '<span class="error">' .. msg .. '</span>'
end

-- Two decimal places, as the timeline subobject rounds its points.
local function round2(x)
	local r = math.floor(math.abs(x) * 100 + 0.5) / 100
	if x < 0 then
		r = -r
	end
	return r
end

-- One entry: "Leh" or "Leh @ 34.15, 77.58" -> {name, coords ("34.15,77.58" or nil), err (or nil)}.
local function entry(s)
	local name, point = s:match('^(.-)%s*@%s*(.-)%s*$')
	local e = {name = mw.text.trim(name or s)}
	if e.name == '' then
		e.err = EMPTY_ERROR
	elseif e.name:find('[%[%]{}|<>]') then
		e.err = NAME_ERROR
	end
	if point then
		local lat, lon = point:match('^(%-?%d+%.?%d*)%s*,%s*(%-?%d+%.?%d*)$')
		lat, lon = tonumber(lat), tonumber(lon)
		if lat and lon and math.abs(lat) <= 90 and math.abs(lon) <= 180 then
			e.coords = round2(lat) .. ',' .. round2(lon)
		else
			e.err = e.err or COORD_ERROR
		end
	end
	return e
end

-- A field as ordered steps: ";" separates steps, "/" separates the places of one step.
local function steps(s)
	local out = {}
	for _, part in ipairs(mw.text.split(s or '', ';', true)) do
		local step = {}
		for _, alt in ipairs(mw.text.split(part, '/', true)) do
			if mw.text.trim(alt) ~= '' then
				table.insert(step, entry(alt))
			end
		end
		if #step > 0 then
			table.insert(out, step)
		end
	end
	return out
end

-- Every place of a field, in order, as one step (origin, destination, commodity).
local function flat(s)
	local out = {}
	for _, step in ipairs(steps(s)) do
		for _, e in ipairs(step) do
			table.insert(out, e)
		end
	end
	return out
end

-- A stored entry has a usable title (a bad point is shown as an error, the place is still stored).
local function storable(e)
	return e.err ~= NAME_ERROR and e.err ~= EMPTY_ERROR
end

local function link(e)
	local s
	if storable(e) then
		s = '[[' .. e.name .. ']]'
	else
		s = mw.text.nowiki(e.name)
	end
	if e.err then
		s = s .. ' ' .. err(e.err)
	end
	return s
end

-- {{#invoke:Trade route|show|1=…}}: the places linked, separated by commas.
-- With mode=via: the steps in order, joined by arrows, the places of one step joined by "or".
function p.show(frame)
	local a = frame.args
	if a.mode == 'via' then
		local parts = {}
		for _, step in ipairs(steps(a[1])) do
			local alts = {}
			for _, e in ipairs(step) do
				table.insert(alts, link(e))
			end
			table.insert(parts, table.concat(alts, ' or '))
		end
		return table.concat(parts, ' → ')
	end
	local parts = {}
	for _, e in ipairs(flat(a[1])) do
		table.insert(parts, link(e))
	end
	return table.concat(parts, ', ')
end

-- {{#invoke:Trade route|names|1=…}}: the bare titles separated by ";", for #set with +sep=;.
function p.names(frame)
	local parts = {}
	for _, e in ipairs(flat(frame.args[1])) do
		if storable(e) then
			table.insert(parts, e.name)
		end
	end
	return table.concat(parts, ';')
end

-- {{#invoke:Trade route|waypoints|origin=…|via=…|destination=…}}: one subobject per place, each
-- linked back to its page. Step 0 is the origin, steps 1 to n the places by way of, step n + 1
-- the destination. A place with no point is stored without coordinates and is not drawn.
function p.waypoints(frame)
	local a = frame.args
	local plan = {{places = flat(a.origin), role = 'Origin'}}
	for _, step in ipairs(steps(a.via)) do
		table.insert(plan, {places = step, role = 'By way of'})
	end
	table.insert(plan, {places = flat(a.destination), role = 'Destination'})
	local out = {}
	for n, step in ipairs(plan) do
		local number = (step.role == 'Destination') and (#plan - 1) or (n - 1)
		for i, e in ipairs(step.places) do
			if storable(e) then
				table.insert(out, frame:callParserFunction('#subobject', {
					'waypoint-' .. number .. '-' .. i,
					['@linkWith'] = 'Has route page',
					['Has waypoint place'] = e.name,
					['Has waypoint step'] = tostring(number),
					['Has waypoint role'] = step.role,
					['Has coordinates'] = e.coords or '',
				}))
			end
		end
	end
	return table.concat(out)
end

local function showYear(y)
	if y < 0 then
		return (-y) .. ' BCE'
	end
	return tostring(y)
end

local function year(s)
	local y = Timeline._year(s)
	if y == 0 then
		return nil
	end
	return y
end

-- {{#invoke:Trade route|period|from=…|to=…|status=…|active=…}}: the Active row. The words in
-- "active" when given, else the years; an error beside it when a year is not a single year.
function p.period(frame)
	local a = frame.args
	local fs, ts = mw.text.trim(a.from or ''), mw.text.trim(a.to or '')
	local f, t = year(fs), year(ts)
	local notes = {}
	if fs ~= '' and not f then
		table.insert(notes, err(YEAR_ERROR))
	end
	if ts ~= '' and not t then
		table.insert(notes, err(YEAR_ERROR))
	end
	if f and t and t < f then
		table.insert(notes, err(ORDER_ERROR))
	end
	local shown = mw.text.trim(a.active or '')
	if shown == '' then
		if f and t and t ~= f then
			local dash = (f < 0 or t < 0) and ' – ' or '–'
			shown = showYear(f) .. dash .. showYear(t)
		elseif f and t then
			shown = showYear(f)
		elseif f and mw.text.trim(a.status or '') == 'Still in use' then
			shown = showYear(f) .. '–present'
		elseif f then
			shown = 'from ' .. showYear(f)
		elseif t then
			shown = 'until ' .. showYear(t)
		end
	end
	if #notes > 0 then
		shown = mw.text.trim(shown .. ' ' .. table.concat(notes, ' '))
	end
	return shown
end

return p