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