QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
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 else
108 feedback->featureAddedToSink( u"OUTPUT"_s );
109 }
110 else if ( !separateDisjoint )
111 {
112 outputFeature.setGeometry( collector( geomQueue ) );
113 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
114 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
115 else
116 feedback->featureAddedToSink( u"OUTPUT"_s );
117 }
118 else
119 {
120 const QgsGeometry combinedGeometry = collector( geomQueue );
121 for ( auto it = combinedGeometry.const_parts_begin(); it != combinedGeometry.const_parts_end(); ++it )
122 {
123 QgsGeometry partGeom( ( ( *it )->clone() ) );
124 partGeom.convertToMultiType();
125 outputFeature.setGeometry( partGeom );
126 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
127 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
128 else
129 feedback->featureAddedToSink( u"OUTPUT"_s );
130 }
131 }
132 }
133 else
134 {
135 QList<int> fieldIndexes;
136 fieldIndexes.reserve( fields.size() );
137 for ( const QString &field : fields )
138 {
139 const int index = source->fields().lookupField( field );
140 if ( index >= 0 )
141 fieldIndexes << index;
142 }
143
144 QHash<QVariant, QgsAttributes> attributeHash;
145 QHash<QVariant, QVector<QgsGeometry>> geometryHash;
146
147 while ( it.nextFeature( f ) )
148 {
149 if ( feedback->isCanceled() )
150 {
151 break;
152 }
153
154 QVariantList indexAttributes;
155 indexAttributes.reserve( fieldIndexes.size() );
156 for ( const int index : std::as_const( fieldIndexes ) )
157 {
158 indexAttributes << f.attribute( index );
159 }
160
161 if ( !attributeHash.contains( indexAttributes ) )
162 {
163 // keep attributes of first feature
164 attributeHash.insert( indexAttributes, f.attributes() );
165 }
166
167 if ( f.hasGeometry() && !f.geometry().isEmpty() )
168 {
169 geometryHash[indexAttributes].append( f.geometry() );
170 }
171 }
172
173 const int numberFeatures = attributeHash.count();
174 QHash<QVariant, QgsAttributes>::const_iterator attrIt = attributeHash.constBegin();
175 for ( ; attrIt != attributeHash.constEnd(); ++attrIt )
176 {
177 if ( feedback->isCanceled() )
178 {
179 break;
180 }
181
182 QgsFeature outputFeature;
183 outputFeature.setAttributes( attrIt.value() );
184 auto geometryHashIt = geometryHash.find( attrIt.key() );
185 if ( geometryHashIt != geometryHash.end() )
186 {
187 QgsGeometry geom = collector( geometryHashIt.value() );
188 if ( !geom.isMultipart() )
189 {
190 geom.convertToMultiType();
191 }
192 if ( !separateDisjoint )
193 {
194 outputFeature.setGeometry( geom );
195 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
196 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
197 else
198 feedback->featureAddedToSink( u"OUTPUT"_s );
199 }
200 else
201 {
202 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
203 {
204 QgsGeometry partGeom( ( ( *it )->clone() ) );
205 partGeom.convertToMultiType();
206 outputFeature.setGeometry( partGeom );
207 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
208 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
209 else
210 feedback->featureAddedToSink( u"OUTPUT"_s );
211 }
212 }
213 }
214 else
215 {
216 if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
217 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, u"OUTPUT"_s ) );
218 else
219 feedback->featureAddedToSink( u"OUTPUT"_s );
220 }
221
222 feedback->setProgress( current * 100.0 / numberFeatures );
223 current++;
224 }
225 }
226
227 sink->finalize();
228 feedback->featureSinkFinalized( u"OUTPUT"_s );
229
230 return outputs;
231}
232
233
234//
235// QgsDissolveAlgorithm
236//
237
238QString QgsDissolveAlgorithm::name() const
239{
240 return u"dissolve"_s;
241}
242
243QString QgsDissolveAlgorithm::displayName() const
244{
245 return QObject::tr( "Dissolve" );
246}
247
248QStringList QgsDissolveAlgorithm::tags() const
249{
250 return QObject::tr( "dissolve,union,combine,collect" ).split( ',' );
251}
252
253QString QgsDissolveAlgorithm::group() const
254{
255 return QObject::tr( "Vector geometry" );
256}
257
258QString QgsDissolveAlgorithm::groupId() const
259{
260 return u"vectorgeometry"_s;
261}
262
263
264void QgsDissolveAlgorithm::initAlgorithm( const QVariantMap & )
265{
266 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
267 addParameter( new QgsProcessingParameterField( u"FIELD"_s, QObject::tr( "Dissolve field(s)" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, true, true ) );
268
269 auto disjointParam = std::make_unique<QgsProcessingParameterBoolean>( u"SEPARATE_DISJOINT"_s, QObject::tr( "Keep disjoint features separate" ), false );
270 disjointParam->setFlags( disjointParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
271 addParameter( disjointParam.release() );
272
273 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Dissolved" ) ) );
274}
275
276QString QgsDissolveAlgorithm::shortHelpString() const
277{
278 return QObject::tr(
279 "This algorithm takes a vector layer and combines their features into new features. One or more attributes can "
280 "be specified to dissolve features belonging to the same class (having the same value for the specified attributes), alternatively "
281 "all features can be dissolved in a single one.\n\n"
282 "All output geometries will be converted to multi geometries. "
283 "In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased.\n\n"
284 "If enabled, the optional \"Keep disjoint features separate\" setting will cause features and parts that do not overlap or touch to be exported "
285 "as separate features (instead of parts of a single multipart feature)."
286 );
287}
288
289QString QgsDissolveAlgorithm::shortDescription() const
290{
291 return QObject::tr( "Combines features of a vector layer into new features, optionally grouped by common attributes." );
292}
293
294Qgis::ProcessingAlgorithmDocumentationFlags QgsDissolveAlgorithm::documentationFlags() const
295{
297}
298
299QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
300{
301 return new QgsDissolveAlgorithm();
302}
303
304QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
305{
306 QGS_MARK_ALGORITHM_SOURCE
307
308 const bool separateDisjoint = parameterAsBool( parameters, u"SEPARATE_DISJOINT"_s, context );
309
310 return processCollection(
311 parameters,
312 context,
313 feedback,
314 [&]( const QVector<QgsGeometry> &parts ) -> QgsGeometry {
315 QgsGeometry result( QgsGeometry::unaryUnion( parts, QgsGeometryParameters(), feedback ) );
316 if ( QgsWkbTypes::geometryType( result.wkbType() ) == Qgis::GeometryType::Line )
317 result = result.mergeLines();
318 // Geos may fail in some cases, let's try a slower but safer approach
319 // See: https://github.com/qgis/QGIS/issues/28411 - Dissolve tool failing to produce outputs
320 if ( !result.lastError().isEmpty() && parts.count() > 2 )
321 {
322 if ( feedback->isCanceled() )
323 return result;
324
325 feedback->pushDebugInfo( QObject::tr( "GEOS exception: taking the slower route ..." ) );
326 result = QgsGeometry();
327 for ( const auto &p : parts )
328 {
329 result = QgsGeometry::unaryUnion( QVector< QgsGeometry >() << result << p, QgsGeometryParameters(), feedback );
330 if ( QgsWkbTypes::geometryType( result.wkbType() ) == Qgis::GeometryType::Line )
331 result = result.mergeLines();
332 if ( feedback->isCanceled() )
333 return result;
334 }
335 }
336 if ( !result.lastError().isEmpty() )
337 {
338 feedback->reportError( result.lastError(), true );
339 if ( result.isEmpty() )
340 throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) );
341 }
342 return result;
343 },
344 10000,
346 separateDisjoint
347 );
348}
349
350//
351// QgsCollectAlgorithm
352//
353
354QString QgsCollectAlgorithm::name() const
355{
356 return u"collect"_s;
357}
358
359QString QgsCollectAlgorithm::displayName() const
360{
361 return QObject::tr( "Collect geometries" );
362}
363
364QStringList QgsCollectAlgorithm::tags() const
365{
366 return QObject::tr( "union,combine,collect,multipart,parts,single" ).split( ',' );
367}
368
369QString QgsCollectAlgorithm::group() const
370{
371 return QObject::tr( "Vector geometry" );
372}
373
374QString QgsCollectAlgorithm::groupId() const
375{
376 return u"vectorgeometry"_s;
377}
378
379QVariantMap QgsCollectAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
380{
381 QGS_MARK_ALGORITHM_SOURCE
382
383 return processCollection( parameters, context, feedback, []( const QVector<QgsGeometry> &parts ) -> QgsGeometry { return QgsGeometry::collectGeometry( parts ); }, 0, Qgis::ProcessingFeatureSourceFlag::SkipGeometryValidityChecks );
384}
385
386
387void QgsCollectAlgorithm::initAlgorithm( const QVariantMap & )
388{
389 addParameter( new QgsProcessingParameterFeatureSource( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
390 addParameter( new QgsProcessingParameterField( u"FIELD"_s, QObject::tr( "Unique ID fields" ), QVariant(), u"INPUT"_s, Qgis::ProcessingFieldParameterDataType::Any, true, true ) );
391
392 addParameter( new QgsProcessingParameterFeatureSink( u"OUTPUT"_s, QObject::tr( "Collected" ) ) );
393}
394
395QString QgsCollectAlgorithm::shortHelpString() const
396{
397 return QObject::tr(
398 "This algorithm takes a vector layer and collects its geometries into new multipart geometries. One or more attributes can "
399 "be specified to collect only geometries belonging to the same class (having the same value for the specified attributes), alternatively "
400 "all geometries can be collected."
401 )
402 + u"\n\n"_s
403 + QObject::tr(
404 "All output geometries will be converted to multi geometries, even those with just a single part. "
405 "This algorithm does not dissolve overlapping geometries - they will be collected together without modifying the shape of each geometry part."
406 )
407 + u"\n\n"_s
408 + QObject::tr( "See the 'Promote to multipart' or 'Aggregate' algorithms for alternative options." );
409}
410
411QString QgsCollectAlgorithm::shortDescription() const
412{
413 return QObject::tr( "Collects geometries of a vector layer into new multipart geometries, optionally grouped by common attributes." );
414}
415
416Qgis::ProcessingAlgorithmDocumentationFlags QgsCollectAlgorithm::documentationFlags() const
417{
419}
420
421QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
422{
423 return new QgsCollectAlgorithm();
424}
425
426
@ 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:3836
QFlags< ProcessingAlgorithmDocumentationFlag > ProcessingAlgorithmDocumentationFlags
Flags describing algorithm behavior for documentation purposes.
Definition qgis.h:3847
@ SkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
Definition qgis.h:3930
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3982
QFlags< ProcessingFeatureSourceFlag > ProcessingFeatureSourceFlags
Flags which control how QgsProcessingFeatureSource fetches features.
Definition qgis.h:3941
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:64
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
QgsGeometry geometry
Definition qgsfeature.h:66
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
Encapsulates parameters under which a geometry operation is performed.
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.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr)
Compute the unary union on a list of geometries.
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.
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.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
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.