QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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 
QgsWkbTypes::multiType
static Type multiType(Type type)
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:301
QgsFeedback::setProgress
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:75
QgsProcessingFeedback
Definition: qgsprocessingfeedback.h:37
QgsGeometry::isMultipart
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
Definition: qgsgeometry.cpp:377
QgsProcessingFeedback::reportError
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Definition: qgsprocessingfeedback.cpp:39
QgsFeature::geometry
QgsGeometry geometry
Definition: qgsfeature.h:71
QgsProcessingParameterFeatureSource
Definition: qgsprocessingparameters.h:2612
QgsProcessingParameterFeatureSink
Definition: qgsprocessingparameters.h:2773
qgsalgorithmdissolve.h
QgsFeature::setGeometry
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:137
QgsProcessingContext
Definition: qgsprocessingcontext.h:43
QgsWkbTypes::geometryType
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:937
QgsFeature::attribute
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QgsGeometry::isNull
bool isNull
Definition: qgsgeometry.h:125
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:66
QgsFeedback::isCanceled
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:66
QgsWkbTypes::LineGeometry
@ LineGeometry
Definition: qgswkbtypes.h:142
QgsFeatureIterator::nextFeature
bool nextFeature(QgsFeature &f)
Definition: qgsfeatureiterator.h:373
QgsGeometry
Definition: qgsgeometry.h:122
QgsFeature::hasGeometry
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
QgsGeometry::convertToMultiType
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
Definition: qgsgeometry.cpp:1468
QgsFeature
Definition: qgsfeature.h:55
QgsFeature::setAttributes
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:127
QgsProcessingParameterField::Any
@ Any
Accepts any field.
Definition: qgsprocessingparameters.h:2502
QgsFeatureIterator
Definition: qgsfeatureiterator.h:263
QgsProcessingException
Definition: qgsexception.h:82
QgsProcessingParameterField
Definition: qgsprocessingparameters.h:2495
QgsGeometry::unaryUnion
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
Definition: qgsgeometry.cpp:2785
QgsGeometry::collectGeometry
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
Definition: qgsgeometry.cpp:247
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