QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
20#include "qgsgeometryengine.h"
21#include "qgsvectorlayer.h"
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29void QgsDistanceWithinAlgorithm::addDistanceParameter()
30{
31 auto distanceParam = std::make_unique<QgsProcessingParameterDistance>( u"DISTANCE"_s, QObject::tr( "Where the features are within" ), 100, u"INPUT"_s, false, 0 );
32 distanceParam->setIsDynamic( true );
33 distanceParam->setDynamicPropertyDefinition( QgsPropertyDefinition( u"Distance"_s, QObject::tr( "Distance within" ), QgsPropertyDefinition::DoublePositive ) );
34 distanceParam->setDynamicLayerParameterName( u"INPUT"_s );
35
36 addParameter( distanceParam.release() );
37}
38
39void QgsDistanceWithinAlgorithm::process(
40 const QgsProcessingContext &context,
41 QgsFeatureSource *targetSource,
42 QgsFeatureSource *referenceSource,
43 double distance,
44 const QgsProperty &distanceProperty,
45 const std::function<void( const QgsFeature & )> &handleFeatureFunction,
46 bool onlyRequireTargetIds,
47 QgsProcessingFeedback *feedback,
48 QgsExpressionContext &expressionContext
49)
50{
51 // By default we will iterate over the reference source and match back
52 // to the target source. We do this on the assumption that the most common
53 // use case is joining a points layer to a polygon layer (e.g. findings
54 // points near a polygon), so by iterating
55 // over the polygons we can take advantage of prepared geometries for
56 // the spatial relationship test.
57 bool iterateOverTarget = false;
58
59 //
60 // Possible reasons to iterate over target are considered here
61 //
62 do
63 {
64 // If distance is dynamic, we MUST iterate over target
65 if ( distanceProperty.isActive() )
66 {
67 iterateOverTarget = true;
68 break;
69 }
70
71 // If reference needs reprojection, we MUST iterate over target
72 if ( targetSource->sourceCrs() != referenceSource->sourceCrs() )
73 {
74 iterateOverTarget = true;
75 break;
76 }
77
78 // if reference is POINTs and target is not, we prefer iterating
79 // over target, to benefit from preparation
80 if ( referenceSource->wkbType() == Qgis::WkbType::Point && targetSource->wkbType() != Qgis::WkbType::Point )
81 {
82 iterateOverTarget = true;
83 break;
84 }
85
86 // neither source nor target or both of them are POINTs, we will
87 // iterate over the source with FEWER features to prepare less
88 if ( targetSource->featureCount() < referenceSource->featureCount() )
89 {
90 iterateOverTarget = true;
91 break;
92 }
93 } while ( 0 );
94
95 if ( iterateOverTarget )
96 {
97 processByIteratingOverTargetSource( context, targetSource, referenceSource, distance, distanceProperty, handleFeatureFunction, onlyRequireTargetIds, feedback, expressionContext );
98 }
99 else
100 {
101 processByIteratingOverReferenceSource( context, targetSource, referenceSource, distance, handleFeatureFunction, onlyRequireTargetIds, feedback );
102 }
103}
104
105void QgsDistanceWithinAlgorithm::processByIteratingOverTargetSource(
106 const QgsProcessingContext &context,
107 QgsFeatureSource *targetSource,
108 QgsFeatureSource *referenceSource,
109 const double distance,
110 const QgsProperty &distanceProperty,
111 const std::function<void( const QgsFeature & )> &handleFeatureFunction,
112 bool onlyRequireTargetIds,
113 QgsProcessingFeedback *feedback,
114 QgsExpressionContext &expressionContext
115)
116{
118 feedback->pushWarning( QObject::tr( "No spatial index exists for intersect layer, performance will be severely degraded" ) );
119
120 QgsFeatureIds foundSet;
122 if ( onlyRequireTargetIds )
123 request.setNoAttributes();
124
125 const bool dynamicDistance = distanceProperty.isActive();
126
127 QgsFeatureIterator fIt = targetSource->getFeatures( request );
128 const double step = targetSource->featureCount() > 0 ? 100.0 / targetSource->featureCount() : 1;
129 const QgsCoordinateReferenceSystem targetSourceCrs = targetSource->sourceCrs();
130 int current = 0;
131 QgsFeature f;
132 while ( fIt.nextFeature( f ) )
133 {
134 if ( feedback->isCanceled() )
135 break;
136
137 if ( !f.hasGeometry() )
138 continue;
139
140 double currentDistance = distance;
141 if ( dynamicDistance )
142 {
143 expressionContext.setFeature( f );
144 currentDistance = distanceProperty.valueAsDouble( expressionContext, currentDistance );
145 }
146
147 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), currentDistance ).setNoAttributes().setDestinationCrs( targetSourceCrs, context.transformContext() );
148 // we only care IF there's ANY features within the target distance here, so fetch at most 1 feature
149 request.setLimit( 1 );
150
151 QgsFeatureIterator testFeatureIt = referenceSource->getFeatures( request );
152 QgsFeature testFeature;
153 if ( testFeatureIt.nextFeature( testFeature ) )
154 {
155 foundSet.insert( f.id() );
156 handleFeatureFunction( f );
157 }
158
159 current += 1;
160 feedback->setProgress( current * step );
161 }
162}
163
164void QgsDistanceWithinAlgorithm::processByIteratingOverReferenceSource(
165 const QgsProcessingContext &context,
166 QgsFeatureSource *targetSource,
167 QgsFeatureSource *referenceSource,
168 const double distance,
169 const std::function<void( const QgsFeature & )> &handleFeatureFunction,
170 bool onlyRequireTargetIds,
171 QgsProcessingFeedback *feedback
172)
173{
175 feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
176
177 QgsFeatureIds foundSet;
178
180 QgsFeatureIterator fIt = referenceSource->getFeatures( request );
181 const double step = referenceSource->featureCount() > 0 ? 100.0 / referenceSource->featureCount() : 1;
182 int current = 0;
183 QgsFeature f;
184 while ( fIt.nextFeature( f ) )
185 {
186 if ( feedback->isCanceled() )
187 break;
188
189 if ( !f.hasGeometry() )
190 continue;
191
192 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), distance );
193 if ( onlyRequireTargetIds )
194 request.setNoAttributes();
195
196 QgsFeatureIterator testFeatureIt = targetSource->getFeatures( request );
197 QgsFeature testFeature;
198 while ( testFeatureIt.nextFeature( testFeature ) )
199 {
200 if ( feedback->isCanceled() )
201 break;
202
203 if ( foundSet.contains( testFeature.id() ) )
204 {
205 // already added this one, no need for further tests
206 continue;
207 }
208
209 foundSet.insert( testFeature.id() );
210 handleFeatureFunction( testFeature );
211 }
212
213 current += 1;
214 feedback->setProgress( current * step );
215 }
216}
217
218
219//
220// QgsSelectWithinDistanceAlgorithm
221//
222
223void QgsSelectWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
224{
225 const QStringList methods = QStringList()
226 << QObject::tr( "creating new selection" )
227 << QObject::tr( "adding to current selection" )
228 << QObject::tr( "selecting within current selection" )
229 << QObject::tr( "removing from current selection" );
230
231 addParameter( new QgsProcessingParameterVectorLayer( u"INPUT"_s, QObject::tr( "Select features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
232
233 addParameter(
234 new QgsProcessingParameterFeatureSource( u"REFERENCE"_s, QObject::tr( "By comparing to the features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) )
235 );
236 addDistanceParameter();
237
238 addParameter( new QgsProcessingParameterEnum( u"METHOD"_s, QObject::tr( "Modify current selection by" ), methods, false, 0 ) );
239}
240
241QString QgsSelectWithinDistanceAlgorithm::name() const
242{
243 return u"selectwithindistance"_s;
244}
245
246Qgis::ProcessingAlgorithmFlags QgsSelectWithinDistanceAlgorithm::flags() const
247{
249}
250
251QString QgsSelectWithinDistanceAlgorithm::displayName() const
252{
253 return QObject::tr( "Select within distance" );
254}
255
256QStringList QgsSelectWithinDistanceAlgorithm::tags() const
257{
258 return QObject::tr( "select,by,maximum,buffer" ).split( ',' );
259}
260
261QString QgsSelectWithinDistanceAlgorithm::group() const
262{
263 return QObject::tr( "Vector selection" );
264}
265
266QString QgsSelectWithinDistanceAlgorithm::groupId() const
267{
268 return u"vectorselection"_s;
269}
270
271QString QgsSelectWithinDistanceAlgorithm::shortHelpString() const
272{
273 return QObject::tr(
274 "This algorithm creates a selection in a vector layer. Features are selected wherever they are within "
275 "the specified maximum distance from the features in an additional reference layer."
276 );
277}
278
279QString QgsSelectWithinDistanceAlgorithm::shortDescription() const
280{
281 return QObject::tr( "Selects features that are within a specified distance from features in another layer." );
282}
283
284QgsSelectWithinDistanceAlgorithm *QgsSelectWithinDistanceAlgorithm::createInstance() const
285{
286 return new QgsSelectWithinDistanceAlgorithm();
287}
288
289QVariantMap QgsSelectWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
290{
291 QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, u"INPUT"_s, context );
292 if ( !selectLayer )
293 throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) );
294
295 const Qgis::SelectBehavior method = static_cast<Qgis::SelectBehavior>( parameterAsEnum( parameters, u"METHOD"_s, context ) );
296 const std::unique_ptr<QgsFeatureSource> referenceSource( parameterAsSource( parameters, u"REFERENCE"_s, context ) );
297 if ( !referenceSource )
298 throw QgsProcessingException( invalidSourceError( parameters, u"REFERENCE"_s ) );
299
300 const double distance = parameterAsDouble( parameters, u"DISTANCE"_s, context );
301 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, u"DISTANCE"_s );
302 QgsProperty distanceProperty;
303 if ( dynamicDistance )
304 distanceProperty = parameters.value( u"DISTANCE"_s ).value<QgsProperty>();
305 QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
306 expressionContext.appendScope( selectLayer->createExpressionContextScope() );
307
308 QgsFeatureIds selectedIds;
309 auto addToSelection = [&]( const QgsFeature &feature ) { selectedIds.insert( feature.id() ); };
310 process( context, selectLayer, referenceSource.get(), distance, distanceProperty, addToSelection, true, feedback, expressionContext );
311
312 selectLayer->selectByIds( selectedIds, method );
313 QVariantMap results;
314 results.insert( u"OUTPUT"_s, parameters.value( u"INPUT"_s ) );
315 return results;
316}
317
318
319//
320// QgsExtractWithinDistanceAlgorithm
321//
322
323void QgsExtractWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
324{
325 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Extract features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
326 addParameter(
327 new QgsProcessingParameterFeatureSource( u"REFERENCE"_s, QObject::tr( "By comparing to the features from" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorAnyGeometry ) )
328 );
329 addDistanceParameter();
330
331 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Extracted (location)" ) ) );
332}
333
334QString QgsExtractWithinDistanceAlgorithm::name() const
335{
336 return u"extractwithindistance"_s;
337}
338
339QString QgsExtractWithinDistanceAlgorithm::displayName() const
340{
341 return QObject::tr( "Extract within distance" );
342}
343
344QStringList QgsExtractWithinDistanceAlgorithm::tags() const
345{
346 return QObject::tr( "extract,by,filter,select,maximum,buffer" ).split( ',' );
347}
348
349QString QgsExtractWithinDistanceAlgorithm::group() const
350{
351 return QObject::tr( "Vector selection" );
352}
353
354QString QgsExtractWithinDistanceAlgorithm::groupId() const
355{
356 return u"vectorselection"_s;
357}
358
359QString QgsExtractWithinDistanceAlgorithm::shortHelpString() const
360{
361 return QObject::tr(
362 "This algorithm creates a new vector layer that only contains matching features from an "
363 "input layer. Features are copied wherever they are within "
364 "the specified maximum distance from the features in an additional reference layer."
365 );
366}
367
368QString QgsExtractWithinDistanceAlgorithm::shortDescription() const
369{
370 return QObject::tr(
371 "Creates a new vector layer with features that are within "
372 "a specified distance from features in another layer."
373 );
374}
375
376QgsExtractWithinDistanceAlgorithm *QgsExtractWithinDistanceAlgorithm::createInstance() const
377{
378 return new QgsExtractWithinDistanceAlgorithm();
379}
380
381QVariantMap QgsExtractWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
382{
383 std::unique_ptr<QgsProcessingFeatureSource> input( parameterAsSource( parameters, u"INPUT"_s, context ) );
384 if ( !input )
385 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
386 const std::unique_ptr<QgsFeatureSource> referenceSource( parameterAsSource( parameters, u"REFERENCE"_s, context ) );
387 if ( !referenceSource )
388 throw QgsProcessingException( invalidSourceError( parameters, u"REFERENCE"_s ) );
389
390 const double distance = parameterAsDouble( parameters, u"DISTANCE"_s, context );
391 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, u"DISTANCE"_s );
392 QgsProperty distanceProperty;
393 if ( dynamicDistance )
394 distanceProperty = parameters.value( u"DISTANCE"_s ).value<QgsProperty>();
395 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, input.get() );
396
397 QString dest;
398 std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, u"OUTPUT"_s, context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
399
400 if ( !sink )
401 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
402
403 auto addToSink = [&]( const QgsFeature &feature ) {
404 QgsFeature f = feature;
405 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
406 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
407 };
408 process( context, input.get(), referenceSource.get(), distance, distanceProperty, addToSink, false, feedback, expressionContext );
409
410 sink->finalize();
411
412 QVariantMap results;
413 results.insert( u"OUTPUT"_s, dest );
414 return results;
415}
416
@ VectorAnyGeometry
Any vector layer with geometry.
Definition qgis.h:3647
@ NotPresent
No spatial index exists for the source.
Definition qgis.h:586
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3724
@ Point
Point.
Definition qgis.h:296
@ NotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
Definition qgis.h:3710
@ NoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
Definition qgis.h:3703
SelectBehavior
Specifies how a selection should be applied.
Definition qgis.h:1850
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.
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:60
QgsFeatureId id
Definition qgsfeature.h:68
QgsGeometry geometry
Definition qgsfeature.h:71
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:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
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.
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:47
@ DoublePositive
Positive double value (including 0).
Definition qgsproperty.h:57
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 dataset.
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, bool validateIds=false)
Selects matching features using a list of feature IDs.
QSet< QgsFeatureId > QgsFeatureIds