/**********************************************************
 * Version $Id: gpx.cpp 1921 2014-01-09 10:24:11Z oconrad $
 *********************************************************/

///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//                     Tool Library                      //
//                       io_shapes                       //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//                        gpx.cpp                        //
//                                                       //
//                 Copyright (C) 2009 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                                //
//                                                       //
///////////////////////////////////////////////////////////

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


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

//---------------------------------------------------------
#include "gpx.h"


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

//---------------------------------------------------------
CGPX_Import::CGPX_Import(void)
{
	Set_Name		(_TL("Import GPX"));

	Set_Author		(SG_T("O. Conrad (c) 2009, J. Spitzmüller (c) 2025"));
	
	Set_Version 	("1.1");

	Set_Description	(_TW(
		"The tool imports GPS data from the GPS eXchange format GPX."
	));
	
	Add_Reference( "http://www.topografix.com/", SG_T("The GPS Exchange Format"));

	//-----------------------------------------------------
	Parameters.Add_Shapes_List(
		NULL	, "SHAPES"			, _TL("GPX Import"),
		_TL(""),
		PARAMETER_OUTPUT_OPTIONAL
	);
	
	m_CRS.Create(Parameters, "");

	Parameters.Add_FilePath(
		NULL	, "FILE"			, _TL("File"),
		_TL(""),
		CSG_String::Format(SG_T("%s|%s|%s|%s"),
			_TL("GPS Exchange Format (*.gpx)")	, SG_T("*.gpx"),
			_TL("All Files")					, SG_T("*.*")
		), NULL, false
 	);

	Parameters.Add_Value(
		NULL	, "TIME"			, _TL("Time Stamp without date"),
		_TL(""),
		PARAMETER_TYPE_Bool, true
 	);
}

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

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

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

	//if( has_GUI() )
	//{
	//	Parameters.Set_Parameter("SHAPES", DATAOBJECT_CREATE);
	//}

	return( CSG_Tool::On_Before_Execution() );
}

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

	return( CSG_Tool::On_After_Execution() );
}

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

//---------------------------------------------------------
bool CGPX_Import::On_Execute(void)
{
	CSG_Shapes		*pWay;
	CSG_MetaData	GPX;

	//-----------------------------------------------------
	m_Name		= Parameters("FILE")	->asString();
	m_pShapes	= Parameters("SHAPES")	->asShapesList();
	m_bTime		= Parameters("TIME")	->asBool();

	//-----------------------------------------------------
	if( !GPX.Create(m_Name) || GPX.Get_Name().CmpNoCase(SG_T("gpx")) )
	{
		return( false );
	}

	//-----------------------------------------------------
	pWay		= SG_Create_Shapes(SHAPE_TYPE_Point, m_Name);

	m_Name		= SG_File_Get_Name(m_Name, false);

	m_pShapes->Del_Items();
	
	//-----------------------------------------------------
	m_CRS.Get_CRS(pWay->Get_Projection(), true);
	
	if(pWay->Get_Projection().Get_Type() == ESG_CRS_Type::Undefined )
	{
		pWay->Get_Projection().Set_GCS_WGS84();
	}

	m_Target = pWay->Get_Projection();
	m_GPS.Set_GCS_WGS84();

	//-----------------------------------------------------
	for(int i=0; i<GPX.Get_Children_Count(); i++)
	{
		CSG_MetaData	*pChild	= GPX.Get_Child(i);

		if( 	 pChild->Get_Name().CmpNoCase(SG_T("wpt")) == 0 )
		{
			Add_Point(pChild, pWay);
		}
		else if( pChild->Get_Name().CmpNoCase(SG_T("rte")) == 0 )
		{
			Add_Route(pChild);
		}
		else if( pChild->Get_Name().CmpNoCase(SG_T("trk")) == 0 )
		{
			Add_Track(pChild);
		}
	}

	//-----------------------------------------------------
	if( pWay->Get_Count() > 0 )
	{
		m_pShapes->Add_Item(pWay);
	}
	else
	{
		delete(pWay);
	}

	return( m_pShapes->Get_Item_Count() > 0 );
}


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

//---------------------------------------------------------
#define GET_CONTENT(node, child, default)	(node->Get_Child(SG_T(child)) ? node->Get_Child(SG_T(child))->Get_Content() : SG_T(default))

