
///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//                     Tool Library                      //
//                    io_webservices                     //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//                  bayern_opendata.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 "opendata_dgm1.h"


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

//---------------------------------------------------------
COpenData_DGM1::COpenData_DGM1(void)
{
	Parameters.Add_Grid_Output("",
		"RESULT"     , _TL("DGM1"),
		_TL("")
	);

	Parameters.Add_FilePath("",
		"TILES"      , _TL("Local Tiles Directory"),
		_TL("Download location for tiles. If requested tile is already present download will be skipped."),
		NULL, NULL, true, true
	);

	//-----------------------------------------------------
	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(""),    0.);
	Parameters.Add_Double("EXTENT", "XMAX", _TL("East"   ), _TL(""), 1000.);
	Parameters.Add_Double("EXTENT", "YMIN", _TL("South"  ), _TL(""),    0.);
	Parameters.Add_Double("EXTENT", "YMAX", _TL("North"  ), _TL(""), 1000.);
	Parameters.Add_Int   ("EXTENT", "NX"  , _TL("Columns"), _TL(""), 1001, 1, true);
	Parameters.Add_Int   ("EXTENT", "NY"  , _TL("Rows"   ), _TL(""), 1001, 1, true);

	Parameters.Add_Double("",
		"BUFFER"     , _TL("Buffer"),
		_TL("add buffer (map units) to extent"),
		0., 0., true
	);

	Parameters.Add_Double("",
		"CELLSIZE"   , _TL("Cellsize"),
		_TL(""),
		1., .1, true
	);

	m_CRS.Create(Parameters);
}


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

//---------------------------------------------------------
bool COpenData_DGM1::Set_Data_CRS(int EPSG)
{
	Parameters.Set_Parameter("CRS_STRING", CSG_String::Format("epsg:%d", m_EPSG = EPSG));

	m_CRS.On_Parameter_Changed(&Parameters, Parameters("CRS_STRING"));

	return( true );
}

//---------------------------------------------------------
bool COpenData_DGM1::Set_Default_Extent(double xMin, double yMin, double xMax, double yMax, double Cellsize)
{
	if( Cellsize < 1. || (xMax - xMin) < 1. || (yMax - yMin) < 1. )
	{
		return( false );
	}

	CSG_Grid_System System(Cellsize, xMin, yMin, xMax, yMax);

	Parameters.Set_Callback(false);
	Parameters["CELLSIZE"].Set_Value(System.Get_Cellsize());
	Parameters["XMIN"    ].Set_Value(System.Get_XMin    ());
	Parameters["XMAX"    ].Set_Value(System.Get_XMax    ());
	Parameters["NX"      ].Set_Value(System.Get_NX      ());
	Parameters["YMIN"    ].Set_Value(System.Get_YMin    ());
	Parameters["YMAX"    ].Set_Value(System.Get_YMax    ());
	Parameters["NY"      ].Set_Value(System.Get_NY      ());
	Parameters.Set_Callback(true);

	return( true );
}


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

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

	return( CSG_Tool::On_Before_Execution() );
}

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

	return( CSG_Tool::On_After_Execution() );
}

//---------------------------------------------------------
int COpenData_DGM1::On_Parameter_Changed(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	if( pParameter->Cmp_Identifier("CELLSIZE") || (pParameter->Get_Parent() && pParameter->Get_Parent()->Cmp_Identifier("EXTENT")) )
	{
		double Cellsize = (*pParameters)("CELLSIZE")->asDouble();
		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 COpenData_DGM1::On_Parameters_Enable(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	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("CELLSIZE"   , pParameter->asInt() != 3);
		pParameters->Set_Enabled("BUFFER"     , pParameter->asInt() == 1 || pParameter->asInt() == 2);
	}

	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());
	}

	m_CRS.On_Parameters_Enable(pParameters, pParameter);

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


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

