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