Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Processing: Add Check Geometry algorithm and test: Follow Boundaries #60789

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmfixgeometrymissingvertex.cpp
processing/qgsalgorithmcheckgeometryarea.cpp
processing/qgsalgorithmfixgeometryarea.cpp
processing/qgsalgorithmcheckgeometryfollowboundaries.cpp
processing/qgsalgorithmclip.cpp
processing/qgsalgorithmconcavehull.cpp
processing/qgsalgorithmconditionalbranch.cpp
Expand Down
214 changes: 214 additions & 0 deletions src/analysis/processing/qgsalgorithmcheckgeometryfollowboundaries.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/***************************************************************************
qgsalgorithmcheckgeometryfollowboundaries.cpp
---------------------
begin : February 2025
copyright : (C) 2025 by Jacky Volpes
email : jacky dot volpes at oslandia 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 "qgsalgorithmcheckgeometryfollowboundaries.h"
#include "qgsgeometrycheckcontext.h"
#include "qgsgeometrycheckerror.h"
#include "qgsgeometryfollowboundariescheck.h"
#include "qgspoint.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataproviderfeaturepool.h"

///@cond PRIVATE

QString QgsGeometryCheckFollowBoundariesAlgorithm::name() const
{
return QStringLiteral( "checkgeometryfollowboundaries" );
}

QString QgsGeometryCheckFollowBoundariesAlgorithm::displayName() const
{
return QObject::tr( "Check Geometry (Follow Boundaries)" );
}

QStringList QgsGeometryCheckFollowBoundariesAlgorithm::tags() const
{
return QObject::tr( "check,geometry,follow,boundaries" ).split( ',' );
}

QString QgsGeometryCheckFollowBoundariesAlgorithm::group() const
{
return QObject::tr( "Check geometry" );
}

QString QgsGeometryCheckFollowBoundariesAlgorithm::groupId() const
{
return QStringLiteral( "checkgeometry" );
}

QString QgsGeometryCheckFollowBoundariesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm checks if the polygons follow the boundaries of the reference layer." );
}

Qgis::ProcessingAlgorithmFlags QgsGeometryCheckFollowBoundariesAlgorithm::flags() const
{
return QgsProcessingAlgorithm::flags() | Qgis::ProcessingAlgorithmFlag::NoThreading;
}

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

void QgsGeometryCheckFollowBoundariesAlgorithm::initAlgorithm( const QVariantMap &configuration )
{
Q_UNUSED( configuration )

addParameter( new QgsProcessingParameterVectorLayer(
QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPolygon )
) );
addParameter( new QgsProcessingParameterFeatureSink(
QStringLiteral( "ERRORS" ), QObject::tr( "Errors layer" ), Qgis::ProcessingSourceType::VectorPoint
) );
addParameter( new QgsProcessingParameterFeatureSink(
QStringLiteral( "OUTPUT" ), QObject::tr( "Output layer" ), Qgis::ProcessingSourceType::VectorPolygon
) );

addParameter( new QgsProcessingParameterVectorLayer(
QStringLiteral( "REF_LAYER" ), QObject::tr( "Reference layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPolygon )
) );

std::unique_ptr< QgsProcessingParameterNumber > tolerance = std::make_unique< QgsProcessingParameterNumber >(
QStringLiteral( "TOLERANCE" ), QObject::tr( "Tolerance" ), Qgis::ProcessingNumberParameterType::Integer, 8, false, 1, 13
);
tolerance->setFlags( tolerance->flags() | Qgis::ProcessingParameterFlag::Advanced );
addParameter( tolerance.release() );
}

bool QgsGeometryCheckFollowBoundariesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mTolerance = parameterAsInt( parameters, QStringLiteral( "TOLERANCE" ), context );

return true;
}

QgsFeaturePool *QgsGeometryCheckFollowBoundariesAlgorithm::createFeaturePool( QgsVectorLayer *layer, bool selectedOnly ) const
{
return new QgsVectorDataProviderFeaturePool( layer, selectedOnly );
}

static QgsFields outputFields()
{
QgsFields fields;
fields.append( QgsField( QStringLiteral( "gc_layerid" ), QMetaType::QString ) );
fields.append( QgsField( QStringLiteral( "gc_layername" ), QMetaType::QString ) );
fields.append( QgsField( QStringLiteral( "gc_featid" ), QMetaType::Int ) );
fields.append( QgsField( QStringLiteral( "gc_partidx" ), QMetaType::Int ) );
fields.append( QgsField( QStringLiteral( "gc_ringidx" ), QMetaType::Int ) );
fields.append( QgsField( QStringLiteral( "gc_vertidx" ), QMetaType::Int ) );
fields.append( QgsField( QStringLiteral( "gc_errorx" ), QMetaType::Double ) );
fields.append( QgsField( QStringLiteral( "gc_errory" ), QMetaType::Double ) );
fields.append( QgsField( QStringLiteral( "gc_error" ), QMetaType::QString ) );
return fields;
}


QVariantMap QgsGeometryCheckFollowBoundariesAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QString dest_output;
QString dest_errors;
QgsVectorLayer *inputLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );

QgsFields fields = outputFields();

std::unique_ptr< QgsFeatureSink > sink_output( parameterAsSink(
parameters, QStringLiteral( "OUTPUT" ), context, dest_output, fields, inputLayer->wkbType(), inputLayer->sourceCrs()
) );
if ( !sink_output )
{
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
}
std::unique_ptr< QgsFeatureSink > sink_errors( parameterAsSink(
parameters, QStringLiteral( "ERRORS" ), context, dest_errors, fields, Qgis::WkbType::Point, inputLayer->sourceCrs()
) );
if ( !sink_errors )
{
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "ERRORS" ) ) );
}

QgsProcessingMultiStepFeedback multiStepFeedback( 3, feedback );

QgsProject *project = inputLayer->project() ? inputLayer->project() : QgsProject::instance();

std::unique_ptr<QgsGeometryCheckContext> checkContext = std::make_unique<QgsGeometryCheckContext>(
mTolerance, inputLayer->sourceCrs(), project->transformContext(), project
);

// Test detection
QList<QgsGeometryCheckError *> checkErrors;
QStringList messages;

const QgsGeometryFollowBoundariesCheck check(
checkContext.get(), QVariantMap(), parameterAsVectorLayer( parameters, QStringLiteral( "REF_LAYER" ), context )
);

multiStepFeedback.setCurrentStep( 1 );
feedback->setProgressText( QObject::tr( "Preparing features…" ) );
QMap<QString, QgsFeaturePool *> featurePools;
featurePools.insert( inputLayer->id(), createFeaturePool( inputLayer ) );

multiStepFeedback.setCurrentStep( 2 );
feedback->setProgressText( QObject::tr( "Collecting errors…" ) );
check.collectErrors( featurePools, checkErrors, messages, feedback );

multiStepFeedback.setCurrentStep( 3 );
feedback->setProgressText( QObject::tr( "Exporting errors…" ) );
double step { checkErrors.size() > 0 ? 100.0 / checkErrors.size() : 1 };
long i = 0;
feedback->setProgress( 0.0 );

for ( QgsGeometryCheckError *error : checkErrors )
{
if ( feedback->isCanceled() )
{
break;
}
QgsFeature f;
QgsAttributes attrs = f.attributes();

attrs << error->layerId()
<< inputLayer->name()
<< error->featureId()
<< error->vidx().part
<< error->vidx().ring
<< error->vidx().vertex
<< error->location().x()
<< error->location().y()
<< error->value().toString();
f.setAttributes( attrs );

f.setGeometry( error->geometry() );
if ( !sink_output->addFeature( f, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink_output.get(), parameters, QStringLiteral( "OUTPUT" ) ) );

f.setGeometry( QgsGeometry::fromPoint( QgsPoint( error->location().x(), error->location().y() ) ) );
if ( !sink_errors->addFeature( f, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink_errors.get(), parameters, QStringLiteral( "ERRORS" ) ) );

i++;
feedback->setProgress( 100.0 * step * static_cast<double>( i ) );
}

QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest_output );
outputs.insert( QStringLiteral( "ERRORS" ), dest_errors );

return outputs;
}

///@endcond
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/***************************************************************************
qgsalgorithmcheckgeometryfollowboundaries.h
---------------------
begin : February 2025
copyright : (C) 2025 by Jacky Volpes
email : jacky dot volpes at oslandia 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 QGSALGORITHMCHECKGEOMETRYFOLLOWBOUNDARIES_H
#define QGSALGORITHMCHECKGEOMETRYFOLLOWBOUNDARIES_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"
#include "qgsfeaturepool.h"

///@cond PRIVATE

class QgsGeometryCheckFollowBoundariesAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsGeometryCheckFollowBoundariesAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
Qgis::ProcessingAlgorithmFlags flags() const override;
QgsGeometryCheckFollowBoundariesAlgorithm *createInstance() const override SIP_FACTORY;

protected:
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QVariantMap processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

private:
QgsFeaturePool *createFeaturePool( QgsVectorLayer *layer, bool selectedOnly = false ) const;
int mTolerance { 8 };
};

///@endcond PRIVATE

#endif // QGSALGORITHMCHECKGEOMETRYFOLLOWBOUNDARIES_H
2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "qgsalgorithmfixgeometrymissingvertex.h"
#include "qgsalgorithmcheckgeometryhole.h"
#include "qgsalgorithmcheckgeometrymissingvertex.h"
#include "qgsalgorithmcheckgeometryfollowboundaries.h"
#include "qgsalgorithmclip.h"
#include "qgsalgorithmconcavehull.h"
#include "qgsalgorithmconditionalbranch.h"
Expand Down Expand Up @@ -335,6 +336,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsGeometryCheckAreaAlgorithm() );
addAlgorithm( new QgsGeometryCheckHoleAlgorithm() );
addAlgorithm( new QgsGeometryCheckMissingVertexAlgorithm() );
addAlgorithm( new QgsGeometryCheckFollowBoundariesAlgorithm() );
addAlgorithm( new QgsClipAlgorithm() );
addAlgorithm( new QgsCollectAlgorithm() );
addAlgorithm( new QgsCombineStylesAlgorithm() );
Expand Down
35 changes: 35 additions & 0 deletions tests/src/analysis/testqgsprocessingcheckgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class TestQgsProcessingCheckGeometry : public QgsTest
void angleAlg_data();
void angleAlg();

void followBoundariesAlg();

void areaAlg();
void holeAlg();
void missingVertexAlg();
Expand Down Expand Up @@ -219,5 +221,38 @@ void TestQgsProcessingCheckGeometry::missingVertexAlg()
QCOMPARE( errorsLayer->featureCount(), 5 );
}

void TestQgsProcessingCheckGeometry::followBoundariesAlg()
{
std::unique_ptr< QgsProcessingAlgorithm > alg(
QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:checkgeometryfollowboundaries" ) )
);
QVERIFY( alg != nullptr );

const QDir testDataDir( QDir( TEST_DATA_DIR ).absoluteFilePath( "geometry_checker" ) );
QgsVectorLayer *polygonLayer = new QgsVectorLayer( testDataDir.absoluteFilePath( "follow_subj.shp" ), QStringLiteral( "polygons" ), QStringLiteral( "ogr" ) );
QgsVectorLayer *refLayer = new QgsVectorLayer( testDataDir.absoluteFilePath( "follow_ref.shp" ), QStringLiteral( "ref_polygons" ), QStringLiteral( "ogr" ) );

QVariantMap parameters;
parameters.insert( QStringLiteral( "INPUT" ), QVariant::fromValue( polygonLayer ) );
parameters.insert( QStringLiteral( "REF_LAYER" ), QVariant::fromValue( refLayer ) );
parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
parameters.insert( QStringLiteral( "ERRORS" ), QgsProcessing::TEMPORARY_OUTPUT );

bool ok = false;
QgsProcessingFeedback feedback;
std::unique_ptr< QgsProcessingContext > context = std::make_unique< QgsProcessingContext >();

QVariantMap results;
results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );

std::unique_ptr<QgsVectorLayer> outputLayer( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "OUTPUT" ) ).toString() ) ) );
std::unique_ptr<QgsVectorLayer> errorsLayer( qobject_cast< QgsVectorLayer * >( context->getMapLayer( results.value( QStringLiteral( "ERRORS" ) ).toString() ) ) );
QVERIFY( outputLayer->isValid() );
QVERIFY( errorsLayer->isValid() );
QCOMPARE( outputLayer->featureCount(), 2 );
QCOMPARE( errorsLayer->featureCount(), 2 );
}

QGSTEST_MAIN( TestQgsProcessingCheckGeometry )
#include "testqgsprocessingcheckgeometry.moc"
Loading