QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
qgsalgorithmdissolve.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmdissolve.cpp
3 ---------------------
4 begin : April 2017
5 copyright : (C) 2017 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 <QString>
21
22using namespace Qt::StringLiterals;
23
25
26//
27// QgsCollectorAlgorithm
28//
29
30QVariantMap QgsCollectorAlgorithm::processCollection(
31 const QVariantMap &parameters,
32 QgsProcessingContext &context,
33 QgsProcessingFeedback *feedback,
34 const std::function<QgsGeometry( const QVector<QgsGeometry> & )> &collector,
35 int maxQueueLength,
37 bool separateDisjoint
38)
39{
40 std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, u"INPUT"_s, context ) );
41 if ( !source )
42 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
43
44 QString dest;
45 std::unique_ptr<QgsFeatureSink> sink(
46 parameterAsSink( parameters, u"OUTPUT"_s, context, dest, source->fields(), QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey )
47 );
48
49 if ( !sink )
50 throw QgsProcessingException( invalidSinkError( parameters, u"OUTPUT"_s ) );
51
52 QVariantMap outputs;
53 outputs.insert( u"OUTPUT"_s, dest );
54
55 const QStringList fields = parameterAsStrings( parameters, u"FIELD"_s, context );
56
57 const long count = source->featureCount();
58
59 QgsFeature f;
60 QgsFeatureIterator it = source->getFeatures( QgsFeatureRequest(), sourceFlags );
61
62 const double step = count > 0 ? 100.0 / count : 1;
63 int current = 0;
64
65 if ( fields.isEmpty() )
66 {
67 // dissolve all - not using fields
68 bool firstFeature = true;
69 // we dissolve geometries in blocks using unaryUnion
70 QVector<QgsGeometry> geomQueue;
71 QgsFeature outputFeature;
72
73 while ( it.nextFeature( f ) )
74 {
75 if ( feedback->isCanceled() )
76 {
77 break;
78 }
79
80 if ( firstFeature )
81 {
82 outputFeature = f;
83 firstFeature = false;
84 }
85
86 if ( f.hasGeometry() && !f.geometry().isEmpty() )
87 {
88 geomQueue.append( f.geometry() );
89 if ( maxQueueLength > 0 && geomQueue.length() > maxQueueLength )
90 {
91 // queue too long, combine it
92 const QgsGeometry tempOutputGeometry = collector( geomQueue );
93 geomQueue.clear();
94 geomQueue << tempOutputGeometry;
95 }
96 }
97
98 feedback->setProgress( current * step );
99 current++;
100 }
101
102 if ( geomQueue.isEmpty() )
103 {
104 outputFeature.clearGeometry();
105 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
106 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
107 }
108 else if ( !separateDisjoint )
109 {
110 outputFeature.setGeometry( collector( geomQueue ) );
111 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
112 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
113 }
114 else
115 {
116 const QgsGeometry combinedGeometry = collector( geomQueue );
117 for ( auto it = combinedGeometry.const_parts_begin(); it != combinedGeometry.const_parts_end(); ++it )
118 {
119 QgsGeometry partGeom( ( ( *it )->clone() ) );
120 partGeom.convertToMultiType();
121 outputFeature.setGeometry( partGeom );
122 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
123 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
124 }
125 }
126 }
127 else
128 {
129 QList<int> fieldIndexes;
130 fieldIndexes.reserve( fields.size() );
131 for ( const QString &field : fields )
132 {
133 const int index = source->fields().lookupField( field );
134 if ( index >= 0 )
135 fieldIndexes << index;
136 }
137
138 QHash<QVariant, QgsAttributes> attributeHash;
139 QHash<QVariant, QVector<QgsGeometry>> geometryHash;
140
141 while ( it.nextFeature( f ) )
142 {
143 if ( feedback->isCanceled() )
144 {
145 break;
146 }
147
148 QVariantList indexAttributes;
149 indexAttributes.reserve( fieldIndexes.size() );
150 for ( const int index : std::as_const( fieldIndexes ) )
151 {
152 indexAttributes << f.attribute( index );
153 }
154
155 if ( !attributeHash.contains( indexAttributes ) )
156 {
157 // keep attributes of first feature
158 attributeHash.insert( indexAttributes, f.attributes() );
159 }
160
161 if ( f.hasGeometry() && !f.geometry().isEmpty() )
162 {
163 geometryHash[indexAttributes].append( f.geometry() );
164 }
165 }
166
167 const int numberFeatures = attributeHash.count();
168 QHash<QVariant, QgsAttributes>::const_iterator attrIt = attributeHash.constBegin();
169 for ( ; attrIt != attributeHash.constEnd(); ++attrIt )
170 {
171 if ( feedback->isCanceled() )
172 {
173 break;
174 }
175
176 QgsFeature outputFeature;
177 outputFeature.setAttributes( attrIt.value() );
178 auto geometryHashIt = geometryHash.find( attrIt.key() );
179 if ( geometryHashIt != geometryHash.end() )
180 {
181 QgsGeometry geom = collector( geometryHashIt.value() );
182 if ( !geom.isMultipart() )
183 {
184 geom.convertToMultiType();
185 }
186 if ( !separateDisjoint )
187 {
188 outputFeature.setGeometry( geom );
189 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
190 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
191 }
192 else
193 {
194 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
195 {
196 QgsGeometry partGeom( ( ( *it )->clone() ) );
197 partGeom.convertToMultiType();
198 outputFeature.setGeometry( partGeom );
199 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
200 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
201 }
202 }
203 }
204 else
205 {
206 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
207 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
208 }
209
210 feedback->setProgress( current * 100.0 / numberFeatures );
211 current++;
212 }
213 }
214
215 sink->finalize();
216
217 return outputs;
218}
219
220
221//
222// QgsDissolveAlgorithm
223//
224
225QString QgsDissolveAlgorithm::name() const
226{
227 return u"dissolve"_s;
228}
229
230QString QgsDissolveAlgorithm::displayName() const
231{
232 return QObject::tr( "Dissolve" );
233}
234
235QStringList QgsDissolveAlgorithm::tags() const
236{
237 return QObject::tr( "dissolve,union,combine,collect" ).split( ',' );
238}
239
240QString QgsDissolveAlgorithm::group() const
241{
242 return QObject::tr( "Vector geometry" );
243}
244
245QString QgsDissolveAlgorithm::groupId() const
246{
247 return u"vectorgeometry"_s;
248}
249
250
251void QgsDissolveAlgorithm::initAlgorithm( const QVariantMap & )
252{
253 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
254 addParameter( new QgsProcessingParameterField( u"FIELD"_s, QObject::tr( "Dissolve field(s)" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, true, true ) );
255
256 auto disjointParam = std::make_unique<QgsProcessingParameterBoolean>( u"SEPARATE_DISJOINT"_s, QObject::tr( "Keep disjoint features separate" ), false );
257 disjointParam->setFlags( disjointParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
258 addParameter( disjointParam.release() );
259
260 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Dissolved" ) ) );
261}
262
263QString QgsDissolveAlgorithm::shortHelpString() const
264{
265 return QObject::tr(
266 "This algorithm takes a vector layer and combines their features into new features. One or more attributes can "
267 "be specified to dissolve features belonging to the same class (having the same value for the specified attributes), alternatively "
268 "all features can be dissolved in a single one.\n\n"
269 "All output geometries will be converted to multi geometries. "
270 "In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased.\n\n"
271 "If enabled, the optional \"Keep disjoint features separate\" setting will cause features and parts that do not overlap or touch to be exported "
272 "as separate features (instead of parts of a single multipart feature)."
273 );
274}
275
276QString QgsDissolveAlgorithm::shortDescription() const
277{
278 return QObject::tr( "Combines features of a vector layer into new features, optionally grouped by common attributes." );
279}
280
281Qgis::ProcessingAlgorithmDocumentationFlags QgsDissolveAlgorithm::documentationFlags() const
282{
284}
285
286QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
287{
288 return new QgsDissolveAlgorithm();
289}
290
291QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
292{
293 const bool separateDisjoint = parameterAsBool( parameters, u"SEPARATE_DISJOINT"_s, context );
294
295 return processCollection(
296 parameters,
297 context,
298 feedback,
299 [&]( const QVector<QgsGeometry> &parts ) -> QgsGeometry {
300 QgsGeometry result( QgsGeometry::unaryUnion( parts ) );
301 if ( QgsWkbTypes::geometryType( result.wkbType() ) == Qgis::GeometryType::Line )
302 result = result.mergeLines();
303 // Geos may fail in some cases, let's try a slower but safer approach
304 // See: https://github.com/qgis/QGIS/issues/28411 - Dissolve tool failing to produce outputs
305 if ( !result.lastError().isEmpty() && parts.count() > 2 )
306 {
307 if ( feedback->isCanceled() )
308 return result;
309
310 feedback->pushDebugInfo( QObject::tr( "GEOS exception: taking the slower route ..." ) );
311 result = QgsGeometry();
312 for ( const auto &p : parts )
313 {
314 result = QgsGeometry::unaryUnion( QVector< QgsGeometry >() << result << p );
315 if ( QgsWkbTypes::geometryType( result.wkbType() ) == Qgis::GeometryType::Line )
316 result = result.mergeLines();
317 if ( feedback->isCanceled() )
318 return result;
319 }
320 }
321 if ( !result.lastError().isEmpty() )
322 {
323 feedback->reportError( result.lastError(), true );
324 if ( result.isEmpty() )
325 throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) );
326 }
327 return result;
328 },
329 10000,
331 separateDisjoint
332 );
333}
334
335//
336// QgsCollectAlgorithm
337//
338
339QString QgsCollectAlgorithm::name() const
340{
341 return u"collect"_s;
342}
343
344QString QgsCollectAlgorithm::displayName() const
345{
346 return QObject::tr( "Collect geometries" );
347}
348
349QStringList QgsCollectAlgorithm::tags() const
350{
351 return QObject::tr( "union,combine,collect,multipart,parts,single" ).split( ',' );
352}
353
354QString QgsCollectAlgorithm::group() const
355{
356 return QObject::tr( "Vector geometry" );
357}
358
359QString QgsCollectAlgorithm::groupId() const
360{
361 return u"vectorgeometry"_s;
362}
363
364QVariantMap QgsCollectAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
365{
366 return processCollection( parameters, context, feedback, []( const QVector<QgsGeometry> &parts ) -> QgsGeometry { return QgsGeometry::collectGeometry( parts ); }, 0, Qgis::ProcessingFeatureSourceFlag::SkipGeometryValidityChecks );
367}
368
369
370void QgsCollectAlgorithm::initAlgorithm( const QVariantMap & )
371{
372 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
373 addParameter( new QgsProcessingParameterField( u"FIELD"_s, QObject::tr( "Unique ID fields" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, true, true ) );
374
375 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Collected" ) ) );
376}
377
378QString QgsCollectAlgorithm::shortHelpString() const
379{
380 return QObject::tr(
381 "This algorithm takes a vector layer and collects its geometries into new multipart geometries. One or more attributes can "
382 "be specified to collect only geometries belonging to the same class (having the same value for the specified attributes), alternatively "
383 "all geometries can be collected."
384 )
385 + u"\n\n"_s
386 + QObject::tr(
387 "All output geometries will be converted to multi geometries, even those with just a single part. "
388 "This algorithm does not dissolve overlapping geometries - they will be collected together without modifying the shape of each geometry part."
389 )
390 + u"\n\n"_s
391 + QObject::tr( "See the 'Promote to multipart' or 'Aggregate' algorithms for alternative options." );
392}
393
394QString QgsCollectAlgorithm::shortDescription() const
395{
396 return QObject::tr( "Collects geometries of a vector layer into new multipart geometries, optionally grouped by common attributes." );
397}
398
399Qgis::ProcessingAlgorithmDocumentationFlags QgsCollectAlgorithm::documentationFlags() const
400{
402}
403
404QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
405{
406 return new QgsCollectAlgorithm();
407}
408
409
@ Line
Lines.
Definition qgis.h:381
@ RegeneratesPrimaryKey
Algorithm always drops any existing primary keys or FID values and regenerates them in outputs.
Definition qgis.h:3734
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3745
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Definition qgis.h:3828
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3880
QFlags< ProcessingFeatureSourceFlag > ProcessingFeatureSourceFlags
Flags which control how QgsProcessingFeatureSource fetches features.
Definition qgis.h:3839
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).
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
@ RegeneratePrimaryKey
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsAttributes attributes
Definition qgsfeature.h:69
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:71
void clearGeometry()
Removes any geometry associated with the feature.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's 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
A geometry is the spatial representation of a feature.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer or feature source field parameter for processing algorithms.
static Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.