
///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//                     Tool Library                      //
//                   pointcloud_tools                    //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//                   pc_csf_filter.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                                //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#include "pc_csf_filter.h"

#include "CSF.h"


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

//---------------------------------------------------------
CPC_CSF_Filter::CPC_CSF_Filter(void)
{
	Set_Name		(_TL("Cloth Simulation Filter"));

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

	Set_Description	(_TW(
		"Airborne LiDAR ground/off-ground filtering method based on Cloth Simulation (CSF) "
		"as proposed by Zhang et al. (2016) and implemented by Jianbo Qi. "
		"\n\n"
		"In 3D computer graphics cloth modeling is used for cloth simulation by fitting the "
		"shape of cloth as represented by a finite number of particles according to physical laws. "
		"The CSF is mainly controlled by four user-defined parameters (Zhang et al 2026):<ul> "
		"<li><b>cloth grid resolution</b>, which represents the horizontal distance between two neighboring particles</li>"
		"<li><b>time step</b>, which controls the displacement of particles from gravity during each iteration</li>"
		"<li><b>rigidness</b>, which controls the rigidness of the cloth</li>"
		"<li>a <b>slope smoothing</b> option, which indicates whether the handling of steep slopes is required during post-processing</li>"
		"</ul>"
		"\n\n"
		"In addition, two threshold parameters are used to aid the identification of ground points. "
		"The first is a <b>distance threshold</b>, that governs the final classification "
		"of the LiDAR points as either ground or off-ground based on the distances "
		"to the cloth grid. "
		"The second threshold parameter is the <b>height difference</b>, which is used during "
		"post-processing to determine whether a movable particle should be moved to the "
		"ground or not. "
	));

	Add_Reference("Zhang, W. , Qi, J., Wan,  P., Wang, H., Xie, D., Wang, X. & Yan, G.", "2016",
		"An Easy-to-Use Airborne LiDAR Data Filtering Method Based on Cloth Simulation",
		"Remote Sens., vol. 8, no. 6, p. 501.",
		SG_T("https://doi.org/10.3390/rs8060501"), SG_T("doi:10.3390/rs8060501")
	);

	Add_Reference(SG_T("https://github.com/jianboqi/CSF"), SG_T("CSF Github Repository"));

	//-----------------------------------------------------
	Parameters.Add_PointCloud("", "POINTS"    , _TL("Points"           ), _TL(""), PARAMETER_INPUT);

	Parameters.Add_PointCloud("", "GROUND"    , _TL("Ground Points"    ), _TL(""), PARAMETER_OUTPUT);
	Parameters.Add_PointCloud("", "OFF_GROUND", _TL("Off-Ground Points"), _TL(""), PARAMETER_OUTPUT);

	Parameters.Add_Table_Field("POINTS", "FIELD", _TL("Field"), _TL(""), true);

	Parameters.Add_Choice("", "OUTPUT", _TL("Output"), _TL(""), CSG_String::Format("%s|%s|%s|%s|%s",
		_TL("attribute field"),
		_TL("delete off-ground points"),
		_TL("ground points"),
		_TL("off-ground points"),
		_TL("ground and off-ground points")), 0
	);

	Parameters.Add_Choice("OUTPUT", "ASPRS_LAS", _TL("Classification"), _TL(""), CSG_String::Format("%s|%s",
		SG_T("0=off-ground, 1=ground"),
		SG_T("ASPRS LAS Standard")), 1
	);

	Parameters.Add_Double("", "cloth_resolution", _TL("Cloth Grid Resolution"), _TL(""),   0.5 , 0., true);
	Parameters.Add_Double("", "time_step"       , _TL("Time Step"            ), _TL(""),   0.65, 0., true);
	Parameters.Add_Int   ("", "rigidness"       , _TL("Rigidness"            ), _TL(""),   3   , 1 , true);
	Parameters.Add_Bool  ("", "bSlopeSmooth"    , _TL("Slope Smoothing"      ), _TL(""),             true);
	Parameters.Add_Double("", "class_threshold" , _TL("Distance Threshold"   ), _TL(""),   0.5 , 0., true);
	Parameters.Add_Double("", "height_threshold", _TL("Height Difference"    ), _TL(""),   0.3 , 0., true);
	Parameters.Add_Int   ("", "iterations"      , _TL("Maximum Iterations"   ), _TL(""), 500   , 1 , true);
}


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