//---------------------------------------------------------
bool CGPX_Import::Add_Route(CSG_MetaData *pRoute)
{
	// <name>	xsd:string
	// <cmt>	xsd:string
	// <desc>	xsd:string
	// <src>	xsd:string
	// <link>	linkType
	// <number> xsd:nonNegativeInteger
	// <type>	xsd:string

	CSG_String	Name(GET_CONTENT(pRoute, "name", "Route"));

	CSG_Shapes	*pPoints	= SG_Create_Shapes(SHAPE_TYPE_Point, CSG_String::Format(SG_T("%s [%s]"), m_Name.c_str(), Name.c_str()));

	m_pShapes->Add_Item(pPoints);

	for(int i=0; i<pRoute->Get_Children_Count(); i++)
	{
		CSG_MetaData	*pChild	= pRoute->Get_Child(i);

		if( pChild->Get_Name().CmpNoCase(SG_T("rtept")) == 0 )
		{
			Add_Point(pChild, pPoints);
		}
	}

	return( true );
}

//---------------------------------------------------------
bool CGPX_Import::Add_Track(CSG_MetaData *pTrack)
{
	// <name>	xsd:string
	// <cmt>	xsd:string
	// <desc>	xsd:string
	// <src>	xsd:string
	// <link>	linkType
	// <number> xsd:nonNegativeInteger
	// <type>	xsd:string

	CSG_MetaData	*pSegment	= pTrack->Get_Child(SG_T("trkseg"));

	if( pSegment )
	{
		CSG_String	Name(GET_CONTENT(pTrack, "name", "Track Segment"));

		CSG_Shapes	*pPoints	= SG_Create_Shapes(SHAPE_TYPE_Point, CSG_String::Format(SG_T("%s [%s]"), m_Name.c_str(), Name.c_str()));
	
		m_CRS.Get_CRS(pPoints->Get_Projection(), true);
		if( pPoints->Get_Projection().Get_Type() == ESG_CRS_Type::Undefined )
			pPoints->Get_Projection().Set_GCS_WGS84();

		m_Target = pPoints->Get_Projection();

		m_pShapes->Add_Item(pPoints);

		for(int i=0; i<pSegment->Get_Children_Count(); i++)
		{
			CSG_MetaData	*pChild	= pSegment->Get_Child(i);

			if( pChild->Get_Name().CmpNoCase(SG_T("trkpt")) == 0 )
			{
				Add_Point(pChild, pPoints);
			}
		}

		return( true );
	}

	return( false );
}


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

//---------------------------------------------------------
#define ADD_FIELD(key, type)	if( pNode->Get_Child(SG_T(key)) )	{	pPoints->Add_Field(SG_T(key), type);	}

inline bool CGPX_Import::Add_Fields(CSG_MetaData *pNode, CSG_Shapes *pPoints)
{
	if( pPoints->Get_Field_Count() == 0 )
	{
		ADD_FIELD("ele"				, SG_DATATYPE_Double);	// <ele>			xsd:decimal				(Höhe)
		ADD_FIELD("time"			, SG_DATATYPE_String);	// <time>			xsd:dateTime			(Datum und Zeit)
		ADD_FIELD("magvar"			, SG_DATATYPE_Double);	// <magvar>			degreesType				(lokale magn. Missweisung)
		ADD_FIELD("geoidheight"		, SG_DATATYPE_Double);	// <geoidheight>	xsd:decimal				(Höhe bezogen auf Geoid)
		ADD_FIELD("name"			, SG_DATATYPE_String);	// <name>			xsd:string				(Beschreibung)
		ADD_FIELD("cmt"				, SG_DATATYPE_String);	// <cmt>			xsd:string				(Kommentar)
		ADD_FIELD("desc"			, SG_DATATYPE_String);	// <desc>			xsd:string				(Elementbeschreibung)
		ADD_FIELD("src"				, SG_DATATYPE_String);	// <src>			xsd:string				(Quelle)
		ADD_FIELD("link"			, SG_DATATYPE_String);	// <link>			linkType				(Link)
		ADD_FIELD("sym"				, SG_DATATYPE_String);	// <sym>			xsd:string				(Darstellungssymbol)
		ADD_FIELD("type"			, SG_DATATYPE_String);	// <type>			xsd:string				(Klassifikation)
		ADD_FIELD("fix"				, SG_DATATYPE_Double);	// <fix>			fixType
		ADD_FIELD("sat"				, SG_DATATYPE_Int   );	// <sat>			xsd:nonNegativeInteger	(Anzahl der empfangenen Satelliten)
		ADD_FIELD("hdop"			, SG_DATATYPE_Double);	// <hdop>			xsd:decimal				(hDOP)
		ADD_FIELD("vdop"			, SG_DATATYPE_Double);	// <vdop>			xsd:decimal				(vDOP)
		ADD_FIELD("pdop"			, SG_DATATYPE_Double);	// <pdop>			xsd:decimal				(3D DOP)
		ADD_FIELD("ageofdgpsdata"	, SG_DATATYPE_Double);	// <ageofdgpsdata>	xsd:decimal				(Letzter DGPS update)
		ADD_FIELD("dgpsid"			, SG_DATATYPE_Int   );	// <dgpsid>			dgpsStationType			(DGPS ID)

		if( m_bTime && pNode->Get_Child(SG_T("time")) )
		{
			pPoints->Add_Field(SG_T("dtime"), SG_DATATYPE_Double);
		}
		else
		{
			m_bTime	= false;
		}
	}

	return( pPoints->Get_Field_Count() > 0 );
}

