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