
///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//                     Tool Library                      //
//                    io_webservices                     //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//                      chelsa.cpp                       //
//                                                       //
//                 Copyrights (C) 2025                   //
//                     Olaf Conrad                       //
//                                                       //
//-------------------------------------------------------//
//                                                       //
// This file is part of 'SAGA - System for Automated     //
// Geoscientific Analyses'. SAGA is free software; you   //
// can redistribute it and/or modify it under the terms  //
// of the GNU General Public License as published by the //
// Free Software Foundation, either version 2 of the     //
// License, or (at your option) any later version.       //
//                                                       //
// SAGA is distributed in the hope that it will be       //
// useful, but WITHOUT ANY WARRANTY; without even the    //
// implied warranty of MERCHANTABILITY or FITNESS FOR A  //
// PARTICULAR PURPOSE. See the GNU General Public        //
// License for more details.                             //
//                                                       //
// You should have received a copy of the GNU General    //
// Public License along with this program; if not, see   //
// <http://www.gnu.org/licenses/>.                       //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//    e-mail:     oconrad@saga-gis.org                   //
//                                                       //
//    contact:    Olaf Conrad                            //
//                Institute of Geography                 //
//                University of Hamburg                  //
//                Germany                                //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#include "chelsa.h"


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
class CHELSA
{
public:
	enum class Datasets
	{
		daily, monthly, climatology, bioclim, T21k, T21k_bioclim
	};

	enum Vars
	{
		clt, cmi, hurs, pet, pr, ps, rsds, sfcWind, spei12, spi12, tas, tasmax, tasmin, tz, vpd,
		bio01, bio02, bio03, bio04, bio05, bio06, bio07, bio08, bio09, bio10, bio11, bio12, bio13, bio14, bio15, bio16, bio17, bio18, bio19,
		fcf, fgd, gdd0, gdd5, gdd10, gdgfgd5, gdgfgd10, gsl, gsp, gst, kg0, kg1, kg2, kg3, kg4, kg5, lgd, ngd0, ngd5, ngd10, npp, scd, swe,
		glz, orog,
	//	// only available for observed climate (1981-2010):
	//	cltmax, cltmean, cltmin, cltrange,                 // cloud cover
	//	cmimax, cmimean, cmimin, cmirange,                 // climate moisture index
	//	hursmax, hursmean, hursmin, hursrange,             // relative humidity
	//	petmax, petmean, petmin, petrange,                 // relative humidity
	//	rsdsmax, rsdsmean, rsdsmin, rsdsrange,             // shortwave flux down
	//	sfcWindmax, sfcWindmean, sfcWindmin, sfcWindrange, // wind speed
	//	vpdmax, vpdmean, vpdmin, vpdrange,                 // vapor pressure deficit
		count
	};

	//-----------------------------------------------------
	static const SG_Char *		Get_Server_Root_Global	(void) { return( SG_T("/vsicurl/https://os.unil.cloud.switch.ch/chelsa02/chelsa/global") ); }

	//-----------------------------------------------------
	static CSG_String			Get_Dataset_Name		(Datasets Dataset)
	{
		switch( Dataset )
		{
		case Datasets::daily       : return( _TL("Daily"                       ) );
		case Datasets::monthly     : return( _TL("Monthly"                     ) );
		case Datasets::climatology : return( _TL("Climatology"                 ) );
		case Datasets::bioclim     : return( _TL("Bioclimatological Indicators") );
		case Datasets::T21k        : return( _TL("TraCE21k-centennial"         ) );
		case Datasets::T21k_bioclim: return( _TL("TraCE21k-centennial-bioclim" ) );
		}

		return( SG_T("") );
	}

	//-----------------------------------------------------
	struct SVariable { CSG_String key, unit, name, description; };

	static int					Get_Variable_Count		(void) { return( Vars::count ); }
	static int					Get_Variable_Count		(Datasets Dataset)
	{
		switch( Dataset )
		{
		case Datasets::daily       : return( 10 );
		case Datasets::monthly     : return( 13 );
		case Datasets::climatology : return( 11 );
		case Datasets::bioclim     : return( 42 );
		case Datasets::T21k        : return(  4 );
		case Datasets::T21k_bioclim: return( 22 );
		}

		return( 0 );
	}

	static int					Get_Variable_ID			(Datasets Dataset, int Index)
	{
		if( Index >= 0 && Index < Get_Variable_Count(Dataset) )
		{
			switch( Dataset )
			{
			case Datasets::daily       : { static const int id[10] = { clt,      hurs,      pr, ps, rsds, sfcWind,                tas, tasmax, tasmin, tz,     }; return( id[Index] ); }
			case Datasets::monthly     : { static const int id[13] = { clt, cmi, hurs, pet, pr,     rsds, sfcWind, spei12, spi12, tas, tasmax, tasmin,     vpd }; return( id[Index] ); }
			case Datasets::climatology : { static const int id[11] = { clt, cmi, hurs, pet, pr,     rsds, sfcWind,                tas, tasmax, tasmin,     vpd }; return( id[Index] ); }
			case Datasets::bioclim     : { static const int id[42] = { bio01, bio02, bio03, bio04, bio05, bio06, bio07, bio08, bio09, bio10, bio11, bio12, bio13, bio14, bio15, bio16, bio17, bio18, bio19, fcf, fgd, gdd0, gdd10, gdd5, gdgfgd10, gdgfgd5, gsl, gsp, gst, kg0, kg1, kg2, kg3, kg4, kg5, lgd, ngd0, ngd10, ngd5, npp, scd, swe }; return( id[Index] ); }
			case Datasets::T21k        : { static const int id[ 4] = {                      pr,                                        tasmax, tasmin, tz      }; return( id[Index] ); }
			case Datasets::T21k_bioclim: { static const int id[42] = { bio01, bio02, bio03, bio04, bio05, bio06, bio07, bio08, bio09, bio10, bio11, bio12, bio13, bio14, bio15, bio16, bio17, bio18, bio19, glz, orog, scd }; return( id[Index] ); }
			}
		}

		return( -1 );
	}

	static struct SVariable &	Get_Variable_Info		(int VarID)
	{
		return( m_Variable_Info[VarID] );
	}

	static struct SVariable &	Get_Variable_Info		(Datasets Dataset, int Index)
	{
		return( Get_Variable_Info(Get_Variable_ID(Dataset, Index)) );
	}

	static CSG_String			Get_Variable_Description(int VarID, bool bWithName = true)
	{
		CSG_String Description(m_Variable_Info[VarID].description);

		if( bWithName )
		{
			Description.Prepend(m_Variable_Info[VarID].name + ": ");
		}

		return( Description );
	}

	static CSG_String			Get_Variable_Choice		(Datasets Dataset)
	{
		CSG_String Variables;

		for(int i=0; i<Get_Variable_Count(Dataset); i++)
		{
			int id = Get_Variable_ID(Dataset, i); Variables += CSG_String::Format("{%d}%s|", id, Get_Variable_Info(id).name.c_str());
		}

		if( Dataset == Datasets::bioclim )
		{
			Variables += CSG_String::Format("{%d}[%s]|", CHELSA::Vars::count, _TL("all"));
		}

		return( Variables );
	}

	static bool					is_Category				(int VarID)
	{
		switch( VarID )
		{
		default:
			return( false );

		case fcf     : // Frost Change Frequency
		case fgd     : // First Day of the Growing Season (TREELIM)
		case gdgfgd5 : // First Growing Degree Day above 5 °C
		case gdgfgd10: // First Growing Degree Day above 10 °C
		case gsl     : // Growing Season Length
		case kg0     : // Köppen-Geiger Climate Classification
		case kg1     : // Köppen-Geiger Climate Classification (without As/Aw)
		case kg2     : // Köppen-Geiger Climate Classification (Peel et al. 2007)
		case kg3     : // Wissmann Climate Classification
		case kg4     : // Thornthwaite Climate Classification
		case kg5     : // Troll-Paffen Climate Classification
		case lgd     : // Last Day of the Growing Season (TREELIM)
		case ngd0    : // Number of Growing Degree Days above 0 °C
		case ngd5    : // Number of Growing Degree Days above 5 °C
		case ngd10   : // Number of Growing Degree Days above 10 °C
		case scd     : // Snow Cover days
			return( true );
		}
	}

	static bool					Get_Variable_Scaling	(Datasets Dataset, Vars VarID, double &Scaling, double &Offset);


private:

	static struct SVariable m_Variable_Info[CHELSA::Vars::count];

};


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
struct CHELSA::SVariable CHELSA::m_Variable_Info[CHELSA::Vars::count] =
{
	{ "clt"     , _TL("%"             ), _TL("Total Cloud Cover Percentage"                                        ), _TL("Total cloud area fraction (reported as a percentage) for the whole atmospheric column as seen from the surface or the top of the atmosphere. Includes both large-scale and convective cloud.") },
	{ "cmi"     , _TL("kg/m²"         ), _TL("Monthly Climate Moisture Index"                                      ), _TL("Monthly ratio of precipitation to potential evapotranspiration; indicator of climatic water availability") },
	{ "hurs"    , _TL("%"             ), _TL("Near-Surface Relative Humidity"                                      ), _TL("The relative humidity with respect to liquid water for T> 0 C and with respect to ice for T") },
	{ "pet"     , _TL("kg/m²"         ), _TL("Monthly Potential Evapotranspiration"                                ), _TL("Total potential evapotranspiration for the month assuming unlimited water availability calculated based on Penman Monteith") },
	{ "pr"      , _TL("kg/m²"         ), _TL("Precipitation"                                                       ), _TL("includes both liquid and solid phases") },
	{ "ps"      , _TL("hPa"           ), _TL("Surface Air Pressure"                                                ), _TL("surface pressure (not mean sea-level pressure)") },
	{ "rsds"    , _TL("W/m²"          ), _TL("Surface Downwelling Shortwave Flux in Air"                           ), _TL("Surface solar irradiance for UV calculations.") },
	{ "sfcWind" , _TL("m/s"           ), _TL("Near-Surface Wind Speed"                                             ), _TL("near-surface (usually 10 meters) wind speed.") },
	{ "spei12"  , _TL(""              ), _TL("Standardized Precipitation Evapotranspiration Index"                 ), _TL("Standardized index of climatic water balance (precipitation minus potential evapotranspiration) over a given integration period here 12 months expressed in standard deviations from the long term mean, used to characterize drought severity and duration") },
	{ "spi12"   , _TL(""              ), _TL("Standardized Precipitation Index"                                    ), _TL("Standardized index of precipitation anomalies over a given integration period here 12 months expressed in standard deviations from the long-term mean; used to characterize drought severity and duration without accounting for evapotranspiration") },
	{ "tas"     , _TL("°C"            ), _TL("Daily Mean Near-Surface Air Temperature"                             ), _TL("near-surface (usually 2 meter) air temperature") },
	{ "tasmax"  , _TL("°C"            ), _TL("Daily Maximum Near-Surface Air Temperature"                          ), _TL("maximum near-surface (usually 2 meter) air temperature") },
	{ "tasmin"  , _TL("°C"            ), _TL("Daily Minimum Near-Surface Air Temperature"                          ), _TL("minimum near-surface (usually 2 meter) air temperature") },
	{ "tz"      , _TL("°C/100m"       ), _TL("Air Temperature Lapse Rate"                                          ), _TL("Rate of change in air temperature with altitude") },
	{ "vpd"     , _TL("Pa"            ), _TL("Vapor Pressure Deficit"                                              ), _TL("Difference between the saturation vapor pressure and actual vapor pressure representing atmospheric drying power") },