//---------------------------------------------------------
bool CGPX_Import::Add_Point(CSG_MetaData *pNode, CSG_Shapes *pPoints)
{
	const SG_Char	*cString;
	TSG_Point		Point;

	if(	(cString = pNode->Get_Property(SG_T("lon"))) != NULL && CSG_String(cString).asDouble(Point.x)
	&&	(cString = pNode->Get_Property(SG_T("lat"))) != NULL && CSG_String(cString).asDouble(Point.y)
	&&	Add_Fields(pNode, pPoints) )
	{
		SG_Get_Projected(m_GPS, m_Target, Point);
		CSG_Shape	*pPoint	= pPoints->Add_Shape();

		pPoint->Add_Point(Point, 0);

		for(int i=0; i<pNode->Get_Children_Count(); i++)
		{
			CSG_MetaData	*pChild	= pNode->Get_Child(i);

			pPoint->Set_Value(pChild->Get_Name(), pChild->Get_Content());
		}

		if( m_bTime )
		{
			CSG_DateTime dt; 
			dt.Parse_Format(pPoint->asString(SG_T("time")), "%FT%TZ");

			pPoint->Set_Value(SG_T("dtime"), dt.Get_Hour() + dt.Get_Minute() / 60.0 + dt.Get_Second() / 3600.0);
		}

		return( true );
	}

	return( false );
}


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

//---------------------------------------------------------
CGPX_Export::CGPX_Export(void)
{
	Set_Name		(_TL("Export GPX"));

	Set_Author		(SG_T("O. Conrad (c) 2009, J. Spitzmüller (c) 2025"));

	Set_Version 	("1.1");

	Set_Description	(_TW(
		"The tool exports vector data points to  the GPS eXchange format GPX."
		
	));
	
	Add_Reference( "http://www.topografix.com/", SG_T("The GPS Exchange Format"));

	//-----------------------------------------------------
	CSG_Parameter	*pNode	= Parameters.Add_Shapes(
		NULL	, "SHAPES"			, _TL("Shapes"),
		_TL(""),
		PARAMETER_INPUT
	);

	Parameters.Add_FilePath(
		NULL	, "FILE"			, _TL("File"),
		_TL(""),
		CSG_String::Format(SG_T("%s|%s|%s|%s"),
			_TL("GPS Exchange Format (*.gpx)")	, SG_T("*.gpx"),
			_TL("All Files")					, SG_T("*.*")
		), NULL, true
 	);



	Parameters.Add_Table_Field(
		pNode	, "ELE"				, _TL("Elevation"),
		_TL(""),
		true
	);

	Parameters.Add_Table_Field(
		pNode	, "NAME"			, _TL("Name"),
		_TL(""),
		true
	);

	Parameters.Add_Table_Field(
		pNode	, "CMT"				, _TL("Comment"),
		_TL(""),
		true
	);

	Parameters.Add_Table_Field(
		pNode	, "DESC"			, _TL("Description"),
		_TL(""),
		true
	);
	
	Parameters.Add_Table_Field(
		pNode	, "TIME"			, _TL("Time"),
		_TL(""),
		true
	);
	
	Parameters.Add_Choice("",
		"EXPORT", _TL("Export as"),
		_TL(""),
		CSG_String::Format("%s|%s",
			_TL("Waypoints"),
			_TL("Track")
		)
	);
	
	Parameters.Add_Choice("",
		"TRACKS", _TL("New Track Segment"),
		_TL(""),
		CSG_String::Format("%s|%s|%s",
			_TL("All Points in One Segment"),
			_TL("New Segment for Each Shape"),
			_TL("New Segment for Each Part")
		)
	);
	
	Parameters.Add_Choice("TIME",
		"TIME_FORMAT", _TL("Time Format"),
		_TL(""),
		CSG_String::Format("%s|%s|%s|%s|%s",
			_TL("Time is already ISO 8601 Zulu"),
			_TL("SAGA Date Field Type"),
			_TL("Julian Day Number (JDN)"),
			_TL("Modified Julian Day Number (MJD)"),
			_TL("Unix Timestamp")
		)
	);
}

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

