Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmrandompointsinpolygons.cpp
3 : : ---------------------
4 : : begin : March 2020
5 : : copyright : (C) 2020 by HÃ¥vard Tveite
6 : : email : havard dot tveite at nmbu dot no
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 : :
19 : : #include "qgsalgorithmrandompointsinpolygons.h"
20 : : #include <random>
21 : :
22 : : // The algorithm parameter names:
23 : 4 : static const QString INPUT = QStringLiteral( "INPUT" );
24 : 4 : static const QString POINTS_NUMBER = QStringLiteral( "POINTS_NUMBER" );
25 : 4 : static const QString MIN_DISTANCE_GLOBAL = QStringLiteral( "MIN_DISTANCE_GLOBAL" );
26 : 4 : static const QString MIN_DISTANCE = QStringLiteral( "MIN_DISTANCE" );
27 : 4 : static const QString MAX_TRIES_PER_POINT = QStringLiteral( "MAX_TRIES_PER_POINT" );
28 : 4 : static const QString SEED = QStringLiteral( "SEED" );
29 : 4 : static const QString INCLUDE_POLYGON_ATTRIBUTES = QStringLiteral( "INCLUDE_POLYGON_ATTRIBUTES" );
30 : 4 : static const QString OUTPUT = QStringLiteral( "OUTPUT" );
31 : 4 : static const QString OUTPUT_POINTS = QStringLiteral( "OUTPUT_POINTS" );
32 : 4 : static const QString POINTS_MISSED = QStringLiteral( "POINTS_MISSED" );
33 : 4 : static const QString POLYGONS_WITH_MISSED_POINTS = QStringLiteral( "POLYGONS_WITH_MISSED_POINTS" );
34 : 4 : static const QString FEATURES_WITH_EMPTY_OR_NO_GEOMETRY = QStringLiteral( "FEATURES_WITH_EMPTY_OR_NO_GEOMETRY" );
35 : : ///@cond PRIVATE
36 : :
37 : 0 : QString QgsRandomPointsInPolygonsAlgorithm::name() const
38 : : {
39 : 0 : return QStringLiteral( "randompointsinpolygons" );
40 : : }
41 : :
42 : 0 : QString QgsRandomPointsInPolygonsAlgorithm::displayName() const
43 : : {
44 : 0 : return QObject::tr( "Random points in polygons" );
45 : : }
46 : :
47 : 0 : QStringList QgsRandomPointsInPolygonsAlgorithm::tags() const
48 : : {
49 : 0 : return QObject::tr( "seed,attributes,create" ).split( ',' );
50 : 0 : }
51 : :
52 : 0 : QString QgsRandomPointsInPolygonsAlgorithm::group() const
53 : : {
54 : 0 : return QObject::tr( "Vector creation" );
55 : : }
56 : :
57 : 0 : QString QgsRandomPointsInPolygonsAlgorithm::groupId() const
58 : : {
59 : 0 : return QStringLiteral( "vectorcreation" );
60 : : }
61 : :
62 : 0 : void QgsRandomPointsInPolygonsAlgorithm::initAlgorithm( const QVariantMap & )
63 : : {
64 : 0 : addParameter( new QgsProcessingParameterFeatureSource( INPUT, QObject::tr( "Input polygon layer" ), QList< int >() << QgsProcessing::TypeVectorPolygon ) );
65 : 0 : std::unique_ptr< QgsProcessingParameterNumber > numberPointsParam = std::make_unique< QgsProcessingParameterNumber >( POINTS_NUMBER, QObject::tr( "Number of points for each feature" ), QgsProcessingParameterNumber::Integer, 1, false, 1 );
66 : 0 : numberPointsParam->setIsDynamic( true );
67 : 0 : numberPointsParam->setDynamicPropertyDefinition( QgsPropertyDefinition( POINTS_NUMBER, QObject::tr( "Number of points for each feature" ), QgsPropertyDefinition::IntegerPositive ) );
68 : 0 : numberPointsParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
69 : 0 : addParameter( numberPointsParam.release() );
70 : :
71 : 0 : std::unique_ptr< QgsProcessingParameterDistance > minDistParam = std::make_unique< QgsProcessingParameterDistance >( MIN_DISTANCE, QObject::tr( "Minimum distance between points" ), 0, INPUT, true, 0 );
72 : 0 : minDistParam->setIsDynamic( true );
73 : 0 : minDistParam->setDynamicPropertyDefinition( QgsPropertyDefinition( MIN_DISTANCE, QObject::tr( "Minimum distance between points" ), QgsPropertyDefinition::DoublePositive ) );
74 : 0 : minDistParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
75 : 0 : addParameter( minDistParam.release() );
76 : :
77 : 0 : std::unique_ptr< QgsProcessingParameterDistance > minDistGlobalParam = std::make_unique< QgsProcessingParameterDistance >( MIN_DISTANCE_GLOBAL, QObject::tr( "Global minimum distance between points" ), 0, INPUT, true, 0 );
78 : 0 : minDistGlobalParam->setFlags( minDistGlobalParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
79 : 0 : addParameter( minDistGlobalParam.release() );
80 : :
81 : 0 : std::unique_ptr< QgsProcessingParameterNumber > maxAttemptsParam = std::make_unique< QgsProcessingParameterNumber >( MAX_TRIES_PER_POINT, QObject::tr( "Maximum number of search attempts (for Min. dist. > 0)" ), QgsProcessingParameterNumber::Integer, 10, true, 1 );
82 : 0 : maxAttemptsParam->setFlags( maxAttemptsParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
83 : 0 : maxAttemptsParam->setIsDynamic( true );
84 : 0 : maxAttemptsParam->setDynamicPropertyDefinition( QgsPropertyDefinition( MAX_TRIES_PER_POINT, QObject::tr( "Maximum number of attempts per point (for Min. dist. > 0)" ), QgsPropertyDefinition::IntegerPositiveGreaterZero ) );
85 : 0 : maxAttemptsParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
86 : 0 : addParameter( maxAttemptsParam.release() );
87 : :
88 : 0 : std::unique_ptr< QgsProcessingParameterNumber > randomSeedParam = std::make_unique< QgsProcessingParameterNumber >( SEED, QObject::tr( "Random seed" ), QgsProcessingParameterNumber::Integer, QVariant(), true, 1 );
89 : 0 : randomSeedParam->setFlags( randomSeedParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
90 : 0 : addParameter( randomSeedParam.release() );
91 : :
92 : 0 : std::unique_ptr< QgsProcessingParameterBoolean > includePolygonAttrParam = std::make_unique< QgsProcessingParameterBoolean >( INCLUDE_POLYGON_ATTRIBUTES, QObject::tr( "Include polygon attributes" ), true );
93 : 0 : includePolygonAttrParam->setFlags( includePolygonAttrParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
94 : 0 : addParameter( includePolygonAttrParam.release() );
95 : :
96 : 0 : addParameter( new
97 : 0 : QgsProcessingParameterFeatureSink( OUTPUT, QObject::tr( "Random points in polygons" ), QgsProcessing::TypeVectorPoint ) );
98 : :
99 : 0 : addOutput( new QgsProcessingOutputNumber( OUTPUT_POINTS, QObject::tr( "Total number of points generated" ) ) );
100 : 0 : addOutput( new QgsProcessingOutputNumber( POINTS_MISSED, QObject::tr( "Number of missed points" ) ) );
101 : 0 : addOutput( new QgsProcessingOutputNumber( POLYGONS_WITH_MISSED_POINTS, QObject::tr( "Number of polygons with missed points" ) ) );
102 : 0 : addOutput( new QgsProcessingOutputNumber( FEATURES_WITH_EMPTY_OR_NO_GEOMETRY, QObject::tr( "Number of features with empty or no geometry" ) ) );
103 : 0 : }
104 : :
105 : 0 : QString QgsRandomPointsInPolygonsAlgorithm::shortHelpString() const
106 : : {
107 : 0 : return QObject::tr( "<p>This algorithm creates a point layer, with points placed randomly "
108 : : "in the polygons of the <i><b>Input polygon layer</b></i>.</p> "
109 : : "<ul><li>For each feature in the <i><b>Input polygon layer</b></i>, the algorithm attempts to add "
110 : : "the specified <i><b>Number of points for each feature</b></i> to the output layer.</li> "
111 : : "<li>A <i><b>Minimum distance between points</b></i> and a "
112 : : "<i><b>Global minimum distance between points</b></i> can be specified.<br> "
113 : : "A point will not be added if there is an already generated point within "
114 : : "this (Euclidean) distance from the generated location. "
115 : : "With <i>Minimum distance between points</i>, only points in the same "
116 : : "polygon feature are considered, while for <i>Global minimum distance "
117 : : "between points</i> all previously generated points are considered. "
118 : : "If the <i>Global minimum distance between points</i> is set equal to "
119 : : "or larger than the (local) <i>Minimum distance between points</i>, the "
120 : : "latter has no effect.<br> "
121 : : "If the <i>Minimum distance between points</i> is too large, "
122 : : "it may not be possible to generate the specified <i>Number of points "
123 : : "for each feature</i>, but all the generated points are returned.</li> "
124 : : "<li>The <i><b>Maximum number of attempts per point</b></i> can be specified.</li> "
125 : : "<li>The seed for the random generator can be provided (<b><i>Random seed</i></b> "
126 : : "- integer, greater than 0).</li> "
127 : : "<li>The user can choose not to <i><b>Include polygon feature attributes</b></i> in "
128 : : "the attributes of the generated point features.</li> "
129 : : "</ul> "
130 : : "The total number of points will be<br> <b>'number of input features'</b> * "
131 : : "<i><b>Number of points for each feature</b></i><br> if there are no misses. "
132 : : "The <i>Number of points for each feature</i>, <i>Minimum distance between points</i> "
133 : : "and <i>Maximum number of attempts per point</i> can be data defined. "
134 : : "<p>Output from the algorithm:</p> "
135 : : "<ul> "
136 : : "<li> The number of features with an empty or no geometry "
137 : : "(<code>FEATURES_WITH_EMPTY_OR_NO_GEOMETRY</code>).</li> "
138 : : "<li> A point layer containing the random points (<code>OUTPUT</code>).</li> "
139 : : "<li> The number of generated features (<code>OUTPUT_POINTS</code>).</li> "
140 : : "<li> The number of missed points (<code>POINTS_MISSED</code>).</li> "
141 : : "<li> The number of features with non-empty geometry and missing points "
142 : : "(<code>POLYGONS_WITH_MISSED_POINTS</code>).</li> "
143 : : "</ul>"
144 : : );
145 : : }
146 : :
147 : :
148 : 0 : QgsRandomPointsInPolygonsAlgorithm *QgsRandomPointsInPolygonsAlgorithm::createInstance() const
149 : : {
150 : 0 : return new QgsRandomPointsInPolygonsAlgorithm();
151 : 0 : }
152 : :
153 : 0 : bool QgsRandomPointsInPolygonsAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
154 : : {
155 : 0 : mNumPoints = parameterAsInt( parameters, POINTS_NUMBER, context );
156 : 0 : mDynamicNumPoints = QgsProcessingParameters::isDynamic( parameters, POINTS_NUMBER );
157 : 0 : if ( mDynamicNumPoints )
158 : 0 : mNumPointsProperty = parameters.value( POINTS_NUMBER ).value< QgsProperty >();
159 : :
160 : 0 : mMinDistance = parameterAsDouble( parameters, MIN_DISTANCE, context );
161 : 0 : mDynamicMinDistance = QgsProcessingParameters::isDynamic( parameters, MIN_DISTANCE );
162 : 0 : if ( mDynamicMinDistance )
163 : 0 : mMinDistanceProperty = parameters.value( MIN_DISTANCE ).value< QgsProperty >();
164 : :
165 : 0 : mMaxAttempts = parameterAsInt( parameters, MAX_TRIES_PER_POINT, context );
166 : 0 : mDynamicMaxAttempts = QgsProcessingParameters::isDynamic( parameters, MAX_TRIES_PER_POINT );
167 : 0 : if ( mDynamicMaxAttempts )
168 : 0 : mMaxAttemptsProperty = parameters.value( MAX_TRIES_PER_POINT ).value< QgsProperty >();
169 : :
170 : 0 : mMinDistanceGlobal = parameterAsDouble( parameters, MIN_DISTANCE_GLOBAL, context );
171 : :
172 : 0 : mUseRandomSeed = parameters.value( SEED ).isValid();
173 : 0 : mRandSeed = parameterAsInt( parameters, SEED, context );
174 : 0 : mIncludePolygonAttr = parameterAsBoolean( parameters, INCLUDE_POLYGON_ATTRIBUTES, context );
175 : 0 : return true;
176 : 0 : }
177 : :
178 : 0 : QVariantMap QgsRandomPointsInPolygonsAlgorithm::processAlgorithm( const QVariantMap ¶meters,
179 : : QgsProcessingContext &context, QgsProcessingFeedback *feedback )
180 : : {
181 : 0 : std::unique_ptr< QgsProcessingFeatureSource > polygonSource( parameterAsSource( parameters, INPUT, context ) );
182 : 0 : if ( !polygonSource )
183 : 0 : throw QgsProcessingException( invalidSourceError( parameters, INPUT ) );
184 : :
185 : 0 : QgsFields fields;
186 : 0 : fields.append( QgsField( QStringLiteral( "rand_point_id" ), QVariant::LongLong ) );
187 : 0 : if ( mIncludePolygonAttr )
188 : 0 : fields.extend( polygonSource->fields() );
189 : :
190 : 0 : QString ldest;
191 : 0 : std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, OUTPUT,
192 : 0 : context, ldest, fields, QgsWkbTypes::Point, polygonSource->sourceCrs() ) );
193 : 0 : if ( !sink )
194 : 0 : throw QgsProcessingException( invalidSinkError( parameters, OUTPUT ) );
195 : :
196 : 0 : QgsExpressionContext expressionContext = createExpressionContext( parameters, context, polygonSource.get() );
197 : :
198 : : // Initialize random engine -- note that we only use this if the user has specified a fixed seed
199 : 0 : std::random_device rd;
200 : 0 : std::mt19937 mt( !mUseRandomSeed ? rd() : mRandSeed );
201 : 0 : std::uniform_real_distribution<> uniformDist( 0, 1 );
202 : 0 : std::uniform_int_distribution<> uniformIntDist( 1, 999999999 );
203 : :
204 : : // Index for finding global close points (mMinDistance != 0)
205 : 0 : QgsSpatialIndex globalIndex;
206 : 0 : int indexPoints = 0;
207 : :
208 : 0 : int totNPoints = 0;
209 : 0 : int missedPoints = 0;
210 : 0 : int missedPolygons = 0;
211 : 0 : int emptyOrNullGeom = 0;
212 : :
213 : 0 : long featureCount = 0;
214 : 0 : long long attempts = 0; // used for unique feature IDs in the indexes
215 : 0 : long numberOfFeatures = polygonSource->featureCount();
216 : 0 : long long desiredNumberOfPoints = 0;
217 : 0 : const double featureProgressStep = 100.0 / ( numberOfFeatures > 0 ? numberOfFeatures : 1 );
218 : 0 : double baseFeatureProgress = 0.0;
219 : 0 : QgsFeature polyFeat;
220 : 0 : QgsFeatureIterator fitL = mIncludePolygonAttr || mDynamicNumPoints || mDynamicMinDistance || mDynamicMaxAttempts ? polygonSource->getFeatures()
221 : 0 : : polygonSource->getFeatures( QgsFeatureRequest().setNoAttributes() );
222 : 0 : while ( fitL.nextFeature( polyFeat ) )
223 : : {
224 : 0 : if ( feedback->isCanceled() )
225 : : {
226 : 0 : feedback->setProgress( 0 );
227 : 0 : break;
228 : : }
229 : 0 : if ( !polyFeat.hasGeometry() )
230 : : {
231 : : // Increment invalid features count
232 : 0 : emptyOrNullGeom++;
233 : 0 : featureCount++;
234 : 0 : baseFeatureProgress += featureProgressStep;
235 : 0 : feedback->setProgress( baseFeatureProgress );
236 : 0 : continue;
237 : : }
238 : 0 : QgsGeometry polyGeom( polyFeat.geometry() );
239 : 0 : if ( polyGeom.isEmpty() )
240 : : {
241 : : // Increment invalid features count
242 : 0 : emptyOrNullGeom++;
243 : 0 : featureCount++;
244 : 0 : baseFeatureProgress += featureProgressStep;
245 : 0 : feedback->setProgress( baseFeatureProgress );
246 : 0 : continue;
247 : : }
248 : 0 : if ( mDynamicNumPoints || mDynamicMinDistance || mDynamicMaxAttempts )
249 : : {
250 : 0 : expressionContext.setFeature( polyFeat );
251 : 0 : }
252 : : // (Re)initialize the local (per polygon) index
253 : 0 : QgsSpatialIndex localIndex;
254 : 0 : int localIndexPoints = 0;
255 : 0 : int pointsAddedForThisFeature = 0;
256 : : // Get data defined parameters
257 : 0 : int numberPointsForThisFeature = mNumPoints;
258 : 0 : if ( mDynamicNumPoints )
259 : 0 : numberPointsForThisFeature = mNumPointsProperty.valueAsInt( expressionContext, numberPointsForThisFeature );
260 : 0 : desiredNumberOfPoints += numberPointsForThisFeature;
261 : 0 : int maxAttemptsForThisFeature = mMaxAttempts;
262 : 0 : if ( mDynamicMaxAttempts )
263 : 0 : maxAttemptsForThisFeature = mMaxAttemptsProperty.valueAsInt( expressionContext, maxAttemptsForThisFeature );
264 : 0 : double minDistanceForThisFeature = mMinDistance;
265 : 0 : if ( mDynamicMinDistance )
266 : 0 : minDistanceForThisFeature = mMinDistanceProperty.valueAsDouble( expressionContext, minDistanceForThisFeature );
267 : 0 : const double pointProgressIncrement = featureProgressStep / ( numberPointsForThisFeature * maxAttemptsForThisFeature );
268 : 0 : double pointProgress = 0.0;
269 : : // Check if we can avoid using the acceptPoint function
270 : 0 : if ( ( minDistanceForThisFeature == 0 ) && ( mMinDistanceGlobal == 0 ) )
271 : : {
272 : 0 : QVector< QgsPointXY > newPoints = polyGeom.randomPointsInPolygon( numberPointsForThisFeature, mUseRandomSeed ? uniformIntDist( mt ) : 0 );
273 : 0 : for ( int i = 0; i < newPoints.length(); i++ )
274 : : {
275 : : // add the point
276 : 0 : QgsPointXY pt = newPoints[i];
277 : 0 : QgsFeature f = QgsFeature( totNPoints );
278 : 0 : QgsAttributes pAttrs = QgsAttributes();
279 : 0 : pAttrs.append( totNPoints );
280 : 0 : if ( mIncludePolygonAttr )
281 : : {
282 : 0 : pAttrs.append( polyFeat.attributes() );
283 : 0 : }
284 : 0 : f.setAttributes( pAttrs );
285 : 0 : QgsGeometry newGeom = QgsGeometry::fromPointXY( pt );
286 : 0 : f.setGeometry( newGeom );
287 : 0 : sink->addFeature( f, QgsFeatureSink::FastInsert );
288 : 0 : totNPoints++;
289 : 0 : pointsAddedForThisFeature++;
290 : 0 : pointProgress += pointProgressIncrement * ( maxAttemptsForThisFeature );
291 : 0 : }
292 : 0 : feedback->setProgress( baseFeatureProgress + pointProgress );
293 : : continue;
294 : 0 : }
295 : : else
296 : : {
297 : : // Have to check for minimum distance, provide the acceptPoints function
298 : 0 : QVector< QgsPointXY > newPoints = polyGeom.randomPointsInPolygon( numberPointsForThisFeature, [ & ]( const QgsPointXY & newPoint ) -> bool
299 : : {
300 : 0 : attempts++;
301 : : // May have to check minimum distance to existing points
302 : : // The first point can always be added
303 : : // Local first (if larger than global)
304 : 0 : if ( minDistanceForThisFeature != 0 && mMinDistanceGlobal < minDistanceForThisFeature && localIndexPoints > 0 )
305 : : {
306 : 0 : QList<QgsFeatureId> neighbors = localIndex.nearestNeighbor( newPoint, 1, minDistanceForThisFeature );
307 : : //if ( totNPoints > 0 && !neighbors.empty() )
308 : 0 : if ( !neighbors.empty() )
309 : : {
310 : 0 : return false;
311 : : }
312 : 0 : }
313 : : // The global
314 : 0 : if ( mMinDistanceGlobal != 0.0 && indexPoints > 0 )
315 : : {
316 : 0 : QList<QgsFeatureId> neighbors = globalIndex.nearestNeighbor( newPoint, 1, mMinDistanceGlobal );
317 : : //if ( totNPoints > 0 && !neighbors.empty() )
318 : 0 : if ( !neighbors.empty() )
319 : : {
320 : 0 : return false;
321 : : }
322 : 0 : }
323 : : // Point is accepted - add it to the indexes
324 : 0 : QgsFeature f = QgsFeature( attempts );
325 : 0 : QgsAttributes pAttrs = QgsAttributes();
326 : 0 : pAttrs.append( attempts );
327 : 0 : f.setAttributes( pAttrs );
328 : 0 : QgsGeometry newGeom = QgsGeometry::fromPointXY( newPoint );
329 : :
330 : 0 : f.setGeometry( newGeom );
331 : : //totNPoints++;
332 : :
333 : 0 : if ( minDistanceForThisFeature != 0 )
334 : : {
335 : 0 : localIndex.addFeature( f );
336 : 0 : localIndexPoints++;
337 : 0 : }
338 : 0 : if ( mMinDistanceGlobal != 0.0 )
339 : : {
340 : 0 : globalIndex.addFeature( f );
341 : 0 : indexPoints++;
342 : 0 : }
343 : 0 : return true;
344 : 0 : }, mUseRandomSeed ? uniformIntDist( mt ) : 0, feedback, maxAttemptsForThisFeature );
345 : :
346 : : // create and output features for the generated points
347 : 0 : for ( int i = 0; i < newPoints.length(); i++ )
348 : : {
349 : 0 : QgsPointXY pt = newPoints[i];
350 : 0 : QgsFeature f = QgsFeature( totNPoints );
351 : 0 : QgsAttributes pAttrs = QgsAttributes();
352 : 0 : pAttrs.append( totNPoints );
353 : 0 : if ( mIncludePolygonAttr )
354 : : {
355 : 0 : pAttrs.append( polyFeat.attributes() );
356 : 0 : }
357 : 0 : f.setAttributes( pAttrs );
358 : 0 : QgsGeometry newGeom = QgsGeometry::fromPointXY( pt );
359 : 0 : f.setGeometry( newGeom );
360 : 0 : sink->addFeature( f, QgsFeatureSink::FastInsert );
361 : 0 : totNPoints++;
362 : 0 : pointsAddedForThisFeature++;
363 : 0 : pointProgress += pointProgressIncrement * ( maxAttemptsForThisFeature );
364 : 0 : }
365 : 0 : feedback->setProgress( baseFeatureProgress + pointProgress );
366 : 0 : }
367 : :
368 : 0 : baseFeatureProgress += featureProgressStep;
369 : 0 : if ( pointsAddedForThisFeature < numberPointsForThisFeature )
370 : : {
371 : 0 : missedPolygons++;
372 : 0 : }
373 : 0 : featureCount++;
374 : 0 : feedback->setProgress( baseFeatureProgress );
375 : 0 : } // while features
376 : 0 : missedPoints = desiredNumberOfPoints - totNPoints;
377 : 0 : feedback->pushInfo( QObject::tr( "Total number of points generated: "
378 : : "%1\nNumber of missed points: "
379 : : "%2\nPolygons with missing points: "
380 : : "%3\nFeatures with empty or missing "
381 : : "geometries: %4"
382 : 0 : ).arg( totNPoints ).arg( missedPoints ).arg( missedPolygons ).arg( emptyOrNullGeom ) );
383 : 0 : QVariantMap outputs;
384 : 0 : outputs.insert( OUTPUT, ldest );
385 : 0 : outputs.insert( OUTPUT_POINTS, totNPoints );
386 : 0 : outputs.insert( POINTS_MISSED, missedPoints );
387 : 0 : outputs.insert( POLYGONS_WITH_MISSED_POINTS, missedPolygons );
388 : 0 : outputs.insert( FEATURES_WITH_EMPTY_OR_NO_GEOMETRY, emptyOrNullGeom );
389 : :
390 : 0 : return outputs;
391 : 0 : }
392 : :
393 : : ///@endcond
|