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