Jump to content

Module:Sandbox/Erutuon/Climate

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Erutuon (talk | contribs) at 01:39, 26 June 2018 (moving some calculations to separate functions; correction of "[CD]w"). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

local p = {}

local map = require "Module:Utility".map

local function errorf(level, ...)
	if type(level) == number then
		return error(string.format(...), level + 1)
	else -- level is actually the format string.
		return error(string.format(level, ...), 2)
	end
end

local function count(func, t)
	local count = 0
	for _, v in ipairs(t) do
		if func(v) then
			count = count + 1
		end
	end
	return count
end


local function make_arr_and_varargs_func(varargs_func)
	local function arr_func (arr, i, j)
		i = i or 1
		j = j or #arr
		assert(i > 0 and j > 0)
		
		if i == j then
			return arr[i]
		elseif i < j then -- |---i+++j---|
			return varargs_func(unpack(arr, i, j))
		else -- |+++j---i+++|
			return varargs_func(arr_func(arr, 1, j), arr_func(arr, i))
		end
	end
	
	return function (...)
		if type(...) == 'table' then -- if first argument is table
			if select(2, ...) then -- if second argument
				return arr_func(...)
			else
				return varargs_func(unpack((...)))
			end
		else
			return varargs_func(...)
		end
	end
end

local function sum_varargs (...)
	local n = select('#', ...)
	local result = 0
	for i = 1, n do
		result = result + select(i, ...)
	end
	return result
end

local mean = make_arr_and_varargs_func(
	function (...)
		return sum_varargs(...) / select('#', ...)
	end)
local sum = make_arr_and_varargs_func(sum_varargs)
local min_and_max = make_arr_and_varargs_func(
	function (...)
		local min, max = math.huge, -math.huge
		for i = 1, select('#', ...) do
			local val = select(i, ...)
			if val < min then
				min = val
			end
			if val > max then
				max = val
			end
		end
		return min, max
	end)

local function min_index(t)
	local index
	local min = math.huge
	for i, v in ipairs(t) do
		if v < min then
			min = v
			index = i
		end
	end
	return index
end

local function max_index(t)
	local index
	local max = -math.huge
	for i, v in ipairs(t) do
		if v > max then
			max = v
			index = i
		end
	end
	return index
end

local function in_range(val, low, high)
	if low < high then -- |---i+++j---|
		return low <= val and val <= high
	else -- |+++j---i+++|
		return val < high or low < val
	end
end

local function mean_of_highs_and_lows(highs, lows)
	local high_count, low_count = #highs, #lows
	-- for now, no annual average accepted
	if not (high_count == 12 and low_count == 12) then
		errorf("Wrong number of highs or lows (%d, %d): expected 12 each",
			high_count, low_count)
	elseif high_count ~= low_count then
		errorf("Number of highs (%d) is not equal to number of lows (%d)",
			high_count, low_count)
	end
	
	local temperatures = {}
	
	for i = 1, high_count do
		if highs[i] <= lows[i] then
			errorf("High #%d (%d) is not greater than low #%d (%d)",
				i, highs[i], i, lows[i])
		end
		temperatures[i] = mean(highs[i], lows[i])
	end
	
	return temperatures
end

local function get_aridity_threshold(mean_temp, total_precip, total_summer_precip)
	local summer_precip_fraction = total_summer_precip / total_precip
	return mean_temp * 20
		+ (summer_precip_fraction >= 0.7 and 280
		or summer_precip_fraction >= 0.3 and 140
		or 0)
end

local function get_first_letter(min_month_temp, max_month_temp)
	return max_month_temp <  10 and "E"
		or min_month_temp <   0 and "D"
		or min_month_temp >= 18 and "A"
		or "C"
end

-- Temperatures and precipitation are tables of mean monthly temperature and
-- precipitation. Or temperatures can be a table containing a table of monthly
-- mean of daily highs and monthly mean of daily lows.
-- Units: °C, mm.
function p.Koeppen(temperatures, precipitation, Southern_Hemisphere)
	local temperature_count = #temperatures
	if temperature_count == 2 then
		local highs_and_lows = temperatures
		temperatures = mean_of_highs_and_lows(unpack(temperatures))
	elseif temperature_count ~=  12 then
		errorf("Wrong number of temperatures (expected 12, got %d)",
			temperature_count)
	elseif #precipitation ~= 12 then
		errorf("Wrong number of precipitation stats (expected 12, got %d)",
			#precipitation)
	end
	
	local min_month_temp, max_month_temp = min_and_max(temperatures)
	
	local first_summer_month, last_summer_month
	if Southern_Hemisphere then
		first_summer_month, last_summer_month = 10, 3 -- October to March
	else
		first_summer_month, last_summer_month =  4, 9 -- April to September
	end
	
	local mean_temp = mean(temperatures)
	local total_precip = sum(precipitation)
	local total_summer_precip = sum(precipitation, first_summer_month, last_summer_month)
	
	local aridity_threshold =
		get_aridity_threshold(mean_temp, total_precip, total_summer_precip)
	
	if total_precip <= aridity_threshold then
		return "B"
			 .. (total_precip < aridity_threshold / 2 and "W" or "S") -- arid, semi-arid
			 .. (min_month_temp < 0 and "k" or "h") -- Alternative: mean_temp < 18
	end
	
	local first_letter = get_first_letter(min_month_temp, max_month_temp)
	
	if first_letter == "E" then
		return first_letter .. (max_month_temp < 0 and "F" or "T")
	elseif first_letter == "A" then
		local min_precip_index = min_index(precipitation)
		local min_month_precip = precipitation[min_precip_index]
		
		return first_letter
			.. (min_month_precip >= 60 and "f"
			or  min_month_precip / total_precip > 0.04 and "m"
			or  in_range(min_precip_index, first_summer_month, last_summer_month) and "s"
			or  "w")
	else
		local min_summer_precip, max_summer_precip =
			min_and_max(precipitation, first_summer_month, last_summer_month)
		local min_winter_precip, max_winter_precip =
			min_and_max(precipitation, last_summer_month + 1, first_summer_month - 1)
		
		local second_letter =
		-- Alternative: total_precip / total_summer_precip >= 0.7
			max_summer_precip > min_winter_precip * 10 and "w"
			or min_summer_precip < 30 and max_winter_precip > min_summer_precip * 3
				and "s"
			or "f"
		
		local months_above_10 = count(
			function (temperature)
				return temperature > 10
			end,
			temperatures)
		
		local third_letter
		if months_above_10 <= 3 then
			if min_month_temp < -38 then
				third_letter = "d"
			else
				third_letter = "c"
			end
		elseif max_month_temp < 22 then
			third_letter = "b"
		else
			third_letter = "a"
		end
		
		return first_letter .. second_letter .. third_letter
	end
end

local function gather_numbers(str)
	local arr = {}
	local i = 0
	for number in str:gmatch('%-?%d+%.?%d*') do
		i = i + 1
		arr[i] = tonumber(number)
	end
	return arr
end

function p.Koeppen_template(frame)
	local temperature_string = frame.args[1]
	local precipitation_string = frame.args[2]
	
	local temperatures, precipitation =
		gather_numbers(temperature_string), gather_numbers(precipitation_string)
	local Southern_Hemisphere = require 'Module:yesno' (frame.args[3])
	
	return p.Koeppen(temperatures, precipitation, Southern_Hemisphere)
end

return p