	//-----------------------------------------------------
	{ "bio01"   , _TL("°C"            ), _TL("Mean Annual Near-Surface Air Temperature"                            ), _TL("Mean annual temperature calculated as the average of mean monthly temperatures over the year") },
	{ "bio02"   , _TL("°C"            ), _TL("Mean Diurnal Near-Surface Air Temperature Range"                     ), _TL("Mean diurnal temperature range computed as the average of monthly (tasmax − tasmin)") },
	{ "bio03"   , _TL("%"             ), _TL("Isothermality"                                                       ), _TL("Isothermality: 100 × (bio02 ÷ bio07); compares day–night variability to annual temperature range") },
	{ "bio04"   , _TL("°C"            ), _TL("Temperature Seasonality"                                             ), _TL("Temperature seasonality given by the standard deviation of mean monthly temperatures") },
	{ "bio05"   , _TL("°C"            ), _TL("Mean Daily Maximum Near-Surface Air Temperature of the Warmest Month"), _TL("Highest monthly mean of daily maximum temperatures (tasmax) across the year; indicates peak thermal conditions") },
	{ "bio06"   , _TL("°C"            ), _TL("Mean Daily Minimum Near-Surface Air Temperature of the Coldest Month"), _TL("Lowest monthly mean of daily minimum temperatures (tasmin) across the year; characterizes winter cold intensity") },
	{ "bio07"   , _TL("°C"            ), _TL("Annual Daily Mean Near-Surface Air Temperature Range"                ), _TL("Annual temperature range calculated as bio05 − bio06; measures amplitude between warmest and coldest months") },
	{ "bio08"   , _TL("°C"            ), _TL("Mean Daily Near-Surface Air Temperature of the Wettest Quarter"      ), _TL("Average monthly mean temperature over the wettest 3-month period of the year") },
	{ "bio09"   , _TL("°C"            ), _TL("Mean Daily Near-Surface Air Temperature of the Driest Quarter"       ), _TL("Average monthly mean temperature over the driest 3-month period of the year") },
	{ "bio10"   , _TL("°C"            ), _TL("Mean Daily Mean Near-Surface Air Temperature of the Warmest Quarter" ), _TL("Average monthly mean temperature over the warmest 3-month period of the year") },
	{ "bio11"   , _TL("°C"            ), _TL("Mean Daily Mean Near-Surface Air Temperature of the Quarter"         ), _TL("Average monthly mean temperature over the coldest 3-month period of the year") },
	{ "bio12"   , _TL("kg/m²"         ), _TL("Annual Precipitation"                                                ), _TL("Sum of monthly precipitation totals across the year") },
	{ "bio13"   , _TL("kg/m²"         ), _TL("Precipitation of the Wettest Month"                                  ), _TL("Maximum monthly precipitation total") },
	{ "bio14"   , _TL("kg/m²"         ), _TL("Precipitation of the Driest Month"                                   ), _TL("Minimum monthly precipitation total") },
	{ "bio15"   , _TL("%"             ), _TL("Precipitation Seasonality"                                           ), _TL("Coefficient of variation (100 × SD ÷ mean) of monthly precipitation totals") },
	{ "bio16"   , _TL("kg/m²"         ), _TL("Mean Monthly Precipitation of the Wettest Quarter"                   ), _TL("Average monthly precipitation during the wettest 3-month period of the year") },
	{ "bio17"   , _TL("kg/m²"         ), _TL("Mean Monthly Precipitation of the Driest Quarter"                    ), _TL("Average monthly precipitation during the driest 3-month period of the year") },
	{ "bio18"   , _TL("kg/m²"         ), _TL("Mean Monthly Precipitation of the Warmest Quarter"                   ), _TL("Average monthly precipitation during the warmest 3-month period of the year") },
	{ "bio19"   , _TL("kg/m²"         ), _TL("Mean Monthly Precipitation of the Coldest Quarter"                   ), _TL("Average monthly precipitation during the coldest 3-month period of the year") },
	{ "fcf"     , _TL("days"          ), _TL("Frost Change Frequency"                                              ), _TL("Number of freeze–thaw transitions per year") },
	{ "fgd"     , _TL("day of year"   ), _TL("First Day of the Growing Season (TREELIM)"                           ), _TL("Day of the year number marking the first occurrence of growing season conditions") },
	{ "gdd0"    , _TL("°C"            ), _TL("Growing Degree Days Heat Sum above 0 °C"                             ), _TL("Sum of daily mean temperatures above 0 °C accumulated over the year") },
	{ "gdd5"    , _TL("°C"            ), _TL("Growing Degree Days Heat Sum above 5 °C"                             ), _TL("Sum of daily mean temperatures above 5 °C accumulated over the year") },
	{ "gdd10"   , _TL("°C"            ), _TL("Growing Degree Days Heat Sum above 10 °C"                            ), _TL("Sum of daily mean temperatures above 10 °C accumulated over the year") },
	{ "gdgfgd5" , _TL("day of year"   ), _TL("First Growing Degree Day above 5 °C"                                 ), _TL("Day of the year number of the first occurrence of a daily mean temperature above 5 °C") },
	{ "gdgfgd10", _TL("day of year"   ), _TL("First Growing Degree Day above 10 °C"                                ), _TL("Day of the year number of the first occurrence of a daily mean temperature above 10 °C") },
	{ "gsl"     , _TL("days"          ), _TL("Growing Season Length"                                               ), _TL("Number of days between the first and last occurrence of growing season conditions") },
	{ "gsp"     , _TL("kg/m²"         ), _TL("Accumulated Precipiation Amount over Growing Season Days"            ), _TL("Total precipitation accumulated during the growing season period") },
	{ "gst"     , _TL("°C"            ), _TL("Mean Temperature of Growing Season Days"                             ), _TL("Average daily mean temperature over all growing season days") },
	{ "kg0"     , _TL("category"      ), _TL("Köppen-Geiger Climate Classification"                                ), _TL("Köppen–Geiger climate classification") },
	{ "kg1"     , _TL("category"      ), _TL("Köppen-Geiger Climate Classification (without As/Aw)"                ), _TL("Köppen–Geiger climate classification without As/Aw differenciation") },
	{ "kg2"     , _TL("category"      ), _TL("Köppen-Geiger Climate Classification (Peel et al. 2007)"             ), _TL("Köppen–Geiger climate classification after <a>href=\"https://doi.org/10.5194/hess-11-1633-2007\">Peel et al. 2007</a>") },
	{ "kg3"     , _TL("category"      ), _TL("Wissmann Climate Classification"                                     ), _TL("Climate classification after Wissmann 1939") },
	{ "kg4"     , _TL("category"      ), _TL("Thornthwaite Climate Classification"                                 ), _TL("Climate classification after Thornthwaite 1931") },
	{ "kg5"     , _TL("category"      ), _TL("Troll-Paffen Climate Classification"                                 ), _TL("Climate classification after Troll-Paffen") },
	{ "lgd"     , _TL("day of year"   ), _TL("Last Day of the Growing Season (TREELIM)"                            ), _TL("Day of the year number of the last occurrence of growing season conditions") },
	{ "ngd0"    , _TL("number of days"), _TL("Number of Growing Degree Days above 0 °C"                            ), _TL("Total number of days in a year with mean daily temperature above 0 °C") },
	{ "ngd5"    , _TL("number of days"), _TL("Number of Growing Degree Days above 5 °C"                            ), _TL("Total number of days in a year with mean daily temperature above 5 °C") },
	{ "ngd10"   , _TL("number of days"), _TL("Number of Growing Degree Days above 10 °C"                           ), _TL("Total number of days in a year with mean daily temperature above 10 °C") },
	{ "npp"     , _TL("gC/m²/yr"      ), _TL("Net Primary Production on Land as Carbon Mass Flux"                  ), _TL("Production of carbon' means the production of biomass expressed as the mass of carbon which it contains. Net primary production is the excess of gross primary production (rate of synthesis of biomass from inorganic precursors) by autotrophs ('producers') for example photosynthesis in plants or phytoplankton over the rate at which the autotrophs themselves respire some of this biomass. 'Productivity' means production per unit area. The phrase 'expressed_as' is used in the construction A_expressed_as_B where B is a chemical constituent of A. It means that the quantity indicated by the standard name is calculated solely with respect to the B contained in A neglecting all other chemical constituents of A.") },
	{ "scd"     , _TL("days"          ), _TL("Snow Cover Days"                                                     ), _TL("Number of days per year with snow cover present at the surface") },
	{ "swe"     , _TL("kg/m²/yr"      ), _TL("Snow Water Equivalent"                                               ), _TL("Total water equivalent of snowpack accumulated over the year") },
	{ "glz"     , _TL("m"             ), _TL("Ice Sheet Surface Altitude"                                          ), _TL("Elevation of the ice sheet surface above sea level") },
	{ "orog"    , _TL("m"             ), _TL("Surface Altitude"                                                    ), _TL("The surface called 'surface' means the lower boundary of the atmosphere. Altitude is the (geometric) height above the geoid which is the reference geopotential surface. The geoid is similar to mean sea level.") }
};