//---------------------------------------------------------
int CPC_CSF_Filter::On_Parameters_Enable(CSG_Parameters *pParameters, CSG_Parameter *pParameter)
{
	if( pParameter->Cmp_Identifier("OUTPUT") )
	{
		pParameters->Set_Enabled("FIELD"     , pParameter->asInt() == 0);
		pParameters->Set_Enabled("ASPRS_LAS" , pParameter->asInt() == 0);
		pParameters->Set_Enabled("GROUND"    , pParameter->asInt() == 2 || pParameter->asInt() == 4);
		pParameters->Set_Enabled("OFF_GROUND", pParameter->asInt() == 3 || pParameter->asInt() == 4);
	}

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


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

//---------------------------------------------------------
bool CPC_CSF_Filter::On_Execute(void)
{
	CSG_PointCloud *pPoints = Parameters("POINTS")->asPointCloud();

	CSF csf; csf.setPointCloud(pPoints);

	//step 2 parameter setting
	csf.params.cloth_resolution = Parameters["cloth_resolution"].asDouble();
	csf.params.time_step        = Parameters["time_step"       ].asDouble();
	csf.params.rigidness        = Parameters["rigidness"       ].asInt   ();
	csf.params.bSlopeSmooth     = Parameters["bSlopeSmooth"    ].asBool  ();
	csf.params.class_threshold  = Parameters["class_threshold" ].asDouble();
	csf.params.height_threshold = Parameters["height_threshold"].asDouble();
	csf.params.iterations       = Parameters["iterations"      ].asInt   ();

	//step3 do filtering
	std::vector<int> Ground, offGround;

	csf.do_filtering(Ground, offGround);

	//-----------------------------------------------------
	switch( Parameters["OUTPUT"].asInt() )
	{
	case  0: {
		int Field = Parameters["FIELD"].asInt();

		if( Field < 0 )
		{
			Field = pPoints->Get_Field_Count();

			pPoints->Add_Field("GROUND", SG_DATATYPE_Char);
		}

		double Class_Off = Parameters["ASPRS_LAS"].asInt() ? 1. : 0.;
		double Class_On  = Parameters["ASPRS_LAS"].asInt() ? 2. : 1.;

		for(std::size_t i=0; i<offGround.size(); i++) { pPoints->Set_Value((sLong)offGround[i], Field, Class_Off); }
		for(std::size_t i=0; i<   Ground.size(); i++) { pPoints->Set_Value((sLong)   Ground[i], Field, Class_On ); }

		DataObject_Update(pPoints);
		break; }

	case  1:
		pPoints->Select(); // clear selection
		for(std::size_t i=0; i<offGround.size(); i++) { pPoints->Select((sLong)offGround[i], true); }
		pPoints->Del_Selection();
		DataObject_Update(pPoints);
		break;

	case  2:
		Set_Points(pPoints, "GROUND"    ,    Ground);
		break;

	case  3:
		Set_Points(pPoints, "OFF_GROUND", offGround);
		break;

	case  4:
		Set_Points(pPoints, "GROUND"    ,    Ground);
		Set_Points(pPoints, "OFF_GROUND", offGround);
		break;
	}

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


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

//---------------------------------------------------------
bool CPC_CSF_Filter::Set_Points(CSG_PointCloud *pInput, const CSG_String &Id, std::vector<int>& Index)
{
	CSG_PointCloud *pOutput = Parameters(Id) ? Parameters[Id].asPointCloud() : NULL;

	if( pOutput )
	{
		pOutput->Create(pInput); pOutput->Fmt_Name("%s [%s]", pInput->Get_Name(), Id.Cmp("GROUND") == 0 ? SG_T("Ground") : SG_T("Off-Ground"));

		for(std::size_t i=0; i<Index.size() && Set_Progress((sLong)i, (sLong)Index.size()); i++)
		{
			pOutput->Add_Shape(pInput->Get_Record((sLong)Index[i]));
		}

		return( true );
	}

	return( false );
}


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

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