//---------------------------------------------------------
bool COpenData_DGM1::On_Execute(void)
{
	CSG_String Directory = Parameters("TILES")->asString();

	if( !SG_Dir_Exists(Directory) )
	{
		Message_Fmt("\n%s: %s\n", _TL("Warning"), _TL("No or invalid directory specified for local tiles database! Using temporary folder instead!"));

		Directory = SG_File_Make_Path(SG_Dir_Get_Temp(), Get_Name() + " (" + m_Name + ")");

		if( !SG_Dir_Create(Directory, true) )
		{
			Error_Fmt("%s: %s", _TL("failed to create temporary tiles directory"), Directory.c_str());

			return( false );
		}
	}

	//--------------------------------------------------------
	CSG_Rect Extent, Extent_Data; CSG_Projection Projection, Projection_Data(m_EPSG);

	double Cellsize = Parameters("CELLSIZE")->asDouble();

	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 );
		}
	}

	//--------------------------------------------------------
	if( Projection != Projection_Data )
	{
		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, Projection_Data) )
		{
			Error_Set("failed to project target to geographic coordinates");

			return( false );
		}

		Extent_Data = AoI.Get_Extent();
	}
	else
	{
		Extent_Data = Extent;
	}

	//--------------------------------------------------------
	if( !Provide_Tiles(Directory, Extent_Data) )
	{
		return( false );
	}

	//--------------------------------------------------------
	CSG_Data_Manager Data;

	CSG_Tool *pTool = SG_Get_Tool_Library_Manager().Create_Tool("io_gdal", 0);

	if( !pTool || !pTool->Reset() || !pTool->Set_Manager(&Data)
	||  !pTool->Set_Parameter("FILES"      , SG_File_Make_Path(Directory, m_VRT_Name, "vrt"))
	||  !pTool->Set_Parameter("EXTENT"     , 1) // "user defined"
	||  !pTool->Set_Parameter("EXTENT_XMIN", Extent_Data.xMin)
	||  !pTool->Set_Parameter("EXTENT_XMAX", Extent_Data.xMax)
	||  !pTool->Set_Parameter("EXTENT_XMAX", Extent_Data.xMax)
	||  !pTool->Set_Parameter("EXTENT_YMIN", Extent_Data.yMin)
	||  !pTool->Set_Parameter("EXTENT_YMAX", Extent_Data.yMax)
	||  !pTool->Execute() )
	{
		Error_Fmt("failed to execute tool \'%s\'", _TL("Import Raster"));

		SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

		return( false );
	}

	CSG_Grid *pGrid = pTool->Get_Parameter("GRIDS")->asGridList()->Get_Grid(0);

	pGrid->Get_Projection().Create(Projection_Data);

	SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

	//--------------------------------------------------------
	if( Projection == Projection_Data && Cellsize == pGrid->Get_Cellsize() )
	{
		pGrid->Set_Name(m_Name);

		Parameters.Set_Parameter("RESULT", pGrid);

		return( true );
	}

	Process_Set_Text("%s...", _TL("projection"));

	pTool = SG_Get_Tool_Library_Manager().Create_Tool("pj_proj4", 4);

	if( !pTool || !pTool->Set_Manager(&Data)
	||  !pTool->Set_Parameter("CRS_STRING"       , Projection.Get_WKT())
	||  !pTool->Set_Parameter("SOURCE"           , pGrid)
	||  !pTool->Set_Parameter("RESAMPLING"       , 3) // B-Spline
	||  !pTool->Set_Parameter("DATA_TYPE"        , 8) // 4 byte floating point
	||  !pTool->Set_Parameter("TARGET_DEFINITION", 0) // 'user defined'
	||  !pTool->Set_Parameter("TARGET_USER_SIZE" , Cellsize)
	||  !pTool->Set_Parameter("TARGET_USER_XMAX" , Extent.xMax)
	||  !pTool->Set_Parameter("TARGET_USER_XMIN" , Extent.xMin)
	||  !pTool->Set_Parameter("TARGET_USER_YMAX" , Extent.yMax)
	||  !pTool->Set_Parameter("TARGET_USER_YMIN" , Extent.yMin)
	||  !pTool->Execute() )
	{
		Error_Fmt("failed to execute tool \'%s\'", _TL("Coordinate Transformation (Grid)"));

		SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

		return( false );
	}

	pGrid = pTool->Get_Parameter("GRID")->asGrid(); Data.Delete(pGrid, true);

	SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

	//--------------------------------------------------------
	pGrid->Set_Name(m_Name);

	Parameters.Set_Parameter("RESULT", pGrid);

	return( true );
}


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

