diff --git a/python/plugins/processing/tests/testdata/expected/extract_by_extent.gfs b/python/plugins/processing/tests/testdata/expected/extract_by_extent.gfs new file mode 100755 index 000000000000..321f9cbe4759 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/extract_by_extent.gfs @@ -0,0 +1,33 @@ + + + extract_by_extent + extract_by_extent + + 3 + EPSG:4326 + + 3 + -1.00000 + 10.00000 + -3.00000 + 3.00000 + + + name + name + String + 5 + + + intval + intval + Integer + + + floatval + floatval + String + 18 + + + diff --git a/python/plugins/processing/tests/testdata/expected/extract_by_extent.gml b/python/plugins/processing/tests/testdata/expected/extract_by_extent.gml new file mode 100755 index 000000000000..3b9a3cece013 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/extract_by_extent.gml @@ -0,0 +1,30 @@ + + + + + -1-3 + 63 + + + + + + -1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1 + aaaaa + 33 + 44.123456 + + + + + 3,2 6,1 6,-3 2,-1 2,2 3,2 + elim + 2 + 3.33 + + + diff --git a/python/plugins/processing/tests/testdata/expected/extract_by_extent_clip.gfs b/python/plugins/processing/tests/testdata/expected/extract_by_extent_clip.gfs new file mode 100755 index 000000000000..d8aaa3132b47 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/extract_by_extent_clip.gfs @@ -0,0 +1,32 @@ + + + extract_by_extent_clip + extract_by_extent_clip + + 6 + EPSG:4326 + + 2 + -1.00000 + 4.77500 + -2.38750 + 3.00000 + + + name + name + String + 5 + + + intval + intval + Integer + + + floatval + floatval + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/extract_by_extent_clip.gml b/python/plugins/processing/tests/testdata/expected/extract_by_extent_clip.gml new file mode 100755 index 000000000000..3c1ffe07bc6f --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/extract_by_extent_clip.gml @@ -0,0 +1,30 @@ + + + + + -1-2.3875 + 4.7753 + + + + + + -1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1 + aaaaa + 33 + 44.123456 + + + + + 3,2 4.775,1.40833333333333 4.775,-2.3875 2,-1 2,2 3,2 + elim + 2 + 3.33 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml old mode 100644 new mode 100755 index c61485e71c73..a816ee7a866b --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -3937,4 +3937,30 @@ tests: - name compare: fields: - fid: skip \ No newline at end of file + fid: skip + + - algorithm: native:extractbyextent + name: Extract by extent + params: + CLIP: false + EXTENT: -1.1650000000000003,4.775,-2.444285714285715,3.4171428571428573 + INPUT: + name: polys.gml + type: vector + results: + OUTPUT: + name: expected/extract_by_extent.gml + type: vector + + - algorithm: native:extractbyextent + name: Extract by extent (clipped) + params: + CLIP: true + EXTENT: -1.1650000000000003,4.775,-2.444285714285715,3.4171428571428573 + INPUT: + name: polys.gml + type: vector + results: + OUTPUT: + name: expected/extract_by_extent_clip.gml + type: vector diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index a7a1062b83ba..ea6cbdc5e4f2 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -82,6 +82,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsMergeLinesAlgorithm() ); addAlgorithm( new QgsSmoothAlgorithm() ); addAlgorithm( new QgsSimplifyAlgorithm() ); + addAlgorithm( new QgsExtractByExtentAlgorithm() ); } void QgsCentroidAlgorithm::initAlgorithm( const QVariantMap & ) @@ -1811,7 +1812,77 @@ QgsFeature QgsSimplifyAlgorithm::processFeature( const QgsFeature &feature, QgsP return f; } -///@endcond +void QgsExtractByExtentAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) ); + addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CLIP" ), QObject::tr( "Clip features to extent" ), false ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted" ) ) ); +} + +QString QgsExtractByExtentAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm creates a new vector layer that only contains features which fall within a specified extent. " + "Any features which intersect the extent will be included.\n\n" + "Optionally, feature geometries can also be clipped to the extent. If this option is selected, then the output " + "geometries will automatically be converted to multi geometries to ensure uniform output geometry types." ); +} + +QgsExtractByExtentAlgorithm *QgsExtractByExtentAlgorithm::createInstance() const +{ + return new QgsExtractByExtentAlgorithm(); +} + +QVariantMap QgsExtractByExtentAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !featureSource ) + return QVariantMap(); + + QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context ); + bool clip = parameterAsBool( parameters, QStringLiteral( "CLIP" ), context ); + + // if clipping, we force multi output + QgsWkbTypes::Type outType = clip ? QgsWkbTypes::multiType( featureSource->wkbType() ) : featureSource->wkbType(); + + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), outType, featureSource->sourceCrs() ) ); + + if ( !sink ) + return QVariantMap(); + + QgsGeometry clipGeom = QgsGeometry::fromRect( extent ); + + double step = featureSource->featureCount() > 0 ? 100.0 / featureSource->featureCount() : 1; + QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( extent ).setFlags( QgsFeatureRequest::ExactIntersect ) ); + QgsFeature f; + int i = -1; + while ( inputIt.nextFeature( f ) ) + { + i++; + if ( feedback->isCanceled() ) + { + break; + } + + if ( clip ) + { + QgsGeometry g = f.geometry().intersection( clipGeom ); + g.convertToMultiType(); + f.setGeometry( g ); + } + + sink->addFeature( f, QgsFeatureSink::FastInsert ); + feedback->setProgress( i * step ); + } + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + return outputs; +} + +///@endcond + diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index b5a6e1737c46..b20987856539 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -662,6 +662,32 @@ class QgsSimplifyAlgorithm : public QgsProcessingFeatureBasedAlgorithm std::unique_ptr< QgsMapToPixelSimplifier > mSimplifier; }; + + +/** + * Native extract/clip by extent algorithm. + */ +class QgsExtractByExtentAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsExtractByExtentAlgorithm() = default; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + QString name() const override { return QStringLiteral( "extractbyextent" ); } + QString displayName() const override { return QObject::tr( "Extract/clip by extent" ); } + virtual QStringList tags() const override { return QObject::tr( "clip,extract,intersect,intersection,mask,extent" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector overlay" ); } + QString shortHelpString() const override; + QgsExtractByExtentAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + +}; + ///@endcond PRIVATE #endif // QGSNATIVEALGORITHMS_H