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