//---------------------------------------------------------
int CGPX_Export::On_Parameters_Enable(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	if( pParameter->Cmp_Identifier("TIME" ) )
	{
		pParameters->Set_Enabled("TIME_FORMAT", 		pParameter->asInt() >= -1 );
	}

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


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

//---------------------------------------------------------
bool CGPX_Export::On_Execute(void)
{
	//-----------------------------------------------------
	CSG_String File	= Parameters("FILE")->asString();
	CSG_Shapes *pShapes	= Parameters("SHAPES")->asShapes();

	int iEle		= Parameters("ELE")			->asInt();	if( iEle  >= pShapes->Get_Field_Count() )	iEle	= -1;
	int iName		= Parameters("NAME")		->asInt();	if( iName >= pShapes->Get_Field_Count() )	iName	= -1;
	int iCmt		= Parameters("CMT")			->asInt();	if( iCmt  >= pShapes->Get_Field_Count() )	iCmt	= -1;
	int iDesc		= Parameters("DESC")		->asInt();	if( iDesc >= pShapes->Get_Field_Count() )	iDesc	= -1;
	int iTime		= Parameters("TIME")		->asInt();	if( iTime >= pShapes->Get_Field_Count() ) 	iTime	= -1; 
	int iFormat 	= Parameters("TIME_FORMAT") ->asInt();

	//-----------------------------------------------------
	int Export 		= Parameters("EXPORT")->asInt();
	int Tracks 		= Parameters("TRACKS")->asInt();

	//-----------------------------------------------------
	CSG_MetaData	GPX;
	GPX.Set_Name(SG_T("gpx"));
	GPX.Add_Property(SG_T("version")			, SG_T("1.0"));
	GPX.Add_Property(SG_T("creator")			, SG_T("SAGA - System for Automated Geoscientific Analyses - http://www.saga-gis.org"));
	GPX.Add_Property(SG_T("xmlns:xsi")			, SG_T("http://www.w3.org/2001/XMLSchema-instance"));
	GPX.Add_Property(SG_T("xmlns")				, SG_T("http://www.topografix.com/GPX/1/0"));
	GPX.Add_Property(SG_T("xsi:schemaLocation")	, SG_T("http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"));

	//if( pShapes->Get_Projection().Get_Type() == ESG_CRS_Type::Undefined )
	//	pShapes->Get_Projection().Set_GCS_WGS84();

	CSG_Projection Source = pShapes->Get_Projection();
	CSG_Projection Target; Target.Set_GCS_WGS84();

	CSG_MetaData *pTrack = NULL, *pSegment = NULL;
	if( Export == 1 ) pTrack = GPX.Add_Child(SG_T("trk"));	
	if( Tracks == 0 ) pSegment = pTrack->Add_Child(SG_T("trkseg"));

	for(int iShape=0; iShape<pShapes->Get_Count(); iShape++)
	{
		CSG_Shape	*pShape	= pShapes->Get_Shape(iShape);

		if( Tracks == 1 ) pSegment = pTrack->Add_Child(SG_T("trkseg"));

		for(int iPart=0; iPart<pShape->Get_Part_Count(); iPart++)
		{
			if( Tracks == 2 ) pSegment = pTrack->Add_Child(SG_T("trkseg"));

			for(int iPoint=0; iPoint<pShape->Get_Point_Count(iPart); iPoint++)
			{
				CSG_MetaData *pPoint = Export == 0 ? GPX.Add_Child(SG_T("wpt")) : pSegment->Add_Child(SG_T("trkpt"));

				TSG_Point Point = pShape->Get_Point(iPoint, iPart);
				SG_Get_Projected( Source, Target, Point ); 

				pPoint->Add_Property(SG_T("lon"), Point.x);
				pPoint->Add_Property(SG_T("lat"), Point.y);

				if( iEle  > -1 ) pPoint->Add_Child(SG_T("ele" ), pShape->asString(iEle));
				if( iName > -1 ) pPoint->Add_Child(SG_T("name"), pShape->asString(iName));
				if( iCmt  > -1 ) pPoint->Add_Child(SG_T("cmt" ), pShape->asString(iCmt));
				if( iDesc > -1 ) pPoint->Add_Child(SG_T("desc"), pShape->asString(iDesc));
				if( iTime > -1 )
				{
					CSG_DateTime TP;
					switch( iFormat )
	   				{
						case 0: TP.Parse_Format	( pShape->asString(iTime), "%FT%TZ" ); 					break;
						case 1: TP.Parse_Format	( pShape->asString(iTime), "%F" 	); TP.Reset_Time(); break;
						case 2: TP.Set			( pShape->asDouble(iTime) 			).Make_UTC(); 		break;
						case 3: TP.Set			( pShape->asDouble(iTime)+2400000.5 ).Make_UTC(); 		break;
						case 4: TP.Set_Unix_Time( pShape->asLong  (iTime)  			); 					break;
					}

					pPoint->Add_Child(SG_T("time"), TP.Format("%Y-%m-%dT%H:%M:%SZ"));
				}
			}
		}
	}

	//-----------------------------------------------------
	if( !GPX.Save(File) )
	{
		return( false );
	}

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


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

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