
///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//                     Tool Library                      //
//                       image_io                        //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//             image_import_georeferenced.cpp            //
//                                                       //
//                 Copyright (C) 2026 by                 //
//                      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                                //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#if HAVE_EXIV2

//---------------------------------------------------------
#include "image_import_georeferenced.h"
#include "image_import_exif.h"


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

//---------------------------------------------------------
CImage_Import_Georeferenced::CImage_Import_Georeferenced(void)
{
	Set_Name		(_TL("Import Images with Flight Parameters"));

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

	Set_Description	(_TW(
		"This tool imports and georeferences airborne images "
		"if necessary information about camera, position and flight "
		"parameters (e.g. yaw, roll, pitch) is provided by the image file "
		"in the <i>Exchangeable Image File Format</i> (Exif) specification. "
	));

	Add_Description(CSG_String::Format("<hr><p>Exiv2 version is <b>%d.%d.%d</b></p>",
		EXIV2_MAJOR_VERSION,
		EXIV2_MINOR_VERSION,
		EXIV2_PATCH_VERSION)
	);

	Add_Reference("https://exiv2.org/", SG_T("Exiv2 C++ metadata library"));

	//-----------------------------------------------------
	Parameters.Add_Grid_List("", "IMAGES", _TL("Images"), _TL(""), PARAMETER_OUTPUT);

	Parameters.Add_FilePath("", "FILES", _TL("Files"), _TL(""), CSG_String::Format(
		"%s"                        "|*.jpg;*.jif;*.jpeg;*.raw;*.dng;*.JPG;*.JIF;*.JPEG;*.RAW;*.DNG|"
		"%s (*.jpg, *.jif, *.jpeg)" "|*.jpg;*.jif;*.jpeg;*.JPG;*.JIF;*.JPEG|"
		"%s (*.raw, *.dng)" 		"|*.raw;*.dng;*.RAW;*.DNG|"
		"%s"                        "|*.*",
		_TL("Recognized File Types"),
		_TL("JPEG - JFIF Compliant"),
		_TL("RAW Images"           ),
		_TL("All Files"            )), NULL, false, false, true
	);

	Parameters.Add_Grid_or_Const("", "DEM", _TL("Elevation"), _TL(""), 0., 0., false, 0., false, false);

	Parameters.Add_Double("", "RESOLUTION" , _TL("Resolution"     ), _TL(""), 1., true);
	Parameters.Add_Choice("", "ORIENTATION", _TL("Orientation"    ), _TL(""), CSG_String::Format("BLUH|PATB"), 0);

	Parameters.Add_Node  ("", "ADJUSTMENTS", _TL("Adjustments"), _TL(""));
	Parameters.Add_Double("ADJUSTMENTS", "KAPPA_ADD" , _TL("Heading" ), _TL("origin adjustment angle [degree] for kappa (Z axis, heading/yaw)"), 0.);
//	Parameters.Add_Bool  ("ADJUSTMENTS", "KAPPA_SIGN", _TL("Heading" ), _TL("origin adjustment angle [degree] for kappa (Z axis, heading/yaw)"), 0.);
	Parameters.Add_Double("ADJUSTMENTS", "Z_ADD"     , _TL("Altitude"), _TL("altitude adjustment"), 0.);

	//-----------------------------------------------------
	m_CRS.Create(Parameters);
}


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

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

	return( CSG_Tool::On_Before_Execution() );
}

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

	return( CSG_Tool::On_After_Execution() );
}