//---------------------------------------------------------
bool COpenData_DGM1::Provide_Tiles(const CSG_String &Directory, CSG_Rect Extent)
{
	CSG_Rect_Int Tiles(
		(int)floor(Extent.xMin / 1000.), (int)floor(Extent.yMin / 1000.),
		(int)floor(Extent.xMax / 1000.), (int)floor(Extent.yMax / 1000.)
	);

	if( m_Tile_Size > 1 )
	{
		Tiles.xMin = m_Tile_Size * (int)(Tiles.xMin / m_Tile_Size);
		Tiles.xMax = m_Tile_Size * (int)(Tiles.xMax / m_Tile_Size);
		Tiles.yMin = m_Tile_Size * (int)(Tiles.yMin / m_Tile_Size);
		Tiles.yMax = m_Tile_Size * (int)(Tiles.yMax / m_Tile_Size);
	}

	int nAdded = 0, nFailed = 0, nFound = 0, i = 0, n = (1 + Tiles.Get_XRange()) * (1 + Tiles.Get_YRange());

	for(int Row=Tiles.yMin; Process_Get_Okay() && Row<=Tiles.yMax; Row+=m_Tile_Size)
	{
		for(int Col=Tiles.xMin; Set_Progress(i++, n) && Col<=Tiles.xMax; Col+=m_Tile_Size)
		{
			int Result = Provide_Tile(Directory, Col, Row);

			if( Result > 0 )
			{
				nAdded  += 1;
			}
			else if( Result < 0 )
			{
				nFailed += 1;
			}
			else
			{
				nFound  += 1;
			}
		}
	}

	if( nFailed > 0 )
	{
		Message_Fmt("\n%d download(s) of %d failed", nFailed, nFailed + nAdded);
	}

	if( (nAdded + nFound > 0) || !SG_File_Exists(SG_File_Make_Path(Directory, m_VRT_Name, "vrt")) )
	{
		Update_VRT(Directory);
	}

	return( nAdded + nFound > 0 );
}

//---------------------------------------------------------
int COpenData_DGM1::Provide_Tile(const CSG_String &Directory, int Col, int Row)
{
	return( Provide_Tile(Directory, Get_Tile_Name(Col, Row), Get_Tile_Request(Col, Row)) );
}

int COpenData_DGM1::Provide_Tile(const CSG_String &Directory, const CSG_String &Tile, const CSG_String &Request)
{
	CSG_String File = SG_File_Make_Path(Directory, Tile);

	if( SG_File_Exists(File) || Tile.is_Empty() || Request.is_Empty() )
	{
		return( 0 );
	}

	//-----------------------------------------------------
	Message_Fmt("\n%s: %s/%s...", _TL("requesting file"), m_ServerPath.c_str(), Request.c_str());

	Process_Set_Text("%s: %s...", Tile.c_str(), _TL("downloading"));

//	SG_UI_Process_Set_Busy(true, CSG_String::Format("%s: %s%s...", _TL("downloading"), m_ServerPath.c_str(), Request.c_str()));

	CSG_CURL Connection(m_ServerPath);

	if( !Connection.Request(Request, File.c_str()) )
	{
//		SG_UI_Process_Set_Busy(false);

		Message_Fmt(_TL("failed"));

		Error_Fmt("%s:\n\n%s%s", _TL("failed to request file from server"), m_ServerPath.c_str(), Request.c_str());

		return( -1 );
	}

//	SG_UI_Process_Set_Busy(false);

	//-----------------------------------------------------
	CSG_File Stream;

	if( !Stream.Open(File) || (Stream.Read_Char() == '<' && Stream.Read_Char() == '!') )
	{
		Stream.Close(); SG_File_Delete(File);

		Message_Fmt(_TL("failed"));

		Error_Fmt("%s:\n\n%s%s", _TL("failed to request file from server"), m_ServerPath.c_str(), Request.c_str());

		return( -1 );
	}

	//-----------------------------------------------------
	Message_Fmt(_TL("okay"));

	return( 1 );
}

