QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
Loading...
Searching...
No Matches
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
19
21
22//
23// QgsCollectorAlgorithm
24//
25
26QVariantMap 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 = parameterAsStrings( 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
203QString QgsDissolveAlgorithm::name() const
204{
205 return QStringLiteral( "dissolve" );
206}
207
208QString QgsDissolveAlgorithm::displayName() const
209{
210 return QObject::tr( "Dissolve" );
211}
212
213QStringList QgsDissolveAlgorithm::tags() const
214{
215 return QObject::tr( "dissolve,union,combine,collect" ).split( ',' );
216}
217
218QString QgsDissolveAlgorithm::group() const
219{
220 return QObject::tr( "Vector geometry" );
221}
222
223QString QgsDissolveAlgorithm::groupId() const
224{
225 return QStringLiteral( "vectorgeometry" );
226}
227
228
229void 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
243QString 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
254QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
255{
256 return new QgsDissolveAlgorithm();
257}
258
259QVariantMap 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() ) == Qgis::GeometryType::Line )
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() ) == Qgis::GeometryType::Line )
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
300QString QgsCollectAlgorithm::name() const
301{
302 return QStringLiteral( "collect" );
303}
304
305QString QgsCollectAlgorithm::displayName() const
306{
307 return QObject::tr( "Collect geometries" );
308}
309
310QStringList QgsCollectAlgorithm::tags() const
311{
312 return QObject::tr( "union,combine,collect,multipart,parts,single" ).split( ',' );
313}
314
315QString QgsCollectAlgorithm::group() const
316{
317 return QObject::tr( "Vector geometry" );
318}
319
320QString QgsCollectAlgorithm::groupId() const
321{
322 return QStringLiteral( "vectorgeometry" );
323}
324
325QVariantMap 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
334void 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
343QString 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
355QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
356{
357 return new QgsCollectAlgorithm();
358}
359
360
361
362
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.
QgsGeometry geometry
Definition qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
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.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
@ 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.
@ FlagAdvanced
Parameter is an advanced parameter which should be hidden from users by default.
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 Qgis::GeometryType geometryType(Qgis::WkbType type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
static Qgis::WkbType multiType(Qgis::WkbType type)
Returns the multi type for a WKB type.