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