Branch data Line data Source code
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 : :
20 : : ///@cond PRIVATE
21 : :
22 : : //
23 : : // QgsCollectorAlgorithm
24 : : //
25 : :
26 : 0 : QVariantMap QgsCollectorAlgorithm::processCollection( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback,
27 : : const std::function<QgsGeometry( const QVector< QgsGeometry >& )> &collector, int maxQueueLength, QgsProcessingFeatureSource::Flags sourceFlags )
28 : : {
29 : 0 : std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
30 : 0 : if ( !source )
31 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
32 : :
33 : 0 : QString dest;
34 : 0 : std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) );
35 : :
36 : 0 : if ( !sink )
37 : 0 : throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
38 : :
39 : 0 : QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context );
40 : :
41 : 0 : long count = source->featureCount();
42 : :
43 : 0 : QgsFeature f;
44 : 0 : QgsFeatureIterator it = source->getFeatures( QgsFeatureRequest(), sourceFlags );
45 : :
46 : 0 : double step = count > 0 ? 100.0 / count : 1;
47 : 0 : int current = 0;
48 : :
49 : 0 : if ( fields.isEmpty() )
50 : : {
51 : : // dissolve all - not using fields
52 : 0 : bool firstFeature = true;
53 : : // we dissolve geometries in blocks using unaryUnion
54 : 0 : QVector< QgsGeometry > geomQueue;
55 : 0 : QgsFeature outputFeature;
56 : :
57 : 0 : while ( it.nextFeature( f ) )
58 : : {
59 : 0 : if ( feedback->isCanceled() )
60 : : {
61 : 0 : break;
62 : : }
63 : :
64 : 0 : if ( firstFeature )
65 : : {
66 : 0 : outputFeature = f;
67 : 0 : firstFeature = false;
68 : 0 : }
69 : :
70 : 0 : if ( f.hasGeometry() && !f.geometry().isNull() )
71 : : {
72 : 0 : geomQueue.append( f.geometry() );
73 : 0 : if ( maxQueueLength > 0 && geomQueue.length() > maxQueueLength )
74 : : {
75 : : // queue too long, combine it
76 : 0 : QgsGeometry tempOutputGeometry = collector( geomQueue );
77 : 0 : geomQueue.clear();
78 : 0 : geomQueue << tempOutputGeometry;
79 : 0 : }
80 : 0 : }
81 : :
82 : 0 : feedback->setProgress( current * step );
83 : 0 : current++;
84 : : }
85 : :
86 : 0 : outputFeature.setGeometry( collector( geomQueue ) );
87 : 0 : sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
88 : 0 : }
89 : : else
90 : : {
91 : 0 : QList< int > fieldIndexes;
92 : 0 : const auto constFields = fields;
93 : 0 : for ( const QString &field : constFields )
94 : : {
95 : 0 : int index = source->fields().lookupField( field );
96 : 0 : if ( index >= 0 )
97 : 0 : fieldIndexes << index;
98 : : }
99 : :
100 : 0 : QHash< QVariant, QgsAttributes > attributeHash;
101 : 0 : QHash< QVariant, QVector< QgsGeometry > > geometryHash;
102 : :
103 : 0 : while ( it.nextFeature( f ) )
104 : : {
105 : 0 : if ( feedback->isCanceled() )
106 : : {
107 : 0 : break;
108 : : }
109 : :
110 : 0 : QVariantList indexAttributes;
111 : 0 : const auto constFieldIndexes = fieldIndexes;
112 : 0 : for ( int index : constFieldIndexes )
113 : : {
114 : 0 : indexAttributes << f.attribute( index );
115 : : }
116 : :
117 : 0 : if ( !attributeHash.contains( indexAttributes ) )
118 : : {
119 : : // keep attributes of first feature
120 : 0 : attributeHash.insert( indexAttributes, f.attributes() );
121 : 0 : }
122 : :
123 : 0 : if ( f.hasGeometry() && !f.geometry().isNull() )
124 : : {
125 : 0 : geometryHash[ indexAttributes ].append( f.geometry() );
126 : 0 : }
127 : 0 : }
128 : :
129 : 0 : int numberFeatures = attributeHash.count();
130 : 0 : QHash< QVariant, QgsAttributes >::const_iterator attrIt = attributeHash.constBegin();
131 : 0 : for ( ; attrIt != attributeHash.constEnd(); ++attrIt )
132 : : {
133 : 0 : if ( feedback->isCanceled() )
134 : : {
135 : 0 : break;
136 : : }
137 : :
138 : 0 : QgsFeature outputFeature;
139 : 0 : if ( geometryHash.contains( attrIt.key() ) )
140 : : {
141 : 0 : QgsGeometry geom = collector( geometryHash.value( attrIt.key() ) );
142 : 0 : if ( !geom.isMultipart() )
143 : : {
144 : 0 : geom.convertToMultiType();
145 : 0 : }
146 : 0 : outputFeature.setGeometry( geom );
147 : 0 : }
148 : 0 : outputFeature.setAttributes( attrIt.value() );
149 : 0 : sink->addFeature( outputFeature, QgsFeatureSink::FastInsert );
150 : :
151 : 0 : feedback->setProgress( current * 100.0 / numberFeatures );
152 : 0 : current++;
153 : 0 : }
154 : 0 : }
155 : :
156 : 0 : QVariantMap outputs;
157 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), dest );
158 : 0 : return outputs;
159 : 0 : }
160 : :
161 : :
162 : : //
163 : : // QgsDissolveAlgorithm
164 : : //
165 : :
166 : 0 : QString QgsDissolveAlgorithm::name() const
167 : : {
168 : 0 : return QStringLiteral( "dissolve" );
169 : : }
170 : :
171 : 0 : QString QgsDissolveAlgorithm::displayName() const
172 : : {
173 : 0 : return QObject::tr( "Dissolve" );
174 : : }
175 : :
176 : 0 : QStringList QgsDissolveAlgorithm::tags() const
177 : : {
178 : 0 : return QObject::tr( "dissolve,union,combine,collect" ).split( ',' );
179 : 0 : }
180 : :
181 : 0 : QString QgsDissolveAlgorithm::group() const
182 : : {
183 : 0 : return QObject::tr( "Vector geometry" );
184 : : }
185 : :
186 : 0 : QString QgsDissolveAlgorithm::groupId() const
187 : : {
188 : 0 : return QStringLiteral( "vectorgeometry" );
189 : : }
190 : :
191 : :
192 : 0 : void QgsDissolveAlgorithm::initAlgorithm( const QVariantMap & )
193 : : {
194 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
195 : 0 : addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Dissolve field(s)" ), QVariant(),
196 : 0 : QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
197 : :
198 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ) ) );
199 : 0 : }
200 : :
201 : 0 : QString QgsDissolveAlgorithm::shortHelpString() const
202 : : {
203 : 0 : 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 : 0 : QgsDissolveAlgorithm *QgsDissolveAlgorithm::createInstance() const
211 : : {
212 : 0 : return new QgsDissolveAlgorithm();
213 : : }
214 : :
215 : 0 : QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
216 : : {
217 : 0 : return processCollection( parameters, context, feedback, [ & ]( const QVector< QgsGeometry > &parts )->QgsGeometry
218 : : {
219 : 0 : QgsGeometry result( QgsGeometry::unaryUnion( parts ) );
220 : 0 : if ( QgsWkbTypes::geometryType( result.wkbType() ) == QgsWkbTypes::LineGeometry )
221 : 0 : 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 : 0 : if ( ! result.lastError().isEmpty() && parts.count() > 2 )
225 : : {
226 : 0 : if ( feedback->isCanceled() )
227 : 0 : return result;
228 : :
229 : 0 : feedback->pushDebugInfo( QObject::tr( "GEOS exception: taking the slower route ..." ) );
230 : 0 : result = QgsGeometry();
231 : 0 : for ( const auto &p : parts )
232 : : {
233 : 0 : result = QgsGeometry::unaryUnion( QVector< QgsGeometry >() << result << p );
234 : 0 : if ( QgsWkbTypes::geometryType( result.wkbType() ) == QgsWkbTypes::LineGeometry )
235 : 0 : result = result.mergeLines();
236 : 0 : if ( feedback->isCanceled() )
237 : 0 : return result;
238 : : }
239 : 0 : }
240 : 0 : if ( ! result.lastError().isEmpty() )
241 : : {
242 : 0 : feedback->reportError( result.lastError(), true );
243 : 0 : if ( result.isEmpty() )
244 : 0 : throw QgsProcessingException( QObject::tr( "The algorithm returned no output." ) );
245 : 0 : }
246 : 0 : return result;
247 : 0 : }, 10000 );
248 : 0 : }
249 : :
250 : : //
251 : : // QgsCollectAlgorithm
252 : : //
253 : :
254 : 0 : QString QgsCollectAlgorithm::name() const
255 : : {
256 : 0 : return QStringLiteral( "collect" );
257 : : }
258 : :
259 : 0 : QString QgsCollectAlgorithm::displayName() const
260 : : {
261 : 0 : return QObject::tr( "Collect geometries" );
262 : : }
263 : :
264 : 0 : QStringList QgsCollectAlgorithm::tags() const
265 : : {
266 : 0 : return QObject::tr( "union,combine,collect,multipart,parts,single" ).split( ',' );
267 : 0 : }
268 : :
269 : 0 : QString QgsCollectAlgorithm::group() const
270 : : {
271 : 0 : return QObject::tr( "Vector geometry" );
272 : : }
273 : :
274 : 0 : QString QgsCollectAlgorithm::groupId() const
275 : : {
276 : 0 : return QStringLiteral( "vectorgeometry" );
277 : : }
278 : :
279 : 0 : QVariantMap QgsCollectAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
280 : : {
281 : 0 : return processCollection( parameters, context, feedback, []( const QVector< QgsGeometry > &parts )->QgsGeometry
282 : : {
283 : 0 : return QgsGeometry::collectGeometry( parts );
284 : 0 : }, 0, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
285 : 0 : }
286 : :
287 : :
288 : 0 : void QgsCollectAlgorithm::initAlgorithm( const QVariantMap & )
289 : : {
290 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
291 : 0 : addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Unique ID fields" ), QVariant(),
292 : 0 : QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) );
293 : :
294 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Collected" ) ) );
295 : 0 : }
296 : :
297 : 0 : QString QgsCollectAlgorithm::shortHelpString() const
298 : : {
299 : 0 : 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 : 0 : "all geometries can be collected." ) +
302 : 0 : QStringLiteral( "\n\n" ) +
303 : 0 : QObject::tr( "All output geometries will be converted to multi geometries, even those with just a single part. "
304 : 0 : "This algorithm does not dissolve overlapping geometries - they will be collected together without modifying the shape of each geometry part." ) +
305 : 0 : QStringLiteral( "\n\n" ) +
306 : 0 : QObject::tr( "See the 'Promote to multipart' or 'Aggregate' algorithms for alternative options." );
307 : 0 : }
308 : :
309 : 0 : QgsCollectAlgorithm *QgsCollectAlgorithm::createInstance() const
310 : : {
311 : 0 : return new QgsCollectAlgorithm();
312 : : }
313 : :
314 : :
315 : :
316 : :
317 : : ///@endcond
|