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