Skip to content

Commit 36ce8d1

Browse files
committed
Port clip alg to c++
Rough benchtests reveal it's about 25% faster then the python version
1 parent d89b160 commit 36ce8d1

File tree

2 files changed

+178
-1
lines changed

2 files changed

+178
-1
lines changed

src/core/processing/qgsnativealgorithms.cpp

+155-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "qgsprocessingutils.h"
2323
#include "qgsvectorlayer.h"
2424
#include "qgsgeometry.h"
25+
#include "qgsgeometryengine.h"
2526
#include "qgswkbtypes.h"
2627

2728
///@cond PRIVATE
@@ -47,7 +48,7 @@ QString QgsNativeAlgorithms::id() const
4748

4849
QString QgsNativeAlgorithms::name() const
4950
{
50-
return tr( "QGIS" );
51+
return tr( "QGIS (native c++)" );
5152
}
5253

5354
bool QgsNativeAlgorithms::supportsNonFileBasedOutput() const
@@ -60,6 +61,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
6061
addAlgorithm( new QgsCentroidAlgorithm() );
6162
addAlgorithm( new QgsBufferAlgorithm() );
6263
addAlgorithm( new QgsDissolveAlgorithm() );
64+
addAlgorithm( new QgsClipAlgorithm() );
6365
}
6466

6567

@@ -360,4 +362,156 @@ QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameter
360362
return outputs;
361363
}
362364

