QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 )
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  outputFeature.setGeometry( collector( geomQueue ) );
87  if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
88  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
89  }
90  else
91  {
92  QList< int > fieldIndexes;
93  const auto constFields = fields;
94  for ( const QString &field : constFields )
95  {
96  const int index = source->fields().lookupField( field );
97  if ( index >= 0 )
98  fieldIndexes << index;
99  }
100 
101  QHash< QVariant, QgsAttributes > attributeHash;
102  QHash< QVariant, QVector< QgsGeometry > > geometryHash;
103 
104  while ( it.nextFeature( f ) )
105  {
106  if ( feedback->isCanceled() )
107  {
108  break;
109  }
110 
111  QVariantList indexAttributes;
112  const auto constFieldIndexes = fieldIndexes;
113  for ( const int index : constFieldIndexes )
114  {
115  indexAttributes << f.attribute( index );
116  }
117 
118  if ( !attributeHash.contains( indexAttributes ) )
119  {
120  // keep attributes of first feature
121  attributeHash.insert( indexAttributes, f.attributes() );
122  }
123 
124  if ( f.hasGeometry() && !f.geometry().isNull() )
125  {
126  geometryHash[ indexAttributes ].append( f.geometry() );
127  }
128  }
129 
130  const int numberFeatures = attributeHash.count();
131  QHash< QVariant, QgsAttributes >::const_iterator attrIt = attributeHash.constBegin();
132  for ( ; attrIt != attributeHash.constEnd(); ++attrIt )
133  {
134  if ( feedback->isCanceled() )
135  {
136  break;
137  }
138 
139  QgsFeature outputFeature;
140  if ( geometryHash.contains( attrIt.key() ) )
141  {
142  QgsGeometry geom = collector( geometryHash.value( attrIt.key() ) );
143  if ( !geom.isMultipart() )
144  {
145  geom.convertToMultiType();
146  }
147  outputFeature.setGeometry( geom );
148  }
149  outputFeature.setAttributes( attrIt.value() );
150  if ( !sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ) )
151  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
152 
153  feedback->setProgress( current * 100.0 / numberFeatures );
154  current++;
155  }
156  }
157 
158  QVariantMap outputs;
159  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
160  return outputs;
161 }
162 
163 
164 //
165 // QgsDissolveAlgorithm
166 //
167 
168 QString QgsDissolveAlgorithm::name() const
169 {
170  return QStringLiteral( "dissolve" );
171 }
172 
173 QString QgsDissolveAlgorithm::displayName() const
174 {
175  return QObject::tr( "Dissolve" );
176 }
177 
178 QStringList QgsDissolveAlgorithm::tags() const
179 {
180  return QObject::tr( "dissolve,union,combine,collect" ).split( ',' );
181 }
182 
183 QString QgsDissolveAlgorithm::group() const
184 {
185  return QObject::tr( "Vector geometry" );
186 }
187 
188 QString QgsDissolveAlgorithm::groupId() const
189 {
190  return QStringLiteral( "vectorgeometry" );
191 }
192 
193 
194 void QgsDissolveAlgorithm::initAlgorithm( const QVariantMap & )
195 {
196  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
197  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Dissolve field(s)" ), QVariant(),
198  QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
199 
200  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ) ) );
201 }
202 
203 QString QgsDissolveAlgorithm::shortHelpString() const
204 {
205  return QObject::tr( "This algorithm takes a vector layer and combines their features into new features. One or more attributes can "
206  "be specified to dissolve features belonging to the same class (having the same value for the specified attributes), alternatively "
207  "all features can be dissolved in a single one.\n\n"
208  "All output geometries will be converted to multi geometries. "
209  "In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased." );
210 }
211 
212 QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
213 {
214  return new QgsDissolveAlgorithm();
215 }
216 
217 QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
218 {
219  return processCollection( parameters, context, feedback, [ & ]( const QVector< QgsGeometry > &parts )->QgsGeometry
220  {
221  QgsGeometry result( QgsGeometry::unaryUnion( parts ) );
222  if ( QgsWkbTypes::geometryType( result.wkbType() ) == QgsWkbTypes::LineGeometry )
223  result = result.mergeLines();
224  // Geos may fail in some cases, let's try a slower but safer approach
225  // See: https://github.com/qgis/QGIS/issues/28411 - Dissolve tool failing to produce outputs
226  if ( ! result.lastError().isEmpty() && parts.count() > 2 )
227  {
228  if ( feedback->isCanceled() )
229  return result;
230 
231  feedback->pushDebugInfo( QObject::tr( "GEOS exception: taking the slower route ..." ) );
232  result = QgsGeometry();
233  for ( const auto &p : parts )
234  {
235  result = QgsGeometry::unaryUnion( QVector< QgsGeometry >() << result << p );
236  if ( QgsWkbTypes::geometryType( result.wkbType() ) == QgsWkbTypes::LineGeometry )
237  result = result.mergeLines();
238  if ( feedback->isCanceled() )
239  return result;
240  }
241  }
242  if ( ! result.lastError().isEmpty() )
243  {
244  feedback->reportError( result.lastError(), true );
245  if ( result.isEmpty() )
246  throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) );
247  }
248  return result;
249  }, 10000 );
250 }
251 
252 //
253 // QgsCollectAlgorithm
254 //
255 
256 QString QgsCollectAlgorithm::name() const
257 {
258  return QStringLiteral( "collect" );
259 }
260 
261 QString QgsCollectAlgorithm::displayName() const
262 {
263  return QObject::tr( "Collect geometries" );
264 }
265 
266 QStringList QgsCollectAlgorithm::tags() const
267 {
268  return QObject::tr( "union,combine,collect,multipart,parts,single" ).split( ',' );
269 }
270 
271 QString QgsCollectAlgorithm::group() const
272 {
273  return QObject::tr( "Vector geometry" );
274 }
275 
276 QString QgsCollectAlgorithm::groupId() const
277 {
278  return QStringLiteral( "vectorgeometry" );
279 }
280 
281 QVariantMap QgsCollectAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
282 {
283  return processCollection( parameters, context, feedback, []( const QVector< QgsGeometry > &parts )->QgsGeometry
284  {
285  return QgsGeometry::collectGeometry( parts );
287 }
288 
289 
290 void QgsCollectAlgorithm::initAlgorithm( const QVariantMap & )
291 {
292  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
293  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Unique ID fields" ), QVariant(),
294  QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
295 
296  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Collected" ) ) );
297 }
298 
299 QString QgsCollectAlgorithm::shortHelpString() const
300 {
301  return QObject::tr( "This algorithm takes a vector layer and collects its geometries into new multipart geometries. One or more attributes can "
302  "be specified to collect only geometries belonging to the same class (having the same value for the specified attributes), alternatively "
303  "all geometries can be collected." ) +
304  QStringLiteral( "\n\n" ) +
305  QObject::tr( "All output geometries will be converted to multi geometries, even those with just a single part. "
306  "This algorithm does not dissolve overlapping geometries - they will be collected together without modifying the shape of each geometry part." ) +
307  QStringLiteral( "\n\n" ) +
308  QObject::tr( "See the 'Promote to multipart' or 'Aggregate' algorithms for alternative options." );
309 }
310 
311 QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
312 {
313  return new QgsCollectAlgorithm();
314 }
315 
316 
317 
318 
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
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...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:153
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:223
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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.
Definition: qgsgeometry.h:125
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
bool isMultipart() const SIP_HOLDGIL
Returns true if WKB of the geometry is of WKBMulti* type.
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.
Definition: qgsexception.h:83
@ FlagSkipGeometryValidityChecks
Invalid geometry checks should always be skipped. This flag can be useful for algorithms which always...
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 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
static Type multiType(Type type) SIP_HOLDGIL
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:304
const QgsField & field
Definition: qgsfield.h:463