//---------------------------------------------------------
int CImage_Import_Georeferenced::On_Parameter_Changed(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	m_CRS.On_Parameter_Changed(pParameters, pParameter);

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

//---------------------------------------------------------
int CImage_Import_Georeferenced::On_Parameters_Enable(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	m_CRS.On_Parameters_Enable(pParameters, pParameter);

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


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

//---------------------------------------------------------
bool CImage_Import_Georeferenced::On_Execute(void)
{
	CSG_Strings Files;

	if( Parameters["FILES"].asFilePath()->Get_FilePaths(Files) == false || Files.Get_Count() < 1 )
	{
		Error_Set(_TL("No files in list!"));

		return( false );
	}

	//-----------------------------------------------------
	Parameters["IMAGES"].asGridList()->Del_Items();

	CSG_Projection Projection; m_CRS.Get_CRS(Projection, false);

	for(int i=0; i<Files.Get_Count() && Set_Progress(i, Files.Get_Count()); i++)
	{
		Process_Set_Text("[%d/%d] %s", i + 1, Files.Get_Count(), SG_File_Get_Name(Files[i], true).c_str());

		Add_File(Files[i], Projection);
	}

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


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

//---------------------------------------------------------
bool CImage_Import_Georeferenced::Add_File(const CSG_String &File, CSG_Projection Projection)
{
	if( SG_File_Exists(File) == false )
	{
		Error_Fmt("%s\n%s: %s", _TL("File does not exist!"), _TL("File"), File.c_str());

		return( false );
	}

	CSG_MetaData Exif; CSG_String Error;

	if( !CEXIV_Helper::Get_MetaData(File, Exif, Error) )
	{
		Error_Fmt("%s\n%s: %s\n%s: %s", _TL("File does not provide metadata!"), _TL("Error"), Error.c_str(), _TL("File"), File.c_str());

		return( false );
	}

	//-----------------------------------------------------
	#define Get_Value(key, value) if( !Exif.Get_Content(key, value) ) { Error_Fmt("\n%s: %s", _TL("Missing"), SG_T(key)); return( false ); }
	#define Cmp_Value(key, value) (Exif(key) && Exif[key].Cmp_Content(value))

	CSG_Point Coordinate; double deg, min, sec, CFL, Altitude, Yaw, Pitch, Roll;

	Get_Value("Exif.Photo.FocalLength"         , CFL);

	Get_Value("Exif.GPSInfo.GPSLongitude.0"    , deg);
	Get_Value("Exif.GPSInfo.GPSLongitude.1"    , min);
	Get_Value("Exif.GPSInfo.GPSLongitude.2"    , sec);

	Coordinate.x = SG_Degree_To_Decimal(deg, min, sec);     if( Cmp_Value("Exif.GPSInfo.GPSLongitudeRef", "W") ) { Coordinate.x *= -1.; }

	Get_Value("Exif.GPSInfo.GPSLatitude.0"     , deg);
	Get_Value("Exif.GPSInfo.GPSLatitude.1"     , min);
	Get_Value("Exif.GPSInfo.GPSLatitude.2"     , sec);

	Coordinate.y = SG_Degree_To_Decimal(deg, min, sec);     if( Cmp_Value("Exif.GPSInfo.GPSLatitudeRef" , "S") ) { Coordinate.y *= -1.; }

	Get_Value("Xmp.drone-dji.FlightYawDegree"  , Yaw  );
	Get_Value("Xmp.drone-dji.FlightPitchDegree", Pitch);
	Get_Value("Xmp.drone-dji.FlightRollDegree" , Roll );

	if( (!Projection.is_Okay() || !Projection.is_Projection()) )
	{
		Projection.Set_UTM_WGS84(1 + (int)fmod(floor((Coordinate.x + 180.) / 6.), 60.), Coordinate.y < 0.);
	}

	if( !Projection.is_Okay() || !Projection.is_Projection() || !SG_Get_Projected(CSG_Projection::Get_GCS_WGS84(), Projection, Coordinate) )
	{
		Message_Fmt(" %s", _TL("Could not project image coordinate!"));

		return( false );
	}

	if( Parameters["DEM"].asGrid() )
	{
		Get_Value("Exif.GPSInfo.GPSAltitude"      , Altitude); if( Cmp_Value("Exif.GPSInfo.GPSAltitudeRef", "1") ) { Altitude *= -1.; }
	}
	else
	{
		Get_Value("Xmp.drone-dji.RelativeAltitude", Altitude);
	}

	Altitude += Parameters["Z_ADD"].asDouble();

	//-----------------------------------------------------
	CSG_Data_Manager Data; CSG_Tool *pTool;

	if( (pTool = SG_Get_Tool_Library_Manager().Create_Tool("io_grid_image", 1)) == NULL // Import Image File
	||  !pTool->Set_Manager(&Data)
	||  !pTool->Set_Parameter("FILE"  , File)
	||  !pTool->Set_Parameter("METHOD", 3   ) // RGB Composite
	||  !pTool->Execute() )
	{
		Error_Fmt("%s \'%s\'", _TL("failed to execute tool"), _TL("Import Image File"));

		SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

		return( false );
	}

	CSG_Grids *pImage = pTool->Get_Parameter("OUT_GRIDS")->asGrids();

	SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

	//--------------------------------------------------------
	if( (pTool = SG_Get_Tool_Library_Manager().Create_Tool("pj_georeference", 4)) == NULL // Direct Georeferencing of Airborne Photographs
	||  !pTool->Set_Manager(NULL)
	||  !pTool->Set_Parameter("INPUT"           , pImage)
	||  !pTool->Set_Parameter("CFL"             , CFL   )
	||  !pTool->Set_Parameter("PXSIZE"          , 2.41  ) 
	||  !pTool->Set_Parameter("KAPPA_OFF"       , Parameters["KAPPA_ADD"].asDouble())
	||  !pTool->Set_Parameter("DEM"             , Parameters["DEM"].asGrid  ())
	||  !pTool->Set_Parameter("DEM_DEFAULT"     , Parameters["DEM"].asDouble())
	||  !pTool->Set_Parameter("ORIENTATION"     , Parameters["ORIENTATION"].asInt())
	||  !pTool->Set_Parameter("X"               , Coordinate.x)
	||  !pTool->Set_Parameter("Y"               , Coordinate.y)
	||  !pTool->Set_Parameter("Z"               , Altitude)
	||  !pTool->Set_Parameter("KAPPA"           , -Yaw  ) // heading
	||  !pTool->Set_Parameter("OMEGA"           ,  Pitch)
	||  !pTool->Set_Parameter("PHI"             ,  Roll )
	||  !pTool->Set_Parameter("TARGET_USER_SIZE", Parameters["RESOLUTION"].asDouble())
	||  !pTool->Execute() )
	{
		Error_Fmt("%s \'%s\'", _TL("failed to execute tool"), _TL("Import Image File"));

		SG_Get_Tool_Library_Manager().Delete_Tool(pTool);

		return( false );
	}

	Data.Delete(pImage);

	pImage = pTool->Get_Parameter("OUTPUT")->asGridList()->Get_Item(0)->asGrids();

	Data.Delete(pImage, true);
	pImage->Get_Projection() = Projection;
	CEXIV_Helper::Get_MetaData(File, *pImage->Get_MetaData().Add_Child(), false);

	Parameters["IMAGES"].asGridList()->Add_Item(pImage);

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


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

//---------------------------------------------------------
#endif // HAVE_EXIV2
