Skip to content

Commit

Permalink
add density algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbruy authored and wonder-sk committed Mar 22, 2023
1 parent a068f9d commit 92e4bdd
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -425,6 +425,7 @@ if (WITH_PDAL)

processing/pdal/qgsalgorithmpdalboundary.cpp
processing/pdal/qgsalgorithmpdalconvertformat.cpp
processing/pdal/qgsalgorithmpdaldensity.cpp
processing/pdal/qgsalgorithmpdalfixprojection.cpp
processing/pdal/qgsalgorithmpdalinformation.cpp
processing/pdal/qgsalgorithmpdalreproject.cpp
Expand Down
115 changes: 115 additions & 0 deletions src/analysis/processing/pdal/qgsalgorithmpdaldensity.cpp
@@ -0,0 +1,115 @@
/***************************************************************************
qgsalgorithmpdaldensity.cpp
---------------------
begin : February 2023
copyright : (C) 2023 by Alexander Bruy
email : alexander dot bruy at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program 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. *
* *
***************************************************************************/

#include "qgsalgorithmpdaldensity.h"

#include "qgsrunprocess.h"
#include "qgspointcloudlayer.h"

///@cond PRIVATE

QString QgsPdalDensityAlgorithm::name() const
{
return QStringLiteral( "density" );
}

QString QgsPdalDensityAlgorithm::displayName() const
{
return QObject::tr( "Density" );
}

QString QgsPdalDensityAlgorithm::group() const
{
return QObject::tr( "Point cloud extraction" );
}

QString QgsPdalDensityAlgorithm::groupId() const
{
return QStringLiteral( "pointcloudextraction" );
}

QStringList QgsPdalDensityAlgorithm::tags() const
{
return QObject::tr( "cell,count,density,raster" ).split( ',' );
}

QString QgsPdalDensityAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm exports a raster file where each cell contains number of points that are in that cell's area." );
}

QgsPdalDensityAlgorithm *QgsPdalDensityAlgorithm::createInstance() const
{
return new QgsPdalDensityAlgorithm();
}

void QgsPdalDensityAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterPointCloudLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "RESOLUTION" ), QObject::tr( "Resoultion of the density raster" ), QgsProcessingParameterNumber::Integer, 1, false, 1 ) );
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TILE_SIZE" ), QObject::tr( "Tile size for parallel runs" ), QgsProcessingParameterNumber::Integer, 1000, false, 1 ) );

std::unique_ptr< QgsProcessingParameterNumber > paramOriginX = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "ORIGIN_X" ), QObject::tr( "X origin of a tile for parallel runs" ), QgsProcessingParameterNumber::Integer, QVariant(), true, 0 );
paramOriginX->setFlags( paramOriginX->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( paramOriginX.release() );
std::unique_ptr< QgsProcessingParameterNumber > paramOriginY = std::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "ORIGIN_Y" ), QObject::tr( "Y origin of a tile for parallel runs" ), QgsProcessingParameterNumber::Integer, QVariant(), true, 0 );
paramOriginY->setFlags( paramOriginY->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( paramOriginY.release() );

addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Density raster" ) ) );
}

