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.

This is a documentation subpage for Climate profile. It contains usage information, categories, and other content that is not part of the original template.

This module shows the computed climate rows in {{Infobox Appellation}} and {{Infobox Growing Area}}: the Köppen class, the elevation and the five closest climate matches among the wiki's appellations and growing areas, each labelled as computed, with a line naming the datasets. It also gives the bare values the two infoboxes store. The method and the datasets are explained at Help:Climate matches.

The figures are not entered on pages. They sit on two data subpages, read with mw.loadData once per page render:

All are generated together from one computation over all places and accessions and are replaced together. They are not edited by hand: a change to one place would leave the similarity figures of the others inconsistent.

Functions

All functions are called with {{#invoke:Climate profile|function}}. The page is found by key= on the call, else by climate_key on the calling infobox, else by the current page title. A page with no entry gets an empty string from every function, so the infobox shows and stores nothing for it.

Function Returns
has yes when the page has an entry, else empty. The infoboxes use it to show the Climate heading.
rows The infobox rows: Köppen class (computed), Elevation (computed), Closest climate matches, and the dataset line, each followed by a row separator.
koppen The Köppen–Geiger class code, for Has computed Köppen class.
elevation The median elevation in metres, a bare number, for Has computed elevation.
matches The five match titles separated by ;, for Has climate match (stored with +sep=;).
count The number of places with an entry.

From Lua, require('Module:Climate profile') gives _profile(key) (the entry), _matches(key) (an array of { title, similarity }, closest first), _normals(key) (a place's, else an accession's), _accession(key) (an accession's point, Köppen class, elevation and normals) and _meta().

Data fields

Each entry of places on the data subpage is keyed by page title.

Field Meaning
kind appellation or growing area
s polygon (sampled inside the border) or point
lat, lon Centre of the border, or the point
area Area inside the border, km² (absent for a point)
k, ks Köppen–Geiger class covering the largest share of the sampled cells, and that share (0–1)
e, e10, e90 Elevation in metres: median, 10th and 90th percentile
es Elevation dataset: G GMTED2010 (1 km), C Copernicus GLO-90 (90 m)
bio5, bio6 Maximum temperature of the warmest month and minimum temperature of the coldest month, °C
bio12, bio18 Annual precipitation and precipitation of the warmest quarter, mm
ss bio18 ÷ bio12
ld Length of the longest day, hours
m Five { title, similarity } pairs, closest first

meta holds the computation date, the dataset names and the standard deviations, means and weights used in the distance. On the normals subpage, each entry holds tmax and tmin (°C), pr (mm per month), vpd (Pa) and rsds (MJ m⁻² day⁻¹), twelve values each; meta.units repeats the units.

Tests

Module:Climate profile/testcases checks named places against the computation's own tables, recomputes every similarity from the stored values and checks the shape of every entry:

32 of 32 cases pass.

Group Case Expected Module Result
Malana (appellation) <nowiki>has</nowiki> <nowiki>yes</nowiki> <nowiki>yes</nowiki> pass
Malana (appellation) <nowiki>koppen</nowiki> <nowiki>Dfc</nowiki> <nowiki>Dfc</nowiki> pass
Malana (appellation) <nowiki>elevation</nowiki> <nowiki>3171</nowiki> <nowiki>3171</nowiki> pass
Malana (appellation) <nowiki>matches</nowiki> <nowiki>Nirang (appellation);Mejigh (appellation);Waichin (appellation);Gohar (appellation);Thal (appellation)</nowiki> <nowiki>Nirang (appellation);Mejigh (appellation);Waichin (appellation);Gohar (appellation);Thal (appellation)</nowiki> pass
Malana (appellation) <nowiki>Köppen text</nowiki> <nowiki>Dfc (cold, no dry season, cold summer)</nowiki> <nowiki>Dfc (cold, no dry season, cold summer)</nowiki> pass
Malana (appellation) <nowiki>elevation text</nowiki> <nowiki>3,171 m (2,672–3,589 m)</nowiki> <nowiki>3,171 m (2,672–3,589 m)</nowiki> pass
Malana (appellation) <nowiki>sources</nowiki> <nowiki>Computed from CHELSA V2.1 (1981–2010), the Köppen–Geiger map of Beck et al. (1991–2020) and Copernicus GLO-90 (90 m) · About these figures</nowiki> <nowiki>Computed from CHELSA V2.1 (1981–2010), the Köppen–Geiger map of Beck et al. (1991–2020) and Copernicus GLO-90 (90 m) · About these figures</nowiki> pass
Saur Valley (appellation) <nowiki>elevation text</nowiki> <nowiki>1,548 m</nowiki> <nowiki>1,548 m</nowiki> pass
Saur Valley (appellation) <nowiki>Köppen text</nowiki> <nowiki>Cwa (temperate, dry winter, hot summer)</nowiki> <nowiki>Cwa (temperate, dry winter, hot summer)</nowiki> pass
Saur Valley (appellation) <nowiki>matches text</nowiki> <nowiki>Patkai Bum – 75
Kali Valley – 74
Liti (appellation) – 73
Naga Hills – 72
Upper Jaldhaka Valley – 71</nowiki>
<nowiki>Patkai Bum – 75
Kali Valley – 74
Liti (appellation) – 73
Naga Hills – 72
Upper Jaldhaka Valley – 71</nowiki>
pass
Tha Sala (appellation) <nowiki>elevation text</nowiki> <nowiki>14 m (5–35 m)</nowiki> <nowiki>14 m (5–35 m)</nowiki> pass
Tha Sala (appellation) <nowiki>Köppen text</nowiki> <nowiki>Af (tropical, rainforest)</nowiki> <nowiki>Af (tropical, rainforest)</nowiki> pass
Tha Sala (appellation) <nowiki>sources</nowiki> <nowiki>Computed from CHELSA V2.1 (1981–2010), the Köppen–Geiger map of Beck et al. (1991–2020) and GMTED2010 (1 km) · About these figures</nowiki> <nowiki>Computed from CHELSA V2.1 (1981–2010), the Köppen–Geiger map of Beck et al. (1991–2020) and GMTED2010 (1 km) · About these figures</nowiki> pass
Abdelghaya Souahel (appellation) <nowiki>rows</nowiki> Köppen class (computed) Csa (temperate, dry summer, hot summer)
Elevation (computed) 1,061 m (681–1,536 m)
Closest climate matches Beni Bouchibet (appellation) – 91
Ketama (appellation) – 89
Chefchaouen – 87
Beni Ahmed Imoukzan (appellation) – 86
Beqaa Valley – 77
Computed from CHELSA V2.1 (1981–2010), the Köppen–Geiger map of Beck et al. (1991–2020) and GMTED2010 (1 km) · About these figures
Csa (temperate, dry summer, hot summer)
Elevation (computed) 1,061 m (681–1,536 m)
Closest climate matches Beni Bouchibet (appellation) – 91
Ketama (appellation) – 89
Chefchaouen – 87
Beni Ahmed Imoukzan (appellation) – 86
Beqaa Valley – 77
Computed from CHELSA V2.1 (1981–2010), the Köppen–Geiger map of Beck et al. (1991–2020) and GMTED2010 (1 km) · About these figures
Yu County, Hebei <nowiki>koppen</nowiki> <nowiki>BSk</nowiki> <nowiki>BSk</nowiki> pass
climate_key <nowiki>parent argument</nowiki> <nowiki>Dfc</nowiki> <nowiki>Dfc</nowiki> pass
No profile <nowiki>has</nowiki> <nowiki></nowiki> <nowiki></nowiki> pass
No profile <nowiki>rows</nowiki> <nowiki></nowiki> <nowiki></nowiki> pass
No profile <nowiki>koppen</nowiki> <nowiki></nowiki> <nowiki></nowiki> pass
No profile <nowiki>elevation</nowiki> <nowiki></nowiki> <nowiki></nowiki> pass
No profile <nowiki>matches</nowiki> <nowiki></nowiki> <nowiki></nowiki> pass
Whole data <nowiki>places</nowiki> <nowiki>377</nowiki> <nowiki>377</nowiki> pass
Whole data <nowiki>places with exactly five matches</nowiki> <nowiki>377</nowiki> <nowiki>377</nowiki> pass
Whole data <nowiki>matches naming a place with no profile</nowiki> <nowiki>0</nowiki> <nowiki>0</nowiki> pass
Whole data <nowiki>places listed as their own match</nowiki> <nowiki>0</nowiki> <nowiki>0</nowiki> pass
Whole data <nowiki>similarities rising down a list</nowiki> <nowiki>0</nowiki> <nowiki>0</nowiki> pass
Whole data <nowiki>Köppen classes in the legend</nowiki> <nowiki>377</nowiki> <nowiki>377</nowiki> pass
Whole data <nowiki>medians inside their 10th–90th range</nowiki> <nowiki>377</nowiki> <nowiki>377</nowiki> pass
Whole data <nowiki>places with twelve months of all five normals</nowiki> <nowiki>377</nowiki> <nowiki>377</nowiki> pass
Whole data <nowiki>similarities more than 1 point off when recomputed</nowiki> <nowiki>0</nowiki> <nowiki>0</nowiki> pass
Whole data <nowiki>similarities 1 point off (rounding; shown, not failed)</nowiki> <nowiki>–</nowiki> <nowiki>2</nowiki> pass
Whole data <nowiki>count()</nowiki> <nowiki>377</nowiki> <nowiki>377</nowiki> pass

-- Module:Climate profile
-- Computed climate rows for {{Infobox Appellation}} and {{Infobox Growing Area}}: the Köppen–Geiger class,
-- the elevation and the five closest climate matches among the wiki's appellations and growing areas.
-- The figures are on Module:Climate profile/data (and the monthly normals on Module:Climate profile/normals);
-- the method and datasets are on Help:Climate matches. Functions and fields: Module:Climate profile/doc.

local p = {}

p.DATA = 'Module:Climate profile/data'
p.NORMALS = 'Module:Climate profile/normals'
p.ACCESSIONS = 'Module:Climate profile/accessions'
local HELP = 'Help:Climate matches'
local MATCHES = 5

-- Beck et al. (2023) legend.
local KOPPEN = {
	Af = 'tropical, rainforest', Am = 'tropical, monsoon', Aw = 'tropical, savannah',
	BWh = 'arid, desert, hot', BWk = 'arid, desert, cold', BSh = 'arid, steppe, hot', BSk = 'arid, steppe, cold',
	Csa = 'temperate, dry summer, hot summer', Csb = 'temperate, dry summer, warm summer',
	Csc = 'temperate, dry summer, cold summer',
	Cwa = 'temperate, dry winter, hot summer', Cwb = 'temperate, dry winter, warm summer',
	Cwc = 'temperate, dry winter, cold summer',
	Cfa = 'temperate, no dry season, hot summer', Cfb = 'temperate, no dry season, warm summer',
	Cfc = 'temperate, no dry season, cold summer',
	Dsa = 'cold, dry summer, hot summer', Dsb = 'cold, dry summer, warm summer', Dsc = 'cold, dry summer, cold summer',
	Dsd = 'cold, dry summer, very cold winter',
	Dwa = 'cold, dry winter, hot summer', Dwb = 'cold, dry winter, warm summer', Dwc = 'cold, dry winter, cold summer',
	Dwd = 'cold, dry winter, very cold winter',
	Dfa = 'cold, no dry season, hot summer', Dfb = 'cold, no dry season, warm summer',
	Dfc = 'cold, no dry season, cold summer', Dfd = 'cold, no dry season, very cold winter',
	ET = 'polar, tundra', EF = 'polar, frost',
}
p.KOPPEN = KOPPEN

local function trim(s)
	if s == nil then return nil end
	s = mw.text.trim(tostring(s))
	return s ~= '' and s or nil
end

-- The page whose figures are wanted: key= on the #invoke, else climate_key= on the calling template,
-- else the current page.
local function getKey(frame)
	local k = trim(frame.args.key)
	if k then return k end
	local parent = frame:getParent()
	if parent then
		k = trim(parent.args.climate_key)
		if k then return k end
	end
	return mw.title.getCurrentTitle().text
end

-- Lua API -----------------------------------------------------------------------------------------

-- The stored profile of a place (a read-only table), or nil.
function p._profile(key)
	if not key then return nil end
	return mw.loadData(p.DATA).places[key]
end

-- The twelve monthly normals of a place or an accession: { tmax = {…12}, tmin, pr, vpd, rsds }, or nil. Units: the
-- meta table of Module:Climate profile/normals. Places are looked up first; the accessions subpage is loaded only
-- for a key that is not a place.
function p._normals(key)
	if not key then return nil end
	return mw.loadData(p.NORMALS).places[key] or p._accession(key)
end

-- The stored point, Köppen class, elevation and normals of an accession (a read-only table), or nil. The
-- accessions are split by title into chunks; the index names the first title of each, so one chunk is loaded.
function p._accession(key)
	if not key then return nil end
	local chunks = mw.loadData(p.ACCESSIONS).chunks
	local page
	local i = 1
	while chunks[i] do
		if chunks[i].first <= key then page = chunks[i].page else break end
		i = i + 1
	end
	if not page then return nil end
	return mw.loadData(page).accessions[key]
end

function p._meta()
	return mw.loadData(p.DATA).meta
end

-- The matches of a place as a plain array of { title = …, similarity = … }, closest first.
function p._matches(key)
	local e = p._profile(key)
	local out = {}
	if not e then return out end
	for i = 1, MATCHES do
		local m = e.m[i]
		if not m then break end
		out[#out + 1] = { title = m[1], similarity = m[2] }
	end
	return out
end

-- Formatting --------------------------------------------------------------------------------------

local function fmt(n)
	return mw.language.getContentLanguage():formatNum(math.floor(n + 0.5))
end

function p._koppenText(e)
	local d = KOPPEN[e.k]
	return d and (e.k .. ' (' .. d .. ')') or e.k
end

function p._elevationText(e)
	if e.e10 == e.e90 then
		return fmt(e.e) .. ' m'
	end
	return fmt(e.e) .. ' m (' .. fmt(e.e10) .. '–' .. fmt(e.e90) .. ' m)'
end

function p._matchesText(key)
	local lines = {}
	for _, m in ipairs(p._matches(key)) do
		lines[#lines + 1] = '[[' .. m.title .. ']]&nbsp;–&nbsp;<span title="Similarity, out of 100">'
			.. m.similarity .. '</span>'
	end
	return table.concat(lines, '<br>')
end

function p._sourcesText(e)
	local meta = p._meta()
	return 'Computed from ' .. meta.climate .. ', ' .. meta.koppen .. ' and '
		.. (meta.elevation[e.es] or e.es) .. ' · [[' .. HELP .. '|About these figures]]'
end

-- Infobox rows, each followed by a row separator; empty when the page has no profile.
function p._rows(key)
	local e = p._profile(key)
	if not e then return '' end
	local o = {}
	local function row(label, value)
		o[#o + 1] = '! class="lw-infobox__label" | ' .. label .. '\n| class="lw-infobox__value" | ' .. value .. '\n|-'
	end
	if e.k and e.k ~= '' then row('Köppen class (computed)', p._koppenText(e)) end
	row('Elevation (computed)', p._elevationText(e))
	local m = p._matchesText(key)
	if m ~= '' then row('Closest climate matches', m) end
	o[#o + 1] = '| colspan="2" class="lw-infobox__caption" | ' .. p._sourcesText(e) .. '\n|-'
	return table.concat(o, '\n')
end

-- #invoke functions -------------------------------------------------------------------------------

-- "yes" when the page has a profile, else empty (for the infobox's Climate header).
function p.has(frame)
	return p._profile(getKey(frame)) and 'yes' or ''
end

function p.rows(frame)
	return p._rows(getKey(frame))
end

-- Bare values for {{#set:}}.
function p.koppen(frame)
	local e = p._profile(getKey(frame))
	return e and e.k or ''
end

function p.elevation(frame)
	local e = p._profile(getKey(frame))
	return e and tostring(math.floor(e.e + 0.5)) or ''
end

-- Match titles separated by ";" (the #set uses |+sep=;).
function p.matches(frame)
	local t = {}
	for _, m in ipairs(p._matches(getKey(frame))) do t[#t + 1] = m.title end
	return table.concat(t, ';')
end

-- The number of places with a profile.
function p.count(frame)
	local n = 0
	for _ in pairs(mw.loadData(p.DATA).places) do n = n + 1 end
	return tostring(n)
end

return p