QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgsalgorithmdistancewithin.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmdistancewithin.cpp
3 ---------------------
4 begin : August 2021
5 copyright : (C) 2021 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "qgsgeometryengine.h"
20#include "qgsvectorlayer.h"
21
23
24void QgsDistanceWithinAlgorithm::addDistanceParameter()
25{
26 std::unique_ptr<QgsProcessingParameterDistance> distanceParam( new QgsProcessingParameterDistance( QStringLiteral( "DISTANCE" ), QObject::tr( "Where the features are within" ), 100, QStringLiteral( "INPUT" ), false, 0 ) );
27 distanceParam->setIsDynamic( true );
28 distanceParam->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Distance" ), QObject::tr( "Distance within" ), QgsPropertyDefinition::DoublePositive ) );
29 distanceParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
30
31 addParameter( distanceParam.release() );
32}
33
34void QgsDistanceWithinAlgorithm::process( const QgsProcessingContext &context, QgsFeatureSource *targetSource, QgsFeatureSource *referenceSource, double distance, const QgsProperty &distanceProperty, const std::function<void( const QgsFeature & )> &handleFeatureFunction, bool onlyRequireTargetIds, QgsProcessingFeedback *feedback, QgsExpressionContext &expressionContext )
35{
36 // By default we will iterate over the reference source and match back
37 // to the target source. We do this on the assumption that the most common
38 // use case is joining a points layer to a polygon layer (e.g. findings
39 // points near a polygon), so by iterating
40 // over the polygons we can take advantage of prepared geometries for
41 // the spatial relationship test.
42 bool iterateOverTarget = false;
43
44 //
45 // Possible reasons to iterate over target are considered here
46 //
47 do
48 {
49 // If distance is dynamic, we MUST iterate over target
50 if ( distanceProperty.isActive() )
51 {
52 iterateOverTarget = true;
53 break;
54 }
55
56 // If reference needs reprojection, we MUST iterate over target
57 if ( targetSource->sourceCrs() != referenceSource->sourceCrs() )
58 {
59 iterateOverTarget = true;
60 break;
61 }
62
63 // if reference is POINTs and target is not, we prefer iterating
64 // over target, to benefit from preparation
65 if ( referenceSource->wkbType() == Qgis::WkbType::Point && targetSource->wkbType() != Qgis::WkbType::Point )
66 {
67 iterateOverTarget = true;
68 break;
69 }
70
71 // neither source nor target or both of them are POINTs, we will
72 // iterate over the source with FEWER features to prepare less
73 if ( targetSource->featureCount() < referenceSource->featureCount() )
74 {
75 iterateOverTarget = true;
76 break;
77 }
78 } while ( 0 );
79
80 if ( iterateOverTarget )
81 {
82 processByIteratingOverTargetSource( context, targetSource, referenceSource, distance, distanceProperty, handleFeatureFunction, onlyRequireTargetIds, feedback, expressionContext );
83 }
84 else
85 {
86 processByIteratingOverReferenceSource( context, targetSource, referenceSource, distance, handleFeatureFunction, onlyRequireTargetIds, feedback );
87 }
88}
89
90void QgsDistanceWithinAlgorithm::processByIteratingOverTargetSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource, QgsFeatureSource *referenceSource, const double distance, const QgsProperty &distanceProperty, const std::function<void( const QgsFeature & )> &handleFeatureFunction, bool onlyRequireTargetIds, QgsProcessingFeedback *feedback, QgsExpressionContext &expressionContext )
91{
93 feedback->pushWarning( QObject::tr( "No spatial index exists for intersect layer, performance will be severely degraded" ) );
94
95 QgsFeatureIds foundSet;
97 if ( onlyRequireTargetIds )
98 request.setNoAttributes();
99
100 const bool dynamicDistance = distanceProperty.isActive();
101
102 QgsFeatureIterator fIt = targetSource->getFeatures( request );
103 const double step = targetSource->featureCount() > 0 ? 100.0 / targetSource->featureCount() : 1;
104 const QgsCoordinateReferenceSystem targetSourceCrs = targetSource->sourceCrs();
105 int current = 0;
106 QgsFeature f;
107 while ( fIt.nextFeature( f ) )
108 {
109 if ( feedback->isCanceled() )
110 break;
111
112 if ( !f.hasGeometry() )
113 continue;
114
115 double currentDistance = distance;
116 if ( dynamicDistance )
117 {
118 expressionContext.setFeature( f );
119 currentDistance = distanceProperty.valueAsDouble( expressionContext, currentDistance );
120 }
121
122 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), currentDistance ).setNoAttributes().setDestinationCrs( targetSourceCrs, context.transformContext() );
123 // we only care IF there's ANY features within the target distance here, so fetch at most 1 feature
124 request.setLimit( 1 );
125
126 QgsFeatureIterator testFeatureIt = referenceSource->getFeatures( request );
127 QgsFeature testFeature;
128 if ( testFeatureIt.nextFeature( testFeature ) )
129 {
130 foundSet.insert( f.id() );
131 handleFeatureFunction( f );
132 }
133
134 current += 1;
135 feedback->setProgress( current * step );
136 }
137}
138
139void QgsDistanceWithinAlgorithm::processByIteratingOverReferenceSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource, QgsFeatureSource *referenceSource, const double distance, const std::function<void( const QgsFeature & )> &handleFeatureFunction, bool onlyRequireTargetIds, QgsProcessingFeedback *feedback )
140{
142 feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
143
144 QgsFeatureIds foundSet;
145
147 QgsFeatureIterator fIt = referenceSource->getFeatures( request );
148 const double step = referenceSource->featureCount() > 0 ? 100.0 / referenceSource->featureCount() : 1;
149 int current = 0;
150 QgsFeature f;
151 while ( fIt.nextFeature( f ) )
152 {
153 if ( feedback->isCanceled() )
154 break;
155
156 if ( !f.hasGeometry() )
157 continue;
158
159 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), distance );
160 if ( onlyRequireTargetIds )
161 request.setNoAttributes();
162
163 QgsFeatureIterator testFeatureIt = targetSource->getFeatures( request );
164 QgsFeature testFeature;
165 while ( testFeatureIt.nextFeature( testFeature ) )
166 {
167 if ( feedback->isCanceled() )
168 break;
169
170 if ( foundSet.contains( testFeature.id() ) )
171 {
172 // already added this one, no need for further tests
173 continue;
174 }
175
176 foundSet.insert( testFeature.id() );
177 handleFeatureFunction( testFeature );
178 }
179
180 current += 1;
181 feedback->setProgress( current * step );
182 }
183}
184
185
186//
187// QgsSelectWithinDistanceAlgorithm
188//
189
190void QgsSelectWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
191{
192 const QStringList methods = QStringList() << QObject::tr( "creating new selection" )
193 << QObject::tr( "adding to current selection" )
194 << QObject::tr( "selecting within current selection" )
195 << QObject::tr( "removing from current selection" );
196
197 addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
198
199 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE" ), QObject::tr( "By comparing to the features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
200 addDistanceParameter();
201
202 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ), QObject::tr( "Modify current selection by" ), methods, false, 0 ) );
203}
204
205QString QgsSelectWithinDistanceAlgorithm::name() const
206{
207 return QStringLiteral( "selectwithindistance" );
208}
209
210Qgis::ProcessingAlgorithmFlags QgsSelectWithinDistanceAlgorithm::flags() const
211{
213}
214
215QString QgsSelectWithinDistanceAlgorithm::displayName() const
216{
217 return QObject::tr( "Select within distance" );
218}
219
220QStringList QgsSelectWithinDistanceAlgorithm::tags() const
221{
222 return QObject::tr( "select,by,maximum,buffer" ).split( ',' );
223}
224
225QString QgsSelectWithinDistanceAlgorithm::group() const
226{
227 return QObject::tr( "Vector selection" );
228}
229
230QString QgsSelectWithinDistanceAlgorithm::groupId() const
231{
232 return QStringLiteral( "vectorselection" );
233}
234
235QString QgsSelectWithinDistanceAlgorithm::shortHelpString() const
236{
237 return QObject::tr( "This algorithm creates a selection in a vector layer. Features are selected wherever they are within "
238 "the specified maximum distance from the features in an additional reference layer." );
239}
240
241QgsSelectWithinDistanceAlgorithm *QgsSelectWithinDistanceAlgorithm::createInstance() const
242{
243 return new QgsSelectWithinDistanceAlgorithm();
244}
245
246QVariantMap QgsSelectWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
247{
248 QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
249 if ( !selectLayer )
250 throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) );
251
252 const Qgis::SelectBehavior method = static_cast<Qgis::SelectBehavior>( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
253 const std::unique_ptr<QgsFeatureSource> referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE" ), context ) );
254 if ( !referenceSource )
255 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE" ) ) );
256
257 const double distance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
258 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
259 QgsProperty distanceProperty;
260 if ( dynamicDistance )
261 distanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value<QgsProperty>();
262 QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
263 expressionContext.appendScope( selectLayer->createExpressionContextScope() );
264
265 QgsFeatureIds selectedIds;
266 auto addToSelection = [&]( const QgsFeature &feature ) {
267 selectedIds.insert( feature.id() );
268 };
269 process( context, selectLayer, referenceSource.get(), distance, distanceProperty, addToSelection, true, feedback, expressionContext );
270
271 selectLayer->selectByIds( selectedIds, method );
272 QVariantMap results;
273 results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
274 return results;
275}
276
277
278//
279// QgsExtractWithinDistanceAlgorithm
280//
281
282void QgsExtractWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
283{
284 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Extract features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
285 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE" ), QObject::tr( "By comparing to the features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
286 addDistanceParameter();
287
288 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (location)" ) ) );
289}
290
291QString QgsExtractWithinDistanceAlgorithm::name() const
292{
293 return QStringLiteral( "extractwithindistance" );
294}
295
296QString QgsExtractWithinDistanceAlgorithm::displayName() const
297{
298 return QObject::tr( "Extract within distance" );
299}
300
301QStringList QgsExtractWithinDistanceAlgorithm::tags() const
302{
303 return QObject::tr( "extract,by,filter,select,maximum,buffer" ).split( ',' );
304}
305
306QString QgsExtractWithinDistanceAlgorithm::group() const
307{
308 return QObject::tr( "Vector selection" );
309}
310
311QString QgsExtractWithinDistanceAlgorithm::groupId() const
312{
313 return QStringLiteral( "vectorselection" );
314}
315
316QString QgsExtractWithinDistanceAlgorithm::shortHelpString() const
317{
318 return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an "
319 "input layer. Features are copied wherever they are within "
320 "the specified maximum distance from the features in an additional reference layer." );
321}
322
323QgsExtractWithinDistanceAlgorithm *QgsExtractWithinDistanceAlgorithm::createInstance() const
324{
325 return new QgsExtractWithinDistanceAlgorithm();
326}
327
328QVariantMap QgsExtractWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
329{
330 std::unique_ptr<QgsProcessingFeatureSource> input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
331 if ( !input )
332 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
333 const std::unique_ptr<QgsFeatureSource> referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE" ), context ) );
334 if ( !referenceSource )
335 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE" ) ) );
336
337 const double distance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
338 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
339 QgsProperty distanceProperty;
340 if ( dynamicDistance )
341 distanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value<QgsProperty>();
342 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, input.get() );
343
344 QString dest;
345 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
346
347 if ( !sink )
348 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
349
350 auto addToSink = [&]( const QgsFeature &feature ) {
351 QgsFeature f = feature;
352 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
353 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
354 };
355 process( context, input.get(), referenceSource.get(), distance, distanceProperty, addToSink, false, feedback, expressionContext );
356
357 sink->finalize();
358
359 QVariantMap results;
360 results.insert( QStringLiteral( "OUTPUT" ), dest );
361 return results;
362}
363
@ VectorAnyGeometry
Any vector layer with geometry.
@ NotPresent
No spatial index exists for the source.
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3410
@ NotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
@ NoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
SelectBehavior
Specifies how a selection should be applied.
Definition qgis.h:1681
This class represents a coordinate reference system (CRS).
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setDistanceWithin(const QgsGeometry &geometry, double distance)
Sets a reference geometry and a maximum distance from this geometry to retrieve features within.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
An interface for objects which provide features via a getFeatures method.
virtual QgsCoordinateReferenceSystem sourceCrs() const =0
Returns the coordinate reference system for features in the source.
virtual Qgis::WkbType wkbType() const =0
Returns the geometry type for features returned by this source.
virtual Qgis::SpatialIndexPresence hasSpatialIndex() const
Returns an enum value representing the presence of a valid spatial index on the source,...
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
virtual long long featureCount() const =0
Returns the number of features contained in the source, or -1 if the feature count is unknown.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
A double numeric parameter for distance values.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer (with or without geometry) parameter for processing algorithms.
static bool isDynamic(const QVariantMap &parameters, const QString &name)
Returns true if the parameter with matching name is a dynamic parameter, and must be evaluated once f...
Definition for a property.
Definition qgsproperty.h:45
@ DoublePositive
Positive double value (including 0)
Definition qgsproperty.h:56
A store for object properties.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
bool isActive() const
Returns whether the property is currently active.
double valueAsDouble(const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property and interprets it as a double.
Represents a vector layer which manages a vector based data sets.
QgsExpressionContextScope * createExpressionContextScope() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Q_INVOKABLE void selectByIds(const QgsFeatureIds &ids, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection)
Selects matching features using a list of feature IDs.
QSet< QgsFeatureId > QgsFeatureIds