QStringList QgsPdalDensityAlgorithm::createArgumentLists( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
Q_UNUSED( feedback );

QgsPointCloudLayer *layer = parameterAsPointCloudLayer( parameters, QStringLiteral( "INPUT" ), context );
if ( !layer )
throw QgsProcessingException( invalidPointCloudError( parameters, QStringLiteral( "INPUT" ) ) );

bool hasOriginX = parameters.value( QStringLiteral( "ORIGIN_X" ) ).isValid();
bool hasOriginY = parameters.value( QStringLiteral( "ORIGIN_Y" ) ).isValid();

if ( ( hasOriginX && !hasOriginY ) || ( !hasOriginX && hasOriginY ) )
{
throw QgsProcessingException( QObject::tr( "Specify both X and Y tile origin or don't set any of them." ) );
}

const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
setOutputValue( QStringLiteral( "OUTPUT" ), outputFile );

int resolution = parameterAsInt( parameters, QStringLiteral( "RESOLUTION" ), context );
int tileSize = parameterAsInt( parameters, QStringLiteral( "TILE_SIZE" ), context );

QStringList args = { QStringLiteral( "density" ),
QStringLiteral( "--input=%1" ).arg( layer->source() ),
QStringLiteral( "--output=%1" ).arg( outputFile ),
QStringLiteral( "--resolution=%1" ).arg( resolution ),
QStringLiteral( "--tile-size=%1" ).arg( tileSize )
};

if ( hasOriginX && hasOriginY )
{
args << QStringLiteral( "--tile-origin-x=%1" ).arg( parameterAsInt( parameters, QStringLiteral( "ORIGIN_X" ), context ) );
args << QStringLiteral( "--tile-origin-y=%1" ).arg( parameterAsInt( parameters, QStringLiteral( "ORIGIN_Y" ), context ) );
}

addThreadsParameter( args );
return args;
}

///@endcond
54 changes: 54 additions & 0 deletions src/analysis/processing/pdal/qgsalgorithmpdaldensity.h
@@ -0,0 +1,54 @@
/***************************************************************************
qgsalgorithmpdaldensity.h
---------------------
begin : February 2023
copyright : (C) 2023 by Alexander Bruy
email : alexander dot bruy at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program 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. *
* *
***************************************************************************/

#ifndef QGSALGORITHMPDALDENSITY_H
#define QGSALGORITHMPDALDENSITY_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgspdalalgorithmbase.h"

///@cond PRIVATE

/**
* Native point cloud density algorithm.
*/
class QgsPdalDensityAlgorithm : public QgsPdalAlgorithmBase
{

public:

QgsPdalDensityAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QString group() const override;
QString groupId() const override;
QStringList tags() const override;
QString shortHelpString() const override;
QgsPdalDensityAlgorithm *createInstance() const override SIP_FACTORY;

QStringList createArgumentLists( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

friend class TestQgsProcessingPdalAlgs;
};

///@endcond PRIVATE

#endif // QGSALGORITHMPDALDENSITY_H
2 changes: 2 additions & 0 deletions src/analysis/processing/pdal/qgspdalalgorithms.cpp
Expand Up @@ -21,6 +21,7 @@

#include "qgsalgorithmpdalboundary.h"
#include "qgsalgorithmpdalconvertformat.h"
#include "qgsalgorithmpdaldensity.h"
#include "qgsalgorithmpdalfixprojection.h"
#include "qgsalgorithmpdalinformation.h"
#include "qgsalgorithmpdalreproject.h"
Expand Down Expand Up @@ -83,6 +84,7 @@ void QgsPdalAlgorithms::loadAlgorithms()

addAlgorithm( new QgsPdalBoundaryAlgorithm() );
addAlgorithm( new QgsPdalConvertFormatAlgorithm() );
addAlgorithm( new QgsPdalDensityAlgorithm() );
addAlgorithm( new QgsPdalFixProjectionAlgorithm() );
addAlgorithm( new QgsPdalInformationAlgorithm() );
addAlgorithm( new QgsPdalReprojectAlgorithm() );
Expand Down
83 changes: 83 additions & 0 deletions tests/src/analysis/testqgsprocessingpdalalgs.cpp
Expand Up @@ -36,6 +36,7 @@ class TestQgsProcessingPdalAlgs: public QObject

void boundary();
void convertFormat();
void density();
void fixProjection();
void info();
void reproject();
Expand Down Expand Up @@ -280,5 +281,87 @@ void TestQgsProcessingPdalAlgs::boundary()
);
}

void TestQgsProcessingPdalAlgs::density()
{
QgsPdalAlgorithmBase *alg = const_cast<QgsPdalAlgorithmBase *>( static_cast< const QgsPdalAlgorithmBase * >( QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "pdal:density" ) ) ) );

std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();
context->setProject( QgsProject::instance() );

QgsProcessingFeedback feedback;

