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