365+
QgsClipAlgorithm::QgsClipAlgorithm()
366+
{
367+
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
368+
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "OVERLAY" ), QObject::tr( "Clip layer" ), QList< int >() << QgsProcessingParameterDefinition::TypeVectorPolygon ) );
369+
370+
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Clipped" ) ) );
371+
addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Clipped" ) ) );
372+
}
373+
374+
QString QgsClipAlgorithm::shortHelpString() const
375+
{
376+
return QObject::tr( "This algorithm clips a vector layer using the polygons of an additional polygons layer. Only the parts of the features "
377+
"in the input layer that falls within the polygons of the clipping layer will be added to the resulting layer.\n\n"
378+
"The attributes of the features are not modified, although properties such as area or length of the features will "
379+
"be modified by the clipping operation. If such properties are stored as attributes, those attributes will have to "
380+
"be manually updated." );
381+
}
382+
383+
QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const
384+
{
385+
std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
386+
if ( !featureSource )
387+
return QVariantMap();
388+
389+
std::unique_ptr< QgsFeatureSource > maskSource( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) );
390+
if ( !maskSource )
391+
return QVariantMap();
392+
393+
QString dest;
394+
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, featureSource->fields(), QgsWkbTypes::multiType( featureSource->wkbType() ), featureSource->sourceCrs(), dest ) );
395+
396+
if ( !sink )
397+
return QVariantMap();
398+
399+
// first build up a list of clip geometries
400+
QList< QgsGeometry > clipGeoms;
401+
QgsFeatureIterator it = maskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( featureSource->sourceCrs() ) );
402+
QgsFeature f;
403+
while ( it.nextFeature( f ) )
404+
{
405+
if ( f.hasGeometry() )
406+
clipGeoms << f.geometry();
407+
}
408+
409+
if ( clipGeoms.isEmpty() )
410+
return QVariantMap();
411+
412+
// are we clipping against a single feature? if so, we can show finer progress reports
413+
bool singleClipFeature = false;
414+
QgsGeometry combinedClipGeom;
415+
if ( clipGeoms.length() > 1 )
416+
{
417+
combinedClipGeom = QgsGeometry::unaryUnion( clipGeoms );
418+
singleClipFeature = false;
419+
}
420+
else
421+
{
422+
combinedClipGeom = clipGeoms.at( 0 );
423+
singleClipFeature = true;
424+
}
425+
426+
// use prepared geometries for faster intersection tests
427+
std::unique_ptr< QgsGeometryEngine > engine( QgsGeometry::createGeometryEngine( combinedClipGeom.geometry() ) );
428+
engine->prepareGeometry();
429+
430+
QgsFeatureIds testedFeatureIds;
431+
432+
int i = -1;
433+
Q_FOREACH ( const QgsGeometry &clipGeom, clipGeoms )
434+
{
435+
i++;
436+
if ( feedback->isCanceled() )
437+
{
438+
break;
439+
}
440+
441+
QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) );
442+
QgsFeatureList inputFeatures;
443+
QgsFeature f;
444+
while ( inputIt.nextFeature( f ) )
445+
inputFeatures << f;
446+
447+
if ( inputFeatures.isEmpty() )
448+
continue;
449+
450+
double step = 0;
451+
if ( singleClipFeature )
452+
step = 100.0 / inputFeatures.length();
453+
454+
int current = 0;
455+
Q_FOREACH ( const QgsFeature &inputFeature, inputFeatures )
456+
{
457+
if ( feedback->isCanceled() )
458+
{
459+
break;
460+
}
461+
462+
if ( !inputFeature.hasGeometry() )
463+
continue;
464+
465+
if ( testedFeatureIds.contains( inputFeature.id() ) )
466+
{
467+
// don't retest a feature we have already checked
468+
continue;
469+
}
470+
testedFeatureIds.insert( inputFeature.id() );
471+
472+
if ( !engine->intersects( *inputFeature.geometry().geometry() ) )
473+
continue;
474+
475+
QgsGeometry newGeometry;
476+
if ( !engine->contains( *inputFeature.geometry().geometry() ) )
477+
{
478+
QgsGeometry currentGeometry = inputFeature.geometry();
479+
newGeometry = combinedClipGeom.intersection( currentGeometry );
480+
if ( newGeometry.wkbType() == QgsWkbTypes::Unknown || QgsWkbTypes::flatType( newGeometry.geometry()->wkbType() ) == QgsWkbTypes::GeometryCollection )
481+
{
482+
QgsGeometry intCom = inputFeature.geometry().combine( newGeometry );
483+
QgsGeometry intSym = inputFeature.geometry().symDifference( newGeometry );
484+
newGeometry = intCom.difference( intSym );
485+
}
486+
}
487+
else
488+
{
489+
// clip geometry totally contains feature geometry, so no need to perform intersection
490+
newGeometry = inputFeature.geometry();
491+
}
492+
493+
QgsFeature outputFeature;
494+
outputFeature.setGeometry( newGeometry );
495+
outputFeature.setAttributes( inputFeature.attributes() );
496+
sink->addFeature( outputFeature );
497+
498+
499+
if ( singleClipFeature )
500+
feedback->setProgress( current * step );
501+
}
502+
503+
if ( !singleClipFeature )
504+
{
505+
// coarse progress report for multiple clip geometries
506+
feedback->setProgress( 100.0 * static_cast< double >( i ) / clipGeoms.length() );
507+
}
508+
}
509+
510+
QVariantMap outputs;
511+
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
512+
return outputs;
513+
}
514+
515+
363516
///@endcond
517+

src/core/processing/qgsnativealgorithms.h

+23
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ class QgsDissolveAlgorithm : public QgsProcessingAlgorithm
112112

113113
};
114114

115+
/**
116+
* Native clip algorithm.
117+
*/
118+
class QgsClipAlgorithm : public QgsProcessingAlgorithm
119+
{
120+
121+
public:
122+
123+
QgsClipAlgorithm();
124+
125+
QString name() const override { return QStringLiteral( "clip" ); }
126+
QString displayName() const override { return QObject::tr( "Clip" ); }
127+
virtual QStringList tags() const override { return QObject::tr( "clip,intersect,intersection,mask" ).split( ',' ); }
128+
QString group() const override { return QObject::tr( "Vector overlay tools" ); }
129+
QString shortHelpString() const override;
130+
131+
protected:
132+
133+
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
134+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override;
135+
136+
};
137+
115138
///@endcond PRIVATE
116139

117140
#endif // QGSNATIVEALGORITHMS_H

0 commit comments

Comments
 (0)