const QString outputFile = QDir::tempPath() + "/density.tif";
if ( QFile::exists( outputFile ) )
QFile::remove( outputFile );

QVariantMap parameters;
// defaults
parameters.insert( QStringLiteral( "INPUT" ), mPointCloudLayerPath );
parameters.insert( QStringLiteral( "OUTPUT" ), outputFile );

QStringList args = alg->createArgumentLists( parameters, *context, &feedback );
QCOMPARE( args, QStringList() << QStringLiteral( "density" )
<< QStringLiteral( "--input=%1" ).arg( mPointCloudLayerPath )
<< QStringLiteral( "--output=%1" ).arg( outputFile )
<< QStringLiteral( "--resolution=1" )
<< QStringLiteral( "--tile-size=1000" )
);

// change resolution
parameters.insert( QStringLiteral( "RESOLUTION" ), 100 );
args = alg->createArgumentLists( parameters, *context, &feedback );
QCOMPARE( args, QStringList() << QStringLiteral( "density" )
<< QStringLiteral( "--input=%1" ).arg( mPointCloudLayerPath )
<< QStringLiteral( "--output=%1" ).arg( outputFile )
<< QStringLiteral( "--resolution=100" )
<< QStringLiteral( "--tile-size=1000" )
);

// set tile size
parameters.insert( QStringLiteral( "TILE_SIZE" ), 100 );
args = alg->createArgumentLists( parameters, *context, &feedback );
QCOMPARE( args, QStringList() << QStringLiteral( "density" )
<< QStringLiteral( "--input=%1" ).arg( mPointCloudLayerPath )
<< QStringLiteral( "--output=%1" ).arg( outputFile )
<< QStringLiteral( "--resolution=100" )
<< QStringLiteral( "--tile-size=100" )
);

// set X tile origin
parameters.insert( QStringLiteral( "ORIGIN_X" ), 1 );
QVERIFY_EXCEPTION_THROWN( alg->createArgumentLists( parameters, *context, &feedback ), QgsProcessingException );

// set Y tile origin
parameters.remove( QStringLiteral( "ORIGIN_X" ) );
parameters.insert( QStringLiteral( "ORIGIN_Y" ), 10 );
QVERIFY_EXCEPTION_THROWN( alg->createArgumentLists( parameters, *context, &feedback ), QgsProcessingException );

// set both X and Y tile origin
parameters.insert( QStringLiteral( "ORIGIN_Y" ), 10 );
parameters.insert( QStringLiteral( "ORIGIN_X" ), 1 );
args = alg->createArgumentLists( parameters, *context, &feedback );
QCOMPARE( args, QStringList() << QStringLiteral( "density" )
<< QStringLiteral( "--input=%1" ).arg( mPointCloudLayerPath )
<< QStringLiteral( "--output=%1" ).arg( outputFile )
<< QStringLiteral( "--resolution=100" )
<< QStringLiteral( "--tile-size=100" )
<< QStringLiteral( "--tile-origin-x=1" )
<< QStringLiteral( "--tile-origin-y=10" )
);

// set max threads to 2, a --threads argument should be added
QgsSettings().setValue( QStringLiteral( "/Processing/Configuration/MAX_THREADS" ), 2 );
args = alg->createArgumentLists( parameters, *context, &feedback );
QCOMPARE( args, QStringList() << QStringLiteral( "density" )
<< QStringLiteral( "--input=%1" ).arg( mPointCloudLayerPath )
<< QStringLiteral( "--output=%1" ).arg( outputFile )
<< QStringLiteral( "--resolution=100" )
<< QStringLiteral( "--tile-size=100" )
<< QStringLiteral( "--tile-origin-x=1" )
<< QStringLiteral( "--tile-origin-y=10" )
<< QStringLiteral( "--threads=2" )
);
}

QGSTEST_MAIN( TestQgsProcessingPdalAlgs )
#include "testqgsprocessingpdalalgs.moc"

0 comments on commit 92e4bdd

Please sign in to comment.