QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context );
40 
41  long count = source->featureCount();
42 
43  QgsFeature f;
44  QgsFeatureIterator it = source->getFeatures( QgsFeatureRequest(), sourceFlags );
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 );
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.
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:135
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:205
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:302
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:145
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:124
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:126
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:938
static Type multiType(Type type) SIP_HOLDGIL
Returns the multi type for a WKB type.
Definition: qgswkbtypes.h:302
const QgsField & field
Definition: qgsfield.h:463