//---------------------------------------------------------
bool CHELSA::Get_Variable_Scaling(Datasets Dataset, Vars VarID, double &Scaling, double &Offset)
{
	Scaling = 1.; Offset = 0.;

	switch( VarID )
	{
	case Vars::clt   : Scaling = Dataset == Datasets::daily ? 1.  : 0.01; break;
	case Vars::cmi   : Scaling = 0.1  ;                   break;
	case Vars::hurs  : Scaling = Dataset == Datasets::daily ? 0.1 : 0.01; break;
	case Vars::pet   : Scaling = 0.01 ;                   break;
	case Vars::pr    : Scaling = 0.1  ;                   break;
	case Vars::tas   : Scaling = 0.1  ; Offset = -273.15; break;
	case Vars::tasmax: Scaling = 0.1  ; Offset = -273.15; break;
	case Vars::tasmin: Scaling = 0.1  ; Offset = -273.15; break;
	case Vars::tz    : Scaling = -100.;                   break;
	case Vars::vpd   : Scaling = 0.001;                   break;

	//-----------------------------------------------------
	case Vars::bio01 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Annual Near-Surface Air Temperature
	case Vars::bio02 : Scaling = 0.1  ;                   break; // Mean Diurnal Near-Surface Air Temperature Range
	case Vars::bio03 : Scaling = 0.1  ;                   break; // Isothermality
	case Vars::bio04 : Scaling = 0.001;                   break; // Temperature Seasonality
	case Vars::bio05 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Daily Maximum Near-Surface Air Temperature of the Warmest Month
	case Vars::bio06 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Daily Minimum Near-Surface Air Temperature of the Coldest Month
	case Vars::bio07 : Scaling = 0.1  ;                   break; // Annual Daily Mean Near-Surface Air Temperature Range
	case Vars::bio08 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Daily Near-Surface Air Temperature of the Wettest Quarter
	case Vars::bio09 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Daily Near-Surface Air Temperature of the Driest Quarter
	case Vars::bio10 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Daily Mean Near-Surface Air Temperature of the Warmest Quarter
	case Vars::bio11 : Scaling = 0.1  ; Offset = -273.15; break; // Mean Daily Mean Near-Surface Air Temperature of the Quarter
	case Vars::bio12 : Scaling = 1.   ;                   break; // Annual Precipitation
	case Vars::bio13 : Scaling = 0.1  ;                   break; // Precipitation of the Wettest Month
	case Vars::bio14 : Scaling = 0.1  ;                   break; // Precipitation of the Driest Month
	case Vars::bio15 : Scaling = 0.1  ;                   break; // Precipitation Seasonality
	case Vars::bio16 : Scaling = 0.1  ;                   break; // Mean Monthly Precipitation of the Wettest Quarter
	case Vars::bio17 : Scaling = 0.1  ;                   break; // Mean Monthly Precipitation of the Driest Quarter
	case Vars::bio18 : Scaling = 0.1  ;                   break; // Mean Monthly Precipitation of the Warmest Quarter
	case Vars::bio19 : Scaling = 0.1  ;                   break; // Mean Monthly Precipitation of the Coldest Quarter

	case Vars::gsp   : Scaling = 0.1  ;                   break; // 
	case Vars::gst   : Scaling = 0.1  ; Offset = -273.15; break; // 
	case Vars::swe   : Scaling = 0.1  ;                   break; // 
	}

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CCHELSA_Global::CCHELSA_Global(void)
{
	Set_Name		(_TL("CHELSA - Global Climate Data"));

	Set_Author		("O.Conrad (c) 2025");

	Set_Description	(_TW(
		"Download, project and optionally resample climate data provided by "
		"<a href=\"https://www.chelsa-climate.org/\">CHELSA</a> "
		"(<i>Climatologies at High resolution for the Earth's Land Surface Areas</i>) "
		"for the area of your interest. "
	));

	Add_Description(CSG_String::Format("<hr><h4>%s</h4>", _TL("Provided Variables")), false);

	for(int j=0; j<=(int)CHELSA::Datasets::T21k_bioclim; j++)
	{
		Add_Description(CSG_String::Format("<h5>%s</h5><table border=\"1\"><tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>", CHELSA::Get_Dataset_Name((CHELSA::Datasets)j).c_str(), _TL("Key"), _TL("Name"), _TL("Unit"), _TL("Description")), false);

		for(int i=0; i<CHELSA::Get_Variable_Count((CHELSA::Datasets)j); i++)
		{
			struct CHELSA::SVariable &Info = CHELSA::Get_Variable_Info((CHELSA::Datasets)j, i);

			Add_Description(CSG_String::Format("<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>",
				Info.key.c_str(), Info.name.c_str(), Info.unit.c_str(), Info.description.c_str()
			), false);
		}

		Add_Description("</table>", false);
	}

	Add_Reference("https://www.chelsa-climate.org/", SG_T("CHELSA Website"));

	Add_Reference(SG_T("Karger, D. N., Conrad, O., Böhner, J., Kawohl, T., Kreft, H., Soria-Auza, R. W., Zimmermann, N. E., Linder, H. P., Kessler, M."), "2021",
		"Climatologies at high resolution for the earth's land surface areas",
		"EnviDat.", SG_T("https://www.doi.org/10.16904/envidat.228"), SG_T("doi:10.16904/envidat.228")
	);

	//-----------------------------------------------------
	if( has_CMD() == false )
	{
		Parameters.Add_Grid_List(""      , "GRIDS"  , _TL("Grids" ), _TL(""), PARAMETER_OUTPUT);
		Parameters.Add_Choice   (""      , "OUTPUT" , _TL("Output"), _TL(""), CSG_String::Format("%s|%s", _TL("files"       ), _TL("memory"         )), 1);
		Parameters.Add_Choice   ("OUTPUT", "MEMTYPE", _TL("Type"  ), _TL(""), CSG_String::Format("%s|%s", _TL("single grids"), _TL("grid collection")), 1);
	}

	Parameters.Add_FilePath("OUTPUT", "FOLDER", _TL("Folder"), _TL(""), NULL, NULL, true, true);
	Parameters.Add_Choice  ("FOLDER", "FORMAT", _TL("Format"), _TL(""), CSG_String::Format("GeoTIFF (*.tif)|%s (*.sg-grd-z)", _TL("SAGA Compressed Grid File")), 1);

	//-----------------------------------------------------
	Parameters.Add_Choice("", "DATASET", _TL("Data Set"), _TL(""), CSG_String::Format("%s|%s|%s|%s|%s|%s",
		_TL("daily"                      ),
		_TL("monthly"                    ),
		_TL("climatology"                ),
		_TL("bioclim"                    ),
		_TL("TraCE21k-cenntenial"        ),
		_TL("TraCE21k-cenntenial-bioclim")
		), 2
	);

	Parameters.Add_Date  ("DATASET", "DAILY_FROM"       , _TL("From Day"    ), _TL(""), CSG_DateTime("1979-01-01").Get_JDN());
	Parameters.Add_Date  ("DATASET", "DAILY_TO"         , _TL("To Day"      ), _TL(""), CSG_DateTime("2024-12-31").Get_JDN());

	Parameters.Add_Int   ("DATASET", "YEAR_FROM"        , _TL("From Year"   ), _TL(""), 1979, 1979, true, 2021, true);
	Parameters.Add_Int   ("DATASET", "YEAR_TO"          , _TL("To Year"     ), _TL(""), 2021, 1979, true, 2021, true);

	Parameters.Add_Choice("DATASET", "PERIOD"           , _TL("Period"      ), _TL(""), "1981-2010|2011-2040|2041-2070|2071-2100", 0);
	Parameters.Add_Choice("PERIOD" , "MODEL"            , _TL("Model"       ), _TL(""), "GFDL-ESM4|IPSL-CM6A-LR|MPI-ESM1-2-HR|MRI-ESM2-0|UKESM1-0-LL", 2);
	Parameters.Add_Choice("PERIOD" , "SCENARIO"         , _TL("Scenario"    ), _TL(""), "ssp126|ssp370|ssp585", 2);

	Parameters.Add_Int   ("DATASET", "CENTURY"          , _TL("Century"     ), _TL(""), -200, -200, true, 20, true);
	Parameters.Add_Int   ("DATASET", "CENTURY_FROM"     , _TL("From Century"), _TL(""), -200, -200, true, 20, true);
	Parameters.Add_Int   ("DATASET", "CENTURY_TO"       , _TL("To Century"  ), _TL(""),   20, -200, true, 20, true);
	Parameters.Add_Int   ("DATASET", "CENTURY_STEP"     , _TL("Step"        ), _TL(""),    1,    1, true, 55, true);

	Parameters.Add_Choice("DATASET", "VAR_DAILY"        , _TL("Variable"    ), _TL(""), CHELSA::Get_Variable_Choice(CHELSA::Datasets::daily       ));
	Parameters.Add_Choice("DATASET", "VAR_MONTHLY"      , _TL("Variable"    ), _TL(""), CHELSA::Get_Variable_Choice(CHELSA::Datasets::monthly     ));
	Parameters.Add_Choice("DATASET", "VAR_CLIMATE"      , _TL("Variable"    ), _TL(""), CHELSA::Get_Variable_Choice(CHELSA::Datasets::climatology ));
	Parameters.Add_Choice("DATASET", "VAR_BIOCLIM"      , _TL("Variable"    ), _TL(""), CHELSA::Get_Variable_Choice(CHELSA::Datasets::bioclim     ));
	Parameters.Add_Choice("DATASET", "VAR_T21K"         , _TL("Variable"    ), _TL(""), CHELSA::Get_Variable_Choice(CHELSA::Datasets::T21k        ));
	Parameters.Add_Choice("DATASET", "VAR_T21K_BIOCLIM" , _TL("Variable"    ), _TL(""), CHELSA::Get_Variable_Choice(CHELSA::Datasets::T21k_bioclim));

	//-----------------------------------------------------
	Parameters.Add_Choice("", "EXTENT"     , _TL("Extent"), _TL(""), CSG_String::Format("%s|%s|%s|%s",
		_TL("user defined"      ),
		_TL("shapes extent"     ),
		_TL("grid system extent"),
		_TL("grid system"       )), 0
	);

	Parameters.Add_Grid_System("EXTENT"     , "GRID_SYSTEM", _TL("Grid System"), _TL(""));
	Parameters.Add_Grid       ("GRID_SYSTEM", "GRID"       , _TL("Grid"       ), _TL(""), PARAMETER_INPUT);
	Parameters.Add_Shapes     ("EXTENT"     , "SHAPES"     , _TL("Shapes"     ), _TL(""), PARAMETER_INPUT);
	Parameters.Add_Double     ("EXTENT"     , "XMIN"       , _TL("West"       ), _TL(""),  260000.);
	Parameters.Add_Double     ("EXTENT"     , "XMAX"       , _TL("East"       ), _TL(""),  941000.);
	Parameters.Add_Double     ("EXTENT"     , "YMIN"       , _TL("South"      ), _TL(""), 5215000.);
	Parameters.Add_Double     ("EXTENT"     , "YMAX"       , _TL("North"      ), _TL(""), 6121000.);
	Parameters.Add_Int        ("EXTENT"     , "NX"         , _TL("Columns"    ), _TL(""),     682, 1, true);
	Parameters.Add_Int        ("EXTENT"     , "NY"         , _TL("Rows"       ), _TL(""),     907, 1, true);
	Parameters.Add_Double     ("EXTENT"     , "BUFFER"     , _TL("Buffer"     ), _TL("add buffer (map units) to extent"), 0., 0., true);

	Parameters.Add_Choice     (""           , "CELLSIZEDEF", _TL("Take Cellsize"), _TL(""), CSG_String::Format("%s|%s",
		_TL("data type resolution"),
		_TL("user defined")), 0
	);

	Parameters.Add_Double("CELLSIZEDEF",
		"CELLSIZE"   , _TL("Cellsize"),
		_TL(""),
		1000., 0.0001, true
	);

	m_CRS.Create(Parameters); Parameters.Set_Parameter("CRS_STRING", "epsg:25832"); m_CRS.On_Parameter_Changed(&Parameters, Parameters("CRS_STRING"));
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CCHELSA_Global::On_Before_Execution(void)
{
	m_CRS.Activate_GUI();

	return( CSG_Tool::On_Before_Execution() );
}

//---------------------------------------------------------
bool CCHELSA_Global::On_After_Execution(void)
{
	m_CRS.Deactivate_GUI();

	return( CSG_Tool::On_After_Execution() );
}

//---------------------------------------------------------
int CCHELSA_Global::On_Parameter_Changed(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	if( pParameter->Cmp_Identifier("CELLSIZE") || (pParameter->Get_Parent() && pParameter->Get_Parent()->Cmp_Identifier("EXTENT")) )
	{
		CSG_Projection Projection((*pParameters)["CRS_STRING"].asString());

		double Cellsize = (*pParameters)["CELLSIZEDEF"].asInt() == 1 ? (*pParameters)["CELLSIZE"].asDouble():Projection.is_Projection() ? 1000. : 1. / 120.;

		double xMin = (*pParameters)["XMIN"].asDouble();
		double yMin = (*pParameters)["YMIN"].asDouble();
		int    NX   = (*pParameters)["NX"  ].asInt   ();
		int    NY   = (*pParameters)["NY"  ].asInt   ();

		if( pParameter->Cmp_Identifier("CELLSIZE") )
		{
			NX = 1 + (int)(((*pParameters)["XMAX"].asDouble() - xMin) / Cellsize);
			NY = 1 + (int)(((*pParameters)["YMAX"].asDouble() - yMin) / Cellsize);
		}

		if( pParameter->Cmp_Identifier("XMAX") ) { xMin = pParameter->asDouble() - Cellsize * NX; }
		if( pParameter->Cmp_Identifier("YMAX") ) { yMin = pParameter->asDouble() - Cellsize * NY; }

		CSG_Grid_System System(Cellsize, xMin, yMin, NX, NY);

		if( System.is_Valid() )
		{
			(*pParameters)["XMIN"].Set_Value(System.Get_XMin());
			(*pParameters)["XMAX"].Set_Value(System.Get_XMax());
			(*pParameters)["YMIN"].Set_Value(System.Get_YMin());
			(*pParameters)["YMAX"].Set_Value(System.Get_YMax());
			(*pParameters)["NX"  ].Set_Value(System.Get_NX  ());
			(*pParameters)["NY"  ].Set_Value(System.Get_NY  ());
		}
	}

	m_CRS.On_Parameter_Changed(pParameters, pParameter);

	return( CSG_Tool::On_Parameter_Changed(pParameters, pParameter) );
}

//---------------------------------------------------------
int CCHELSA_Global::On_Parameters_Enable(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	if( pParameter->Cmp_Identifier("OUTPUT") )
	{
		pParameters->Set_Enabled("FOLDER"           , pParameter->asInt() == 0);
		pParameters->Set_Enabled("MEMTYPE"          , pParameter->asInt() == 1 && (*pParameters)["DATASET"].asInt() != 3);
	}

	if( pParameter->Cmp_Identifier("DATASET") )
	{
		pParameters->Set_Enabled("DAILY_FROM"       , pParameter->asInt() == 0); // daily
		pParameters->Set_Enabled("DAILY_TO"         , pParameter->asInt() == 0); // daily
		pParameters->Set_Enabled("VAR_DAILY"        , pParameter->asInt() == 0); // daily
		pParameters->Set_Enabled("YEAR_FROM"        , pParameter->asInt() == 1); // monthly
		pParameters->Set_Enabled("YEAR_TO"          , pParameter->asInt() == 1); // monthly
		pParameters->Set_Enabled("VAR_MONTHLY"      , pParameter->asInt() == 1); // monthly
		pParameters->Set_Enabled("VAR_CLIMATE"      , pParameter->asInt() == 2); // climatologies
		pParameters->Set_Enabled("PERIOD"           , pParameter->asInt() == 2   // climatologies
		                                           || pParameter->asInt() == 3); // bioclim
		pParameters->Set_Enabled("VAR_BIOCLIM"      , pParameter->asInt() == 3); // bioclim
		pParameters->Set_Enabled("VAR_T21K"         , pParameter->asInt() == 4); // TraCE21k
		pParameters->Set_Enabled("CENTURY"          , pParameter->asInt() == 4); // TraCE21k
		pParameters->Set_Enabled("CENTURY_FROM"     , pParameter->asInt() == 5); // TraCE21k bioclim
		pParameters->Set_Enabled("CENTURY_TO"       , pParameter->asInt() == 5); // TraCE21k bioclim
		pParameters->Set_Enabled("CENTURY_STEP"     , pParameter->asInt() == 5); // TraCE21k bioclim
		pParameters->Set_Enabled("VAR_T21K_BIOCLIM" , pParameter->asInt() == 5); // TraCE21k bioclim

		pParameters->Set_Enabled("MEMTYPE"          , pParameter->asInt() != 3 && (*pParameters)["OUTPUT"].asInt() == 1); // bioclim (climatology) output always as single grids (not for files output)
	}

	if( pParameter->Cmp_Identifier("PERIOD") )
	{
		pParameters->Set_Enabled("MODEL"            , pParameter->asInt() > 0); // future projection
		pParameters->Set_Enabled("SCENARIO"         , pParameter->asInt() > 0); // future projection
	}

	//-----------------------------------------------------
	if( pParameter->Cmp_Identifier("EXTENT") )
	{
		pParameters->Set_Enabled("XMIN"             , pParameter->asInt() == 0);
		pParameters->Set_Enabled("XMAX"             , pParameter->asInt() == 0);
		pParameters->Set_Enabled("YMIN"             , pParameter->asInt() == 0);
		pParameters->Set_Enabled("YMAX"             , pParameter->asInt() == 0);
		pParameters->Set_Enabled("NX"               , pParameter->asInt() == 0);
		pParameters->Set_Enabled("NY"               , pParameter->asInt() == 0);
		pParameters->Set_Enabled("SHAPES"           , pParameter->asInt() == 1);
		pParameters->Set_Enabled("GRID_SYSTEM"      , pParameter->asInt() >= 2);
		pParameters->Set_Enabled("CELLSIZEDEF"      , pParameter->asInt() != 3);
		pParameters->Set_Enabled("BUFFER"           , pParameter->asInt() == 1 || pParameter->asInt() == 2);
	}

	if( pParameter->Cmp_Identifier("CELLSIZEDEF") )
	{
		pParameters->Set_Enabled("CELLSIZE"         , pParameter->asInt() == 1); // user defined
	}

	if( pParameters->Get_Name().Cmp(Get_Name()) == 0 )
	{
		CSG_Data_Object *pObject =
			(*pParameters)["EXTENT"].asInt() == 1 ? (*pParameters)["SHAPES"].asDataObject() :
			(*pParameters)["EXTENT"].asInt() >= 2 ? (*pParameters)["GRID"  ].asDataObject() : NULL;

		pParameters->Set_Enabled("CRS_PICKER", !SG_Get_Data_Manager().Exists(pObject) || !pObject->Get_Projection().is_Okay());
	}

	return( CSG_Tool::On_Parameters_Enable(pParameters, pParameter) );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CCHELSA_Global::On_Execute(void)
{
	bool bResult = false;

	if( Initialize() )
	{
		switch( Parameters["DATASET"].asInt() )
		{
		case  0: bResult = Get_Daily       (); break; // daily
		case  1: bResult = Get_Monthly     (); break; // monthly
		case  2: bResult = Get_Climatology (); break; // climatology
		case  3: bResult = Get_BioClim     (); break; // bioclim
		case  4: bResult = Get_T21k        (); break; // TraCE21k
		case  5: bResult = Get_T21k_BioClim(); break; // TraCE21k bioclim
		}

		Finalize();
	}

	return( bResult && (m_pGrids == NULL || m_pGrids->Get_Grid_Count() > 0 || (m_pCollection && m_pCollection->Get_Grid_Count() > 0)) );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CCHELSA_Global::Get_Daily(void)
{
	// https://os.unil.cloud.switch.ch/chelsa02/chelsa/global/daily/tas/1980/CHELSA_tas_01_01_1980_V.2.1.tif

	int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::daily, Parameters["VAR_DAILY"].asInt());

	CSG_String file, path, key(CHELSA::Get_Variable_Info(id).key);

	if( m_pCollection )
	{
		m_Attributes.Create();
		m_Attributes.Add_Field("id"      , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("date"    , SG_DATATYPE_Date  );
		m_Attributes.Add_Field("variable", SG_DATATYPE_String);
		m_Attributes.Add_Record();
		m_Attributes[0].Set_Value("variable", key);
		m_pCollection->Create(m_Attributes); m_pCollection->Set_Z_Name_Field(1);
		m_pCollection->Fmt_Name("CHELSA Daily [%s]", key.c_str());
		m_pCollection->Set_Description(CHELSA::Get_Variable_Description(id));
		m_pCollection->Set_Unit       (CHELSA::Get_Variable_Info(id).unit);
	}

	path.Printf("%s/daily/%s/%%04d/", CHELSA::Get_Server_Root_Global(), key.c_str());
	file.Printf("CHELSA_%s_%%02d_%%02d_%%04d_V.2.1.tif", key.c_str());

	CSG_DateTime dates[2] = { Parameters["DAILY_FROM"].asDate()->Get_Date(), Parameters["DAILY_TO"].asDate()->Get_Date() };
	if( dates[0] > dates[1] ) { CSG_DateTime date(dates[0]); dates[0] = dates[1]; dates[1] = date; }
	if (dates[0] < CSG_DateTime("1979-01-02") ) { dates[0].Set("1979-01-02"); }
	if (dates[1] > CSG_DateTime("2025-08-29") ) { dates[1].Set("2025-08-29"); }

	int i = 0, n = 1 + (dates[1] - dates[0]).Get_Days();

	for(CSG_DateTime date=dates[0]; date<=dates[1] && Set_Progress(i++, n); date+=CSG_TimeSpan::Day())
	{
		Process_Set_Text("%04d-%02d-%02d [%d/%d]", date.Get_Year(), date.Get_Month(), date.Get_Day(), i, n);

		if( m_Attributes.Get_Count() )
		{
			m_Attributes[0].Set_Value(0, i);
			m_Attributes[0].Set_Value(1, date.Format_ISODate());
		}

		Get_Variable((int)CHELSA::Datasets::daily, id, CSG_String::Format(path.c_str(), date.Get_Year()), CSG_String::Format(file.c_str(), date.Get_Day(), date.Get_Month(), date.Get_Year()));
	}

	//-----------------------------------------------------
	if( m_pCollection )
	{
		double Scaling, Offset; CHELSA::Get_Variable_Scaling(CHELSA::Datasets::daily, (CHELSA::Vars)id, Scaling, Offset);

		m_pCollection->Set_Scaling(Scaling, Offset);
	}

	return( true );
}

//---------------------------------------------------------
bool CCHELSA_Global::Get_Monthly(void)
{
	// https://os.unil.cloud.switch.ch/chelsa02/chelsa/global/monthly/tas/1979/CHELSA_tas_02_1979_V.2.1.tif

	int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::monthly, Parameters["VAR_MONTHLY"].asInt());

	CSG_String file, path, key(CHELSA::Get_Variable_Info(id).key);

	if( m_pCollection )
	{
		m_Attributes.Create();
		m_Attributes.Add_Field("id"      , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("year"    , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("month"   , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("name"    , SG_DATATYPE_String);
		m_Attributes.Add_Field("variable", SG_DATATYPE_String);
		m_Attributes.Add_Record();
		m_Attributes[0].Set_Value("variable", key);
		m_pCollection->Create(m_Attributes); m_pCollection->Set_Z_Name_Field(3);
		m_pCollection->Fmt_Name("CHELSA Monthly [%s]", key.c_str());
		m_pCollection->Set_Description(CHELSA::Get_Variable_Description(id));
		m_pCollection->Set_Unit       (CHELSA::Get_Variable_Info(id).unit);
	}

	path.Printf("%s/monthly/%s/%%04d/", CHELSA::Get_Server_Root_Global(), key.c_str());
	file.Printf("CHELSA_%s_%%02d_%%04d_V.2.1.tif", key.c_str());

	int years[2] = { Parameters["YEAR_FROM"].asInt(), Parameters["YEAR_TO"].asInt() };
	if( years[0] > years[1] ) { int year = years[0]; years[0] = years[1]; years[1] = year; }
	if( years[0] < 1979 ) { years[0] = 1979; }
	if( years[1] > 2021 ) { years[1] = 2021; }

	int n = 12 * (1 + years[1] - years[0]);

	for(int year=years[0], i=0; year<=years[1] && Process_Get_Okay(); year++)
	{
		for(int month=1; month<=12 && Set_Progress(i++, n); month++)
		{
			Process_Set_Text("%04d-%02d [%d/%d]", year, month, i, n);

			if( m_Attributes.Get_Count() )
			{
				m_Attributes[0].Set_Value(0, i);
				m_Attributes[0].Set_Value(1, year);
				m_Attributes[0].Set_Value(2, month);
				m_Attributes[0].Set_Value(3, CSG_String::Format("%04d, %s", year, CSG_DateTime::Get_MonthName((CSG_DateTime::Month)(month - 1)).c_str()));
			}

			Get_Variable((int)CHELSA::Datasets::monthly, id, CSG_String::Format(path.c_str(), year), CSG_String::Format(file.c_str(), month, year));
		}
	}

	//-----------------------------------------------------
	if( m_pCollection )
	{
		double Scaling, Offset; CHELSA::Get_Variable_Scaling(CHELSA::Datasets::monthly, (CHELSA::Vars)id, Scaling, Offset);

		m_pCollection->Set_Scaling(Scaling, Offset);
	}

	return( true );
}

//---------------------------------------------------------
bool CCHELSA_Global::Get_Climatology(void)
{
	// https://os.unil.cloud.switch.ch/chelsa02/chelsa/global/climatologies/pr/1981-2010/CHELSA_pr_01_1981-2010_V.2.1.tif
	// https://os.unil.cloud.switch.ch/chelsa02/chelsa/global/climatologies/pr/2011-2040/MPI-ESM1-2-HR/ssp585/CHELSA_mpi-esm1-2-hr_r1i1p1f1_w5e5_ssp585_pr_01_2011-2040_V.2.1.tif

	int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::climatology, Parameters["VAR_CLIMATE"].asInt());

	CSG_String file, path, period(Parameters["PERIOD"].asString()), key(CHELSA::Get_Variable_Info(id).key);

	if( m_pCollection )
	{
		m_Attributes.Create();
		m_Attributes.Add_Field("month"   , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("name"    , SG_DATATYPE_String);
		m_Attributes.Add_Field("variable", SG_DATATYPE_String);
		m_Attributes.Add_Record();
		m_Attributes[0].Set_Value("variable", key);
		m_pCollection->Create(m_Attributes); m_pCollection->Set_Z_Name_Field(1);
		m_pCollection->Fmt_Name("CHELSA Climatology %s [%s]", period.c_str(), key.c_str());
		m_pCollection->Set_Description(CHELSA::Get_Variable_Description(id));
		m_pCollection->Set_Unit       (CHELSA::Get_Variable_Info(id).unit);
	}

	if( Parameters["PERIOD"].asInt() == 0 ) // current
	{
		path.Printf("%s/climatologies/%s/%s/", CHELSA::Get_Server_Root_Global(), key.c_str(), period.c_str());
		file.Printf("CHELSA_%s_%%02d_%s_V.2.1.tif", key.c_str(), period.c_str());
	}
	else // future projection
	{
		CSG_String model(Parameters["MODEL"].asString()), scenario(Parameters["SCENARIO"].asString());

		path.Printf("%s/climatologies/%s/%s/%s/%s/", CHELSA::Get_Server_Root_Global(), key.c_str(), period.c_str(), model.c_str(), scenario.c_str());
		file.Printf("CHELSA_%s_r1i1p1f1_w5e5_%s_%s_%%02d_%s_V.2.1.tif", model.Make_Lower().c_str(), scenario.c_str(), key.c_str(), period.c_str());
	}

	//-----------------------------------------------------
	for(int month=1; month<=12 && Set_Progress(month, 12); month++)
	{
		if( m_Attributes.Get_Count() )
		{
			m_Attributes[0].Set_Value(0, month);
			m_Attributes[0].Set_Value(1, CSG_DateTime::Get_MonthName((CSG_DateTime::Month)(month - 1)));
		}

		Get_Variable((int)CHELSA::Datasets::climatology, id, path, CSG_String::Format(file.c_str(), month));
	}

	//-----------------------------------------------------
	if( m_pCollection )
	{
		double Scaling, Offset; CHELSA::Get_Variable_Scaling(CHELSA::Datasets::climatology, (CHELSA::Vars)id, Scaling, Offset);

		m_pCollection->Set_Scaling(Scaling, Offset);
	}

	return( true );
}

//---------------------------------------------------------
bool CCHELSA_Global::Get_BioClim(void)
{
	// https://os.unil.cloud.switch.ch/chelsa02/chelsa/global/bioclim/bio01/1981-2010/CHELSA_bio01_1981-2010_V.2.1.tif
	// https://os.unil.cloud.switch.ch/chelsa02/chelsa/global/bioclim/bio01/2011-2040/MPI-ESM1-2-HR/ssp585/CHELSA_mpi-esm1-2-hr_ssp585_bio01_2011-2040_V.2.1.tif

	CSG_String path, file, period(Parameters["PERIOD"].asString());

	if( Parameters["PERIOD"].asInt() == 0 ) // current
	{
		path.Printf("%s/bioclim/%%s/%s/", CHELSA::Get_Server_Root_Global(), period.c_str());
		file.Printf("CHELSA_%%s_%s_V.2.1.tif", period.c_str());
	}
	else // future projection
	{
		CSG_String model(Parameters["MODEL"].asString()), scenario(Parameters["SCENARIO"].asString());

		path.Printf("%s/bioclim/%%s/%s/%s/%s/", CHELSA::Get_Server_Root_Global(), period.c_str(), model.c_str(), scenario.c_str());
		file.Printf("CHELSA_%s_%s_%%s_%s_V.2.1.tif", model.Make_Lower().c_str(), scenario.c_str(), period.c_str());
	}

	//-----------------------------------------------------
	int Choice = Parameters["VAR_BIOCLIM"].asInt();

	if( Choice < CHELSA::Get_Variable_Count(CHELSA::Datasets::bioclim) ) // single variable
	{
		int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::bioclim, Choice);

		const CSG_String &key = CHELSA::Get_Variable_Info(id).key;

		CSG_Grid *pVariable = Get_Variable((int)CHELSA::Datasets::bioclim, id, CSG_String::Format(path.c_str(), key.c_str()), CSG_String::Format(file.c_str(), key.c_str()));

		Set_Classification(id, pVariable);
	}
	else
	{
		for(int i=0; i<CHELSA::Get_Variable_Count(CHELSA::Datasets::bioclim) && Set_Progress(i, CHELSA::Get_Variable_Count(CHELSA::Datasets::bioclim)); i++)
		{
			int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::bioclim, i);

			const CSG_String &key = CHELSA::Get_Variable_Info(id).key;

			Process_Set_Text("%s [%d/%d]", key.c_str(), i + 1, CHELSA::Get_Variable_Count(CHELSA::Datasets::bioclim));

			CSG_Grid *pVariable = Get_Variable((int)CHELSA::Datasets::bioclim, id, CSG_String::Format(path.c_str(), key.c_str()), CSG_String::Format(file.c_str(), key.c_str()));

			Set_Classification(id, pVariable);
		}
	}

	return( true );
}

//---------------------------------------------------------
bool CCHELSA_Global::Get_T21k(void)
{
	// https://os.zhdk.cloud.switch.ch/chelsa01/chelsa_trace21k/global/centennial/pr/CHELSA_TraCE21k_pr_02_0000_V.1.0.tif

	int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::T21k, Parameters["VAR_T21K"].asInt()), century = Parameters["CENTURY"].asInt();

	CSG_String file, path, key(CHELSA::Get_Variable_Info(id).key);

	if( m_pCollection )
	{
		m_Attributes.Create();
		m_Attributes.Add_Field("month"   , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("name"    , SG_DATATYPE_String);
		m_Attributes.Add_Field("variable", SG_DATATYPE_String);
		m_Attributes.Add_Field("century" , SG_DATATYPE_Int   );
		m_Attributes.Add_Record();
		m_Attributes[0].Set_Value("variable", key); m_Attributes[0].Set_Value("century", century);
		m_pCollection->Create(m_Attributes); m_pCollection->Set_Z_Name_Field(1);
		m_pCollection->Fmt_Name("CHELSA TraCE21k [%s] %d.ct", key.c_str(), century);
		m_pCollection->Set_Description(CHELSA::Get_Variable_Description(id));
		m_pCollection->Set_Unit       (CHELSA::Get_Variable_Info(id).unit);
	}

	path.Printf("/vsicurl/https://os.zhdk.cloud.switch.ch/chelsa01/chelsa_trace21k/global/centennial/%s/", key.c_str());
	file.Printf("CHELSA_TraCE21k_%s_%%02d_%c%03d_V.1.0.tif", key.c_str(), century < 0 ? '-' : '0', abs(century));

	//-----------------------------------------------------
	for(int month=1; month<=12 && Set_Progress(month, 12); month++)
	{
		if( m_Attributes.Get_Count() )
		{
			m_Attributes[0].Set_Value(0, month);
			m_Attributes[0].Set_Value(1, CSG_DateTime::Get_MonthName((CSG_DateTime::Month)(month - 1)));
		}

		Get_Variable((int)CHELSA::Datasets::T21k, id, path, CSG_String::Format(file.c_str(), month));
	}

	//-----------------------------------------------------
	if( m_pCollection )
	{
		double Scaling, Offset; CHELSA::Get_Variable_Scaling(CHELSA::Datasets::T21k, (CHELSA::Vars)id, Scaling, Offset);

		m_pCollection->Set_Scaling(Scaling, Offset);
	}

	return( true );
}

//---------------------------------------------------------
bool CCHELSA_Global::Get_T21k_BioClim(void)
{
	// https://os.zhdk.cloud.switch.ch/chelsa01/chelsa_trace21k/global/bioclim/bio01/CHELSA_TraCE21k_bio01_0000_V.1.0.tif

	int id = CHELSA::Get_Variable_ID(CHELSA::Datasets::T21k_bioclim, Parameters["VAR_T21K_BIOCLIM"].asInt()), step = Parameters["CENTURY_STEP"].asInt();

	CSG_String file, path, key(CHELSA::Get_Variable_Info(id).key);

	if( m_pCollection )
	{
		m_Attributes.Create();
		m_Attributes.Add_Field("id"      , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("century" , SG_DATATYPE_Int   );
		m_Attributes.Add_Field("variable", SG_DATATYPE_String);
		m_Attributes.Add_Record();
		m_Attributes[0].Set_Value("variable", key);
		m_pCollection->Create(m_Attributes); m_pCollection->Set_Z_Name_Field(1);
		m_pCollection->Fmt_Name("CHELSA TraCE21k [%s]", key.c_str());
		m_pCollection->Set_Description(CHELSA::Get_Variable_Description(id));
		m_pCollection->Set_Unit       (CHELSA::Get_Variable_Info(id).unit);
	}

	path.Printf("/vsicurl/https://os.zhdk.cloud.switch.ch/chelsa01/chelsa_trace21k/global/bioclim/%s/", key.c_str());
	file.Printf("CHELSA_TraCE21k_%s_%%c%%03d_V.1.0.tif", key.c_str());

	int centuries[2] = { Parameters["CENTURY_FROM"].asInt(), Parameters["CENTURY_TO"].asInt() };
	if( centuries[0] > centuries[1] ) { int century = centuries[0]; centuries[0] = centuries[1]; centuries[1] = century; }
	if (centuries[0] < -200 ) { centuries[0] = -200; }
	if (centuries[1] >   20 ) { centuries[1] =   20; }

	int i = 0, n = (1 + centuries[1] - centuries[0]) / step;

	for(int century=centuries[0]; century<=centuries[1] && Set_Progress(i++, n); century+=step)
	{
		Process_Set_Text("century %d [%d/%d]", century, i, n);

		if( m_Attributes.Get_Count() )
		{
			m_Attributes[0].Set_Value(0, i);
			m_Attributes[0].Set_Value(1, century);
		}

		Get_Variable((int)CHELSA::Datasets::T21k_bioclim, id, path, CSG_String::Format(file.c_str(), century < 0 ? '-' : '0', abs(century)));
	}

	//-----------------------------------------------------
	if( m_pCollection )
	{
		double Scaling, Offset; CHELSA::Get_Variable_Scaling(CHELSA::Datasets::T21k_bioclim, (CHELSA::Vars)id, Scaling, Offset);

		m_pCollection->Set_Scaling(Scaling, Offset);
	}

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CSG_Grid * CCHELSA_Global::Get_Variable(int Dataset, int VarID, const CSG_String &Path, const CSG_String &File)
{
	CSG_Grid *pVariable = NULL;

	if( m_pImport && VarID >= 0 && VarID < CHELSA::Vars::count )
	{
		SG_UI_ProgressAndMsg_Lock(true);

		m_pImport->Get_Parameter("GRIDS")->asGridList()->Del_Items();
		m_pImport->Set_Parameter("FILES", Path + File);

		if( m_pImport->Execute() == false )
		{
			SG_UI_ProgressAndMsg_Lock(false);
			Message_Fmt("\n%s: \"%s%s\"", _TL("failed to load remote file"), Path.AfterFirst('/').AfterFirst('/').c_str(), File.c_str());
			SG_UI_ProgressAndMsg_Lock(true);
		}
		else
		{
			pVariable = m_pImport->Get_Parameter("GRIDS")->asGridList()->Get_Grid(0);

			if( m_pProject )
			{
				if( pVariable->Get_Projection().is_Okay() == false )
				{
					pVariable->Get_Projection().Set_GCS_WGS84();
				}

				CSG_Grid *pProjected = SG_Create_Grid();

				m_pProject->Set_Callback(false);
				m_pProject->Set_Parameter("SOURCE", pVariable);
				m_pProject->Set_Parameter("GRID"  , pProjected);
				m_pProject->Set_Parameter("RESAMPLING", CHELSA::is_Category(VarID) ? 0 : 3); // B-Spline
				m_pProject->Set_Callback(true);

				if( m_pProject->Execute() )
				{
					delete(pVariable); pVariable = pProjected;
				}
			}

			//---------------------------------------------
			if( pVariable )
			{
				if( m_pCollection )
				{
					if( m_Attributes.Get_Field_Count() && m_Attributes.Get_Count() )
					{
						m_pCollection->Add_Grid(m_Attributes[0], pVariable, true);
					}
					else
					{
						m_pCollection->Add_Grid(m_pCollection->Get_Grid_Count() + 1, pVariable, true);
					}
				}
				else
				{
					double Scaling, Offset; CHELSA::Get_Variable_Scaling((CHELSA::Datasets)Dataset, (CHELSA::Vars)VarID, Scaling, Offset);

					pVariable->Set_Scaling    (Scaling, Offset);
					pVariable->Set_Unit       (CHELSA::Get_Variable_Info(VarID).unit);
					pVariable->Set_Description(CHELSA::Get_Variable_Description(VarID));

					if( m_pGrids )
					{
						m_pGrids->Add_Item(pVariable);
					}
					else
					{
						if( SG_Dir_Exists(m_Folder) )
						{
							pVariable->Save(SG_File_Make_Path(m_Folder, SG_File_Get_Name(File, false), m_Format));
						}

						delete(pVariable); pVariable = NULL;
					}
				}
			}
		}

		SG_UI_ProgressAndMsg_Lock(false);
	}

	return( pVariable );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CCHELSA_Global::Initialize(void)
{
	m_pGrids = NULL; m_pCollection = NULL; m_Folder.Clear();

	if( Parameters("OUTPUT") == NULL || Parameters["OUTPUT"].asInt() == 0 ) // files
	{
		m_Folder = Parameters["FOLDER"].asString();
		m_Format = Parameters["FORMAT"].asInt() == 0 ? "tif" : "sg-grd-z";

		if( !SG_Dir_Exists(m_Folder) )
		{
			Error_Fmt("%s\n\"%s\"", _TL("Target folder does not exist!"), m_Folder.c_str());

			return( false );
		}
	}
	else // memory
	{
		m_pGrids = Parameters["GRIDS"].asGridList(); m_pGrids->Del_Items();

		if( Parameters["MEMTYPE"].is_Enabled() && Parameters["MEMTYPE"].asInt() == 1 )
		{
			m_pGrids->Add_Item(m_pCollection = SG_Create_Grids());
		}
	}

	//-----------------------------------------------------
	CSG_Rect Extent, Extent_GCS; CSG_Projection Projection;

	double Cellsize = Parameters("CELLSIZEDEF")->asInt() == 1 ? Parameters("CELLSIZE")->asDouble() : 1000.;

	switch( Parameters("EXTENT")->asInt() )
	{
	default: // user defined
		Extent.Create(
			Parameters("XMIN")->asDouble(), Parameters("YMIN")->asDouble(),
			Parameters("XMAX")->asDouble(), Parameters("YMAX")->asDouble()
		);
		break;

	case  1: // shapes extent
		Extent.Create(Parameters("SHAPES")->asShapes()->Get_Extent());
		Projection  = Parameters("SHAPES")->asShapes()->Get_Projection();

		if( Parameters("BUFFER")->asDouble() > 0. )
		{
			Extent.Inflate(Parameters("BUFFER")->asDouble(), false);
		}
		break;

	case  2: // grid system extent
		Extent.Create(Parameters("GRID")->asGrid()->Get_Extent());
		Projection  = Parameters("GRID")->asGrid()->Get_Projection();

		if( Parameters("BUFFER")->asDouble() > 0. )
		{
			Extent.Inflate(Parameters("BUFFER")->asDouble(), false);
		}
		break;

	case  3: // grid system
		Cellsize    = Parameters("GRID")->asGrid()->Get_System().Get_Cellsize();
		Extent.Create(Parameters("GRID")->asGrid()->Get_Extent());
		Projection  = Parameters("GRID")->asGrid()->Get_Projection();
		break;
	}

	if( !Projection.is_Okay() )
	{
		m_CRS.Get_CRS(Projection);

		if( !Projection.is_Okay() )
		{
			return( false );
		}
	}

	//-----------------------------------------------------
	m_pProject = NULL;

	if( Projection.is_Projection() )
	{
		if( Parameters("EXTENT")->asInt() != 3 ) // grid system
		{
			Extent.xMin = Cellsize * floor(Extent.xMin / Cellsize);
			Extent.xMax = Cellsize * ceil (Extent.xMax / Cellsize);
			Extent.yMin = Cellsize * floor(Extent.yMin / Cellsize);
			Extent.yMax = Cellsize * ceil (Extent.yMax / Cellsize);
		}

		CSG_Shapes AoI(SHAPE_TYPE_Point); AoI.Get_Projection() = Projection;

		AoI.Add_Shape()->Add_Point(Extent.Get_XMin   (), Extent.Get_YMin   ());
		AoI.Add_Shape()->Add_Point(Extent.Get_XMin   (), Extent.Get_YCenter());
		AoI.Add_Shape()->Add_Point(Extent.Get_XMin   (), Extent.Get_YMax   ());
		AoI.Add_Shape()->Add_Point(Extent.Get_XCenter(), Extent.Get_YMax   ());
		AoI.Add_Shape()->Add_Point(Extent.Get_XMax   (), Extent.Get_YMax   ());
		AoI.Add_Shape()->Add_Point(Extent.Get_XMax   (), Extent.Get_YCenter());
		AoI.Add_Shape()->Add_Point(Extent.Get_XMax   (), Extent.Get_YMin   ());
		AoI.Add_Shape()->Add_Point(Extent.Get_XCenter(), Extent.Get_YMin   ());

		if( !SG_Get_Projected(&AoI, NULL, CSG_Projection::Get_GCS_WGS84()) )
		{
			Error_Set("failed to project target to geographic coordinates");

			Finalize();

			return( false );
		}

		Extent_GCS = AoI.Get_Extent(); // Extent_GCS.Inflate(10 * 3 / 3600, false)

		if( (m_pProject = SG_Get_Tool_Library_Manager().Create_Tool("pj_proj4", 4)) != NULL )
		{
			m_pProject->Set_Manager(NULL);
			m_pProject->Set_Parameter("CRS_STRING"       , Projection.Get_WKT());
			m_pProject->Set_Parameter("RESAMPLING"       , 3); // B-Spline
			m_pProject->Set_Parameter("DATA_TYPE"        , 8); // 4 byte floating point
			m_pProject->Set_Parameter("TARGET_DEFINITION", 0); // 'user defined'
			m_pProject->Set_Parameter("TARGET_USER_SIZE" , Cellsize);
			m_pProject->Set_Parameter("TARGET_USER_XMAX" , Extent.xMax);
			m_pProject->Set_Parameter("TARGET_USER_XMIN" , Extent.xMin);
			m_pProject->Set_Parameter("TARGET_USER_YMAX" , Extent.yMax);
			m_pProject->Set_Parameter("TARGET_USER_YMIN" , Extent.yMin);
		}
	}
	else
	{
		Extent_GCS = Extent;
	}

	//-----------------------------------------------------
	m_pImport = SG_Get_Tool_Library_Manager().Create_Tool("io_gdal", 0); // Import Raster

	if( !m_pImport )
	{
		Error_Set("Failed to request tool: Import Raster");

		Finalize();

		return( false );
	}

	m_pImport->Set_Manager(NULL);
	m_pImport->Set_Parameter("EXTENT", 1); // user defined"
	m_pImport->Set_Parameter("EXTENT_XMIN", Extent_GCS.xMin);
	m_pImport->Set_Parameter("EXTENT_XMAX", Extent_GCS.xMax);
	m_pImport->Set_Parameter("EXTENT_YMIN", Extent_GCS.yMin);
	m_pImport->Set_Parameter("EXTENT_YMAX", Extent_GCS.yMax);

	//-----------------------------------------------------
	return( true );
}

//---------------------------------------------------------
bool CCHELSA_Global::Finalize(void)
{
	SG_Get_Tool_Library_Manager().Delete_Tool(m_pImport ); m_pImport  = NULL;
	SG_Get_Tool_Library_Manager().Delete_Tool(m_pProject); m_pProject = NULL;

	m_Attributes.Destroy();

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CCHELSA_Global::Set_Classification(int VarID, CSG_Grid *pVariable)
{
	if( !pVariable )
	{
		return( false );
	}

	#define ADD_CLASS(r, g, b, name, description) { CSG_Table_Record &Class = *Classes.Add_Record();\
		Class.Set_Value(0, SG_GET_RGB(r, g, b));\
		Class.Set_Value(1, name               );\
		Class.Set_Value(2, description        );\
		Class.Set_Value(3, Classes.Get_Count());\
		Class.Set_Value(4, Classes.Get_Count());\
	}

	CSG_Table Classes;

	Classes.Add_Field("Color"      , SG_DATATYPE_Color );
	Classes.Add_Field("Name"       , SG_DATATYPE_String);
	Classes.Add_Field("Description", SG_DATATYPE_String);
	Classes.Add_Field("Minimum"    , SG_DATATYPE_Double);
	Classes.Add_Field("Maximum"    , SG_DATATYPE_Double);

	switch( VarID )
	{
	default: return( false );

	// Köppen-Geiger
	case CHELSA::kg0: case CHELSA::kg1: case CHELSA::kg2: Classes.Del_Records();
		ADD_CLASS(148,   1,   1, "Af"     , "equatorial, fully humid");
		ADD_CLASS(254,   0,   0, "Am"     , "equatorial, monsoonal");
		ADD_CLASS(255, 154, 154, "As"     , "equatorial, summer dry");
		ADD_CLASS(255, 207, 207, "Aw"     , "equatorial, winter dry");
		ADD_CLASS(255, 255, 101, "BWk"    , "cold desert");
		ADD_CLASS(255, 207,   0, "BWh"    , "hot desert");
		ADD_CLASS(207, 170,  85, "BSk"    , "cold steppe");
		ADD_CLASS(207, 142,  20, "BSh"    , "hot steppe");
		ADD_CLASS(  0,  48,   0, "Cfa"    , "warm temperate, fully humid, hot summer");
		ADD_CLASS(  1,  79,   1, "Cfb"    , "warm temperate, fully humid, warm summer");
		ADD_CLASS(  0, 120,   0, "Cfc"    , "warm temperate, fully humid, cool summer");
		ADD_CLASS(  0, 254,   0, "Csa"    , "warm temperate, summer dry, hot summer");
		ADD_CLASS(149, 255,   0, "Csb"    , "warm temperate, summer dry, warm summer");
		ADD_CLASS(203, 255,   0, "Csc"    , "warm temperate, summer dry, cool summer");
		ADD_CLASS(181, 101,   0, "Cwa"    , "warm temperate, winter dry, hot summer");
		ADD_CLASS(149, 102,   3, "Cwb"    , "warm temperate, winter dry, warm summer");
		ADD_CLASS( 93,  64,   2, "Cwc"    , "warm temperate, winter dry, cool summer");
		ADD_CLASS( 48,   0,  48, "Dfa"    , "snow, fully humid, hot summer");
		ADD_CLASS(101,   0, 101, "Dfb"    , "snow, fully humid, warm summer");
		ADD_CLASS(203,   0, 203, "Dfc"    , "snow, fully humid, cool summer");
		ADD_CLASS(199,  20, 135, "Dfd"    , "snow, fully humid, extremely continental");
		ADD_CLASS(253, 108, 253, "Dsa"    , "snow, summer dry, hot summer");
		ADD_CLASS(254, 182, 255, "Dsb"    , "snow, summer dry, warm summer");
		ADD_CLASS(231, 202, 253, "Dsc"    , "snow, summer dry, cool summer");
		ADD_CLASS(202, 203, 203, "Dsd"    , "snow, summer dry, extremely continental");
		ADD_CLASS(203, 182, 255, "Dwa"    , "snow, winter dry, hot summer");
		ADD_CLASS(153, 125, 178, "Dwb"    , "snow, winter dry, warm summer");
		ADD_CLASS(138,  89, 178, "Dwc"    , "snow, winter dry, cool summer");
		ADD_CLASS(109,  36, 178, "Dwd"    , "snow, winter dry, extremely continental");
		ADD_CLASS(101, 255, 255, "ET"     , "polar tundra");
		ADD_CLASS(100, 150, 255, "EF"     , "polar frost");
		ADD_CLASS(245, 245, 245, "NA"     , "NA");
		break;

	// Wissmann
	case CHELSA::kg3: Classes.Del_Records();
		ADD_CLASS(172,   0,   0, "I A"    , "Rainforest, equatorial");
		ADD_CLASS(225,   0,   0, "I F"    , "Rainforest, weak dry period");
		ADD_CLASS(255,  70,  70, "I T"    , "Savannah and monsoonal Rainforest");
		ADD_CLASS(255, 200, 179, "I S"    , "Steppe, tropical");
		ADD_CLASS(255, 225, 179, "I D"    , "Desert, tropical");
		ADD_CLASS(128,  64,   0, "II Fa"  , "");
		ADD_CLASS(196,  92,   0, "II Fb"  , "");
		ADD_CLASS(255, 127,   0, "II Tw"  , "");
		ADD_CLASS(255, 156,   0, "II Ts"  , "");
		ADD_CLASS(255, 225,   0, "II S"   , "");
		ADD_CLASS(255, 255,  64, "II D"   , "");
		ADD_CLASS(  0, 192,   0, "III F"  , "");
		ADD_CLASS(  0, 255,   0, "III Tw" , "Summer green and coniferous forest, winter dry");
		ADD_CLASS(127, 255,   0, "III Ts" , "Summer green and coniferous forest, cool etesien");
		ADD_CLASS(156, 255,   0, "III S"  , "");
		ADD_CLASS(225, 255,   0, "III D"  , "");
		ADD_CLASS(  0, 147, 147, "IV F"   , "Humid boreal forest");
		ADD_CLASS(  0, 200, 200, "IV T"   , "Winter dry boreal forest");
		ADD_CLASS(  0, 255, 255, "IV S"   , "Boreal steppe");
		ADD_CLASS(127, 255, 255, "IV D"   , "Boreal desert");
		ADD_CLASS(172, 172, 255, "V"      , "Polar tundra");
		ADD_CLASS(  0,   0, 255, "VI"     , "Polar frost");
		ADD_CLASS(245, 245, 245, "NA"     , "NA");
	break;

	// Troll-Paffen
	case CHELSA::kg5: Classes.Del_Records();
		ADD_CLASS(230, 250, 250, "I.1"    , "Polar ice-deserts");
		ADD_CLASS(216, 245, 250, "I.2"    , "Polar frost-debris belt");
		ADD_CLASS(185, 224, 250, "I.3"    , "Tundra");
		ADD_CLASS(156, 205, 240, "I.4"    , "Sub-polar tussock grassland and moors");
		ADD_CLASS(190, 170, 214, "II.1"   , "Oceanic humid coniferous woods");
		ADD_CLASS(215, 201, 229, "II.2"   , "Continental coniferous woods");
		ADD_CLASS(234, 225, 238, "II.3"   , "Highly continental dry coniferous woods");
		ADD_CLASS(145, 116,  90, "III.1"  , "Evergreen broad-leaved and mixed woods");
		ADD_CLASS(170, 152, 106, "III.2"  , "Oceanic deciduous broad-leaved and mixed woods");
		ADD_CLASS(193, 164, 123, "III.3"  , "Sub-oceanic deciduous broad-leaved and mixed woods");
		ADD_CLASS(210, 180, 140, "III.4"  , "Sub-continental deciduous broad-leaved and mixed woods");
		ADD_CLASS(226, 220, 177, "III.5"  , "Continental deciduous broad-leaved and mixed woods as well as wooded steppe");
		ADD_CLASS(242, 235, 220, "III.6"  , "Highly continental deciduous broad-leaved and mixed woods as well as wooded steppe");
		ADD_CLASS(233, 226, 150, "III.7"  , "Deciduous broad-leaved and mixed wood and wooded steppe favoured by warmth, but withstanding cold and aridity in winter");
		ADD_CLASS(223, 216, 140, "III.7a" , "Thermophile dry wood and wooded stepe which withstands moderate to hard winters");
		ADD_CLASS(218, 200, 100, "III.8"  , "Humid deciduous broad-leaved and mixed wood which favours warmth");
		ADD_CLASS(234, 207,  80, "III.9"  , "High grass-steppe with perennial herbs");
		ADD_CLASS(224, 197,  70, "III.9a" , "Humid steppe with mild winters");
		ADD_CLASS(244, 236,  88, "III.10" , "Short grass-, or dwarf shrub-, or thorn-steppe");
		ADD_CLASS(234, 226,  78, "III.10a", "Steppe with short grass, dwarf shrups and thorns");
		ADD_CLASS(241, 239, 112, "III.11" , "Central and East-Asian grass and dwarf shrub steppe");
		ADD_CLASS(245, 245, 200, "III.12" , "Semi-desert and desert with cold winters");
		ADD_CLASS(235, 235, 190, "III.12a", "Semi-desert and desert with mild winters");
		ADD_CLASS(201, 138, 110, "IV.1"   , "Sub-tropical hard-leaved and coniferous wood");
		ADD_CLASS(227, 158, 110, "IV.2"   , "Sub-tropical grass and shrub-steppe");
		ADD_CLASS(241, 195, 143, "IV.3"   , "Sub-tropical thorn- and succulants-steppe");
		ADD_CLASS(235, 175,  80, "IV.4"   , "Sub-tropical steppe with short grass, hard-leaved monsoon wood and wooded-steppe");
		ADD_CLASS(255, 219, 109, "IV.5"   , "Sub-tropical semi-deserts and deserts");
		ADD_CLASS(251, 172, 100, "IV.6"   , "Sub-tropical high-grassland");
		ADD_CLASS(229, 157,  90, "IV.7"   , "Sub-tropical humid forests (laurel and coniferous forests)");
		ADD_CLASS( 77, 117,  77, "V.1"    , "Evergreen tropical rain forest and half deciduous transition wood");
		ADD_CLASS(117, 152,  77, "V.2"    , "Rain-green humid forest and humid grass-savannah");
		ADD_CLASS(107, 142,  67, "V.2a"   , "Half deciduous transition wood");
		ADD_CLASS(150, 180,  80, "V.3"    , "Rain-green dry wood and dry savannah");
		ADD_CLASS(192, 211, 106, "V.4"    , "Tropical thorn-succulent wood and savannah");
		ADD_CLASS(182, 201,  96, "V.4a"   , "Tropical dry climates with humid months in winter");
		ADD_CLASS(212, 228, 181, "V.5"    , "Tropical semi-deserts and deserts");
		ADD_CLASS(245, 245, 245, "NA"     , "NA");
		break;

	// Thornthwaite
	case CHELSA::kg4: Classes.Del_Records();
		{
			const int Color[6] = {
				SG_GET_RGB(255,   0,   0),
				SG_GET_RGB(255, 127,   0),
				SG_GET_RGB(255, 255,   0),
				SG_GET_RGB(  0, 255,   0),
				SG_GET_RGB(  0, 255, 255),
				SG_GET_RGB(  0,   0, 255)
			};

			const CSG_String pName[5] = { "Wet", "Humid", "Subhumid", "Semiarid", "Arid" };
			const CSG_String tName[6] = { "Tropical", "Mesothermal", "Microthermal", "Taiga", "Tundra", "Frost" };

			Classes.Set_Count(5 * 6);

			for(int t=0; t<6; t++)
			{
				CSG_Colors Colors(5); Colors.Set_Ramp(Color[t], Color[t]); Colors.Set_Ramp_Brighness(64, 200);

				for(int p=0; p<5; p++)
				{
					int i = p + 5 * t; CSG_Table_Record &Class = Classes[i];

					Class.Set_Value(0, Colors[p]);
					Class.Set_Value(1, pName[p] + " / " + tName[t]);
					Class.Set_Value(3, 1 + i);
					Class.Set_Value(4, 1 + i);
				}
			}

			ADD_CLASS(245, 245, 245, "NA", "NA");
		}
		break;
	}

	DataObject_Add(pVariable);

	CSG_Parameter *pLUT = DataObject_Get_Parameter(pVariable, "LUT");

	if( !pLUT || !pLUT->asTable() || !pLUT->asTable()->Assign_Values(Classes) )
	{
		return( false );
	}

	DataObject_Set_Parameter(pVariable, pLUT);
	DataObject_Set_Parameter(pVariable, "COLORS_TYPE", 1); // Classified

	return( true );
}


///////////////////////////////////////////////////////////
//                                                       //
//                                                       //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