//---------------------------------------------------------
bool COpenData_DGM1::Update_VRT(const CSG_String &Directory)
{
	CSG_Strings Files;

	if( !SG_Dir_List_Files(Files, Directory, m_Extension) || Files.Get_Count() < 1 )
	{
		Error_Set(_TL("no files found in directory"));

		return( false );
	}

	CSG_String Tiles;

	for(int i=0; i<Files.Get_Count(); i++)
	{
		Tiles += "\"" + Files[i] + "\" ";
	}

	//-----------------------------------------------------
	CSG_Tool *pTool = SG_Get_Tool_Library_Manager().Create_Tool("io_gdal", 12);

	SG_UI_ProgressAndMsg_Lock(true);

	bool bResult = pTool
	  && pTool->Set_Parameter("FILES"   , Tiles)
	  && pTool->Set_Parameter("VRT_NAME", SG_File_Make_Path(Directory, m_VRT_Name, "vrt"))
	  && pTool->Execute();

	SG_UI_ProgressAndMsg_Lock(false);

	SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

	if( !bResult )
	{
		Error_Set(_TL("failed to update Virtual Raster Tiles file"));
	}

	return( bResult );
}


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

//---------------------------------------------------------
COpenData_DGM1_Bayern::COpenData_DGM1_Bayern(void)
{
	Set_Name		("DGM1 Bayern");

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

	Set_Description	(_TW(
		"This tool provides easy-to-use access to the "
		SG_T("<i>\'Digitales Geländemodell 1m (DGM1)\'</i> ")
		"elevation data from the OpenData server of the "
		"<i>Bayrische Vermessungsverwaltung (Bavaria)</i>. "
		"It uses a local database in the chosen directory which provides "
		"the original tiles. If the tiles covering the requested area are "
		"not found in this directory the tool tries to download these "
		"from the server. "
		"\n\n"
		"The data is licensed under "
		"<a href=\"https://creativecommons.org/licenses/by/4.0/deed.de\">CC BY 4.0</a> "
	));

	Add_Reference("https://geodaten.bayern.de/opengeodata/OpenDataDetail.html?pn=dgm1",
		SG_T("Bayrische Vermessungsverwaltung - OpenData")
	);

	Add_Reference("https://creativecommons.org/licenses/by/4.0/deed.de",
		SG_T("CC BY 4.0")
	);

	//-----------------------------------------------------
	m_ServerPath = "https://download1.bayernwolke.de/a/dgm/dgm1/";

	//-----------------------------------------------------
	Set_Default_Extent( // Zugspitze !
		649000., 5253000.,
		650000., 5254000.
	);

	Set_Data_CRS(25832); // ETRS89 / UTM32N
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_Bayern::Get_Tile_Name(int Col, int Row)
{
	return( CSG_String::Format("%d_%d.tif", Col, Row) );
}


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

//---------------------------------------------------------
COpenData_DGM1_RLP::COpenData_DGM1_RLP(void)
{
	Set_Name		("DGM1 Rheinland-Pfalz");

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

	Set_Description	(_TW(
		"This tool provides easy-to-use access to the "
		SG_T("<i>\'Digitales Geländemodell 1m (DGM1)\'</i> ")
		"elevation data from the OpenData server of the "
		"<i>Landesamt für Vermessung und Geobasisinformation Rheinland-Pfalz (Rhineland-Palatinate)</i>. "
		"It uses a local database in the chosen directory which provides "
		"the original tiles. If the tiles covering the requested area are "
		"not found in this directory the tool tries to download these "
		"from the server. "
		"\n\n"
		"The data is licensed under "
		"<a href=\"creativecommons.org/licenses/by-sa/4.0/\">CC BY-SA 4.0</a> "
	));

	Add_Reference("https://geoshop.rlp.de/opendata-%C3%BCbersicht.html",
		SG_T("OpenData Landesamt für Vermessung und Geobasisinformation Rheinland-Pfalz")
	);

	Add_Reference("https://geoshop.rlp.de/dl-de_by-2-0.html",
		SG_T("Datenlizenz Deutschland - Namensnennung 2.0")
	);

	Add_Reference("https://geoshop.rlp.de/allgemeine_nutzungsbedingungen.html",
		SG_T("CC BY-SA 4.0")
	);

	//-----------------------------------------------------
	m_ServerPath = "https://geobasis-rlp.de/data/dgm1/current/tif/";

	//-----------------------------------------------------
	Set_Default_Extent( // Dauner Maare !
		344500., 5558000.,
		348500., 5562000.
	);

	Set_Data_CRS(25832); // ETRS89 / UTM32N
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_RLP::Get_Tile_Name(int Col, int Row)
{
	return( CSG_String::Format("dgm01_32_%d_%d_1_rp.tif", Col, Row) );
}


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

//---------------------------------------------------------
COpenData_DGM1_NRW::COpenData_DGM1_NRW(void)
{
	Set_Name		("DGM1 Nordrhein-Westfalen");

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

	Set_Description	(_TW(
		"This tool provides easy-to-use access to the "
		SG_T("<i>\'Digitales Geländemodell 1m (DGM1)\'</i> ")
		"elevation data from the OpenGeodata server of "
		"<i>North Rhine-Westphalia (Nordrhein-Westfalen, NRW)</i>. "
		"It uses a local database in the chosen directory which provides "
		"the original tiles. If the tiles covering the requested area are "
		"not found in this directory the tool tries to download these "
		"from the server. "
		"\n\n"
		"The data is licensed under "
		"<a href=\"https://www.govdata.de/dl-de/zero-2-0\">Data licence Germany - Zero - Version 2.0</a>.\n"
		"Any use is permitted without restrictions or conditions. "
		"The data and meta-data provided may, for commercial and non-commercial use, in particular\n"
		"1. be copied, printed, presented, altered, processed and transmitted to third parties;\n"
		"2. be merged with own data and with the data of others and be combined to form new and independent datasets;\n"
		"3. be integrated in internal and external business processes, products and applications in public and non-public electronic networks. "
	));

	Add_Reference("https://www.opengeodata.nrw.de/produkte/geobasis/hm/dgm1_tiff/dgm1_tiff/",
		SG_T("OpenGeodata.NRW")
	);

	Add_Reference("https://www.bezreg-koeln.nrw.de/geobasis-nrw/produkte-und-dienste/hoehenmodelle/digitale-gelaendemodelle/digitales-gelaendemodell/",
		SG_T("Produktinformation")
	);

	Add_Reference("https://www.govdata.de/dl-de/zero-2-0/",
		SG_T("Datenlizenz Deutschland - Zero - Version 2.0")
	);

	//-----------------------------------------------------
	Parameters.Add_Choice("", "DATASET", _TL("Data Set"), _TL("Download Digital Terrain Model (DGM) or Digital Surface Model (DOM)"), "DGM|DOM");

	//-----------------------------------------------------
	Set_Default_Extent( // Cologne Cathedral !
		356000., 5645000.,
		357000., 5646000.
	);

	Set_Data_CRS(25832); // ETRS89 / UTM32N
}

//---------------------------------------------------------
bool COpenData_DGM1_NRW::On_Execute(void)
{
	m_ServerPath = Parameters["DATASET"].asInt() == 0
		? "https://www.opengeodata.nrw.de/produkte/geobasis/hm/dgm1_tiff/dgm1_tiff/"
		: "https://www.opengeodata.nrw.de/produkte/geobasis/hm/dom1_tiff/dom1_tiff/";

	m_Name = Parameters["DATASET"].asInt() == 0 ? "DGM1" : "DOM1";

	return( COpenData_DGM1::On_Execute() );
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_NRW::Get_Tile_Name(int Col, int Row)
{
	return( "" );
}

//---------------------------------------------------------
int COpenData_DGM1_NRW::Provide_Tile(const CSG_String &Directory, int Col, int Row)
{
	const SG_Char *Type = Parameters["DATASET"].asInt() == 0 ? SG_T("dgm") : SG_T("dom");

	Process_Set_Text("%s: %s1_32_%d_%d_1_nw.tif...", _TL("downloading"), Type, Col, Row);
	Message_Fmt("\n%s: %s/%s1_32_%d_%d_1_nw_2019-24.tif...", _TL("requesting tile"), m_ServerPath.c_str(), Type, Col, Row);

	for(int Year=2024; Year>=2015; Year--)
	{
		CSG_String Tile(CSG_String::Format("%s1_32_%d_%d_1_nw_%d.tif", Type, Col, Row, Year));

		SG_UI_ProgressAndMsg_Lock(true);
		int Result = COpenData_DGM1::Provide_Tile(Directory, Tile, Tile);
		SG_UI_ProgressAndMsg_Lock(false);

		if( Result >= 0 )
		{
			Message_Fmt(_TL("okay"));

			return( Result );
		}
	}

	Message_Fmt(_TL("failed"));

	Error_Fmt("%s:\n\n%s%s1_32_%d_%d_1_nw.tif", _TL("failed to request file from server"), m_ServerPath.c_str(), Type, Col, Row);

	return( -1 );
}


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

//---------------------------------------------------------
COpenData_DGM1_MV::COpenData_DGM1_MV(void)
{
	Set_Name		("DGM1 Mecklenburg-Vorpommern");

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

	Set_Description	(_TW(
		"This tool provides easy-to-use access to the "
		SG_T("<i>\'Digitales Geländemodell 1m (DGM1)\'</i> ")
		"elevation data from the OpenGeodata server of "
		"<i>Mecklenburg-Western Pomerania (Mecklenburg-Vorpommern, MV)</i>. "
		"It uses a local database in the chosen directory which provides "
		"the original tiles. If the tiles covering the requested area are "
		"not found in this directory the tool tries to download these "
		"from the server. "
		"\n\n"
		"The licensee is obligated to provide a clearly visible source "
		"reference for each public reproduction, distribution or presentation "
		"of the geodata, as well as for each publication or external use of "
		"any processing or modification, which must be designed as follows: "
		"© GeoBasis-DE/M-V (year of last data delivery)"
	));

	Add_Reference("https://www.geoportal-mv.de/",
		SG_T("GeoPortal.MV")
	);

	//-----------------------------------------------------
	m_ServerPath = "https://www.geodaten-mv.de/dienste/dgm_download";

	m_Tile_Size  = 2; // => 2 kilometer

	//-----------------------------------------------------
	Set_Default_Extent( // Jasmund, Ruegen !
		406000., 6039500.,
		414700., 6049600.
	);

	Set_Data_CRS(25833); // ETRS89 / UTM33N
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_MV::Get_Tile_Name(int Col, int Row)
{
	return( CSG_String::Format("dgm1_33_%d_%d_2_gtiff.tif", Col, Row) );
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_MV::Get_Tile_Request(int Col, int Row)
{
	return( CSG_String::Format("?index=4&dataset=ca268792-s2q1-4a39-b34c-9ec5bf9a4469&file=dgm1_33_%d_%d_2_gtiff.tif&", Col, Row) );
}


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

//---------------------------------------------------------
COpenData_DGM1_NI::COpenData_DGM1_NI(void)
{
	Set_Name		("DGM1 Niedersachsen");

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

	Set_Description	(_TW(
		"This tool provides easy-to-use access to the "
		SG_T("<i>\'Digitales Geländemodell 1m (DGM1)\'</i> ")
		"elevation data from the OpenGeodata server of "
		"<i>Lower Saxony (Niedersachsen)</i>. "
		"It uses a local database in the chosen directory which provides "
		"the original tiles. If the tiles covering the requested area are "
		"not found in this directory the tool tries to download these "
		"from the server. "
		"\n\n"
		"<b>License</b>\n"
		"The geodata (open geodata) listed on this LGLN ​​website can be used "
		"in accordance with the General Terms and Conditions of Use (GTC). "
		"The relevant terms of use can be found "
		"<a href=\"https://www.lgln.niedersachsen.de/startseite/wir_uber_uns_amp_organisation/allgemeine_geschafts_und_nutzungsbedingungen_agnb/allgemeine-geschafts-und-nutzungsbedingungen-agnb-97401.html\">here</a> "
		"under \"7 Use of Open Geodata\" of the GTC."
	));

	Add_Reference("https://ni-lgln-opengeodata.hub.arcgis.com/",
		SG_T("OpenGeoData.NI")
	);

	Add_Reference("https://ni-lgln-opengeodata.hub.arcgis.com/apps/lgln-opengeodata::digitales-gel%C3%A4ndemodell-dgm1/about/",
		SG_T("DGM1@OpenGeoData.NI")
	);

	Add_Reference("https://www.lgln.niedersachsen.de/startseite/wir_uber_uns_amp_organisation/allgemeine_geschafts_und_nutzungsbedingungen_agnb/allgemeine-geschafts-und-nutzungsbedingungen-agnb-97401.html",
		SG_T("General Terms and Conditions of Use")
	);

	//-----------------------------------------------------
	Set_Default_Extent( // Forest of Göttingen !
		562000., 5704000.,
		578000., 5718000.
	);

	Set_Data_CRS(25832); // ETRS89 / UTM33N
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_NI::Get_Tile_Name(int Col, int Row)
{
	return( CSG_String::Format("dgm1_32_%d_%d_1_ni.tif", Col, Row) );
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_NI::Get_Tile_Request(int Col, int Row)
{
	const CSG_String Server = "https://dgm.s3.eu-de.cloud-object-storage.appdomain.cloud/";

	int id = 320000000 + Col * 10000 + Row; TTiles *Tiles = Get_Tiles();

	for(int i=0; Tiles[i].id; i++)
	{
		if( id == Tiles[i].id )
		{
			CSG_String Path(Tiles[i].path);

			m_ServerPath = Server + Path.BeforeLast('/');

			return( Path.AfterLast('/') );
		}
	}

	return( "" );
}


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

//---------------------------------------------------------
COpenData_DGM1_SH::COpenData_DGM1_SH(void)
{
	Set_Name		("DGM1 Schleswig-Holstein");

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

	Set_Description	(_TW(
		"This tool provides easy-to-use access to the "
		SG_T("<i>\'Digitales Geländemodell 1m (DGM1)\'</i> ")
		"elevation data from the OpenGeodata server of "
		"<i>Schleswig-Holstein</i>. "
		"It uses a local database in the chosen directory which provides "
		"the original tiles. If the tiles covering the requested area are "
		"not found in this directory the tool tries to download these "
		"from the server. "
		"\n\n"
		"The data is licensed under "
		"<a href=\"https://creativecommons.org/licenses/by/4.0/deed.de\">CC BY 4.0</a> "
	));

	Add_Reference("https://geodaten.schleswig-holstein.de/gaialight-sh/_apps/dladownload/index.php/",
		SG_T("Schleswig-Holstein - Offene Geobasisdaten")
	);

	Add_Reference("https://creativecommons.org/licenses/by/4.0/deed.de",
		SG_T("CC BY 4.0")
	);

	//-----------------------------------------------------
	Parameters.Add_Choice("", "DATASET", _TL("Data Set"), _TL("Download Digital Terrain Model (DGM) or Digital Surface Model (DOM)"), "DGM|DOM");

	//-----------------------------------------------------
	m_ServerPath = "https://geodaten.schleswig-holstein.de/gaialight-sh/_apps/dladownload/massen.php";

	//-----------------------------------------------------
	Set_Default_Extent( // Haithabu !
		536000., 6038000.,
		537000., 6039000.
	);

	Set_Data_CRS(25832); // ETRS89 / UTM33N
}

//---------------------------------------------------------
bool COpenData_DGM1_SH::On_Execute(void)
{
	m_Name      = Parameters["DATASET"].asInt() == 0 ? "DGM1" : "DOM1";
	m_Extension = Parameters["DATASET"].asInt() == 0 ?  "xyz" :  "tif";

	return( COpenData_DGM1::On_Execute() );
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_SH::Get_Tile_Name(int Col, int Row)
{
	return( Parameters["DATASET"].asInt() == 0
		? CSG_String::Format("dgm1_32_%d_%d_1_sh.xyz", Col, Row)
		: CSG_String::Format("dom1_32_%d_%d_1_sh.tif", Col, Row)
	);
}

//---------------------------------------------------------
CSG_String COpenData_DGM1_SH::Get_Tile_Request(int Col, int Row)
{
	int id = 320000000 + Col * 10000 + Row; TTiles *Tiles = Get_Tiles(Parameters["DATASET"].asInt());

	for(int i=0; Tiles[i].id; i++)
	{
		if( id == Tiles[i].id )
		{
			return( CSG_String("?file=") + Tiles[i].path );
		}
	}

	return( "" );
}


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

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