23 static const QString INPUT = QStringLiteral(
"INPUT" );
24 static const QString POINTS_NUMBER = QStringLiteral(
"POINTS_NUMBER" );
25 static const QString MIN_DISTANCE_GLOBAL = QStringLiteral(
"MIN_DISTANCE_GLOBAL" );
26 static const QString MIN_DISTANCE = QStringLiteral(
"MIN_DISTANCE" );
27 static const QString MAX_TRIES_PER_POINT = QStringLiteral(
"MAX_TRIES_PER_POINT" );
28 static const QString SEED = QStringLiteral(
"SEED" );
29 static const QString INCLUDE_POLYGON_ATTRIBUTES = QStringLiteral(
"INCLUDE_POLYGON_ATTRIBUTES" );
30 static const QString OUTPUT = QStringLiteral(
"OUTPUT" );
31 static const QString OUTPUT_POINTS = QStringLiteral(
"OUTPUT_POINTS" );
32 static const QString POINTS_MISSED = QStringLiteral(
"POINTS_MISSED" );
33 static const QString POLYGONS_WITH_MISSED_POINTS = QStringLiteral(
"POLYGONS_WITH_MISSED_POINTS" );
34 static const QString FEATURES_WITH_EMPTY_OR_NO_GEOMETRY = QStringLiteral(
"FEATURES_WITH_EMPTY_OR_NO_GEOMETRY" );
37 QString QgsRandomPointsInPolygonsAlgorithm::name()
const
39 return QStringLiteral(
"randompointsinpolygons" );
42 QString QgsRandomPointsInPolygonsAlgorithm::displayName()
const
44 return QObject::tr(
"Random points in polygons" );
47 QStringList QgsRandomPointsInPolygonsAlgorithm::tags()
const
49 return QObject::tr(
"seed,attributes,create" ).split(
',' );
52 QString QgsRandomPointsInPolygonsAlgorithm::group()
const
54 return QObject::tr(
"Vector creation" );
57 QString QgsRandomPointsInPolygonsAlgorithm::groupId()
const
59 return QStringLiteral(
"vectorcreation" );
62 void QgsRandomPointsInPolygonsAlgorithm::initAlgorithm(
const QVariantMap & )
65 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 numberPointsParam->setIsDynamic(
true );
68 numberPointsParam->setDynamicLayerParameterName( QStringLiteral(
"INPUT" ) );
69 addParameter( numberPointsParam.release() );
71 std::unique_ptr< QgsProcessingParameterDistance > minDistParam = std::make_unique< QgsProcessingParameterDistance >( MIN_DISTANCE, QObject::tr(
"Minimum distance between points" ), 0, INPUT,
true, 0 );
72 minDistParam->setIsDynamic(
true );
74 minDistParam->setDynamicLayerParameterName( QStringLiteral(
"INPUT" ) );
75 addParameter( minDistParam.release() );
77 std::unique_ptr< QgsProcessingParameterDistance > minDistGlobalParam = std::make_unique< QgsProcessingParameterDistance >( MIN_DISTANCE_GLOBAL, QObject::tr(
"Global minimum distance between points" ), 0, INPUT,
true, 0 );
79 addParameter( minDistGlobalParam.release() );
81 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 );
83 maxAttemptsParam->setIsDynamic(
true );
85 maxAttemptsParam->setDynamicLayerParameterName( QStringLiteral(
"INPUT" ) );
86 addParameter( maxAttemptsParam.release() );
88 std::unique_ptr< QgsProcessingParameterNumber > randomSeedParam = std::make_unique< QgsProcessingParameterNumber >( SEED, QObject::tr(
"Random seed" ),
QgsProcessingParameterNumber::Integer, QVariant(),
true, 1 );
90 addParameter( randomSeedParam.release() );
92 std::unique_ptr< QgsProcessingParameterBoolean > includePolygonAttrParam = std::make_unique< QgsProcessingParameterBoolean >( INCLUDE_POLYGON_ATTRIBUTES, QObject::tr(
"Include polygon attributes" ),
true );
94 addParameter( includePolygonAttrParam.release() );
101 addOutput(
new QgsProcessingOutputNumber( POLYGONS_WITH_MISSED_POINTS, QObject::tr(
"Number of polygons with missed points" ) ) );
102 addOutput(
new QgsProcessingOutputNumber( FEATURES_WITH_EMPTY_OR_NO_GEOMETRY, QObject::tr(
"Number of features with empty or no geometry" ) ) );
105 QString QgsRandomPointsInPolygonsAlgorithm::shortHelpString()
const
107 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> "
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> "
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> "
148 QgsRandomPointsInPolygonsAlgorithm *QgsRandomPointsInPolygonsAlgorithm::createInstance()
const
150 return new QgsRandomPointsInPolygonsAlgorithm();
155 mNumPoints = parameterAsInt( parameters, POINTS_NUMBER, context );
157 if ( mDynamicNumPoints )
158 mNumPointsProperty = parameters.value( POINTS_NUMBER ).value<
QgsProperty >();
160 mMinDistance = parameterAsDouble( parameters, MIN_DISTANCE, context );
162 if ( mDynamicMinDistance )
163 mMinDistanceProperty = parameters.value( MIN_DISTANCE ).value<
QgsProperty >();
165 mMaxAttempts = parameterAsInt( parameters, MAX_TRIES_PER_POINT, context );
167 if ( mDynamicMaxAttempts )
168 mMaxAttemptsProperty = parameters.value( MAX_TRIES_PER_POINT ).value<
QgsProperty >();
170 mMinDistanceGlobal = parameterAsDouble( parameters, MIN_DISTANCE_GLOBAL, context );
172 mUseRandomSeed = parameters.value( SEED ).isValid();
173 mRandSeed = parameterAsInt( parameters, SEED, context );
174 mIncludePolygonAttr = parameterAsBoolean( parameters, INCLUDE_POLYGON_ATTRIBUTES, context );
178 QVariantMap QgsRandomPointsInPolygonsAlgorithm::processAlgorithm(
const QVariantMap ¶meters,
181 std::unique_ptr< QgsProcessingFeatureSource > polygonSource( parameterAsSource( parameters, INPUT, context ) );
182 if ( !polygonSource )
186 fields.
append(
QgsField( QStringLiteral(
"rand_point_id" ), QVariant::LongLong ) );
187 if ( mIncludePolygonAttr )
188 fields.
extend( polygonSource->fields() );
191 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, OUTPUT,
196 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, polygonSource.get() );
199 std::random_device rd;
200 std::mt19937 mt( !mUseRandomSeed ? rd() : mRandSeed );
201 const std::uniform_real_distribution<> uniformDist( 0, 1 );
202 std::uniform_int_distribution<> uniformIntDist( 1, 999999999 );
209 int missedPoints = 0;
210 int missedPolygons = 0;
211 int emptyOrNullGeom = 0;
213 long featureCount = 0;
214 long long attempts = 0;
215 const long numberOfFeatures = polygonSource->featureCount();
216 long long desiredNumberOfPoints = 0;
217 const double featureProgressStep = 100.0 / ( numberOfFeatures > 0 ? numberOfFeatures : 1 );
218 double baseFeatureProgress = 0.0;
220 QgsFeatureIterator fitL = mIncludePolygonAttr || mDynamicNumPoints || mDynamicMinDistance || mDynamicMaxAttempts ? polygonSource->getFeatures()
234 baseFeatureProgress += featureProgressStep;
239 if ( polyGeom.isEmpty() )
244 baseFeatureProgress += featureProgressStep;
248 if ( mDynamicNumPoints || mDynamicMinDistance || mDynamicMaxAttempts )
254 int localIndexPoints = 0;
255 int pointsAddedForThisFeature = 0;
257 int numberPointsForThisFeature = mNumPoints;
258 if ( mDynamicNumPoints )
259 numberPointsForThisFeature = mNumPointsProperty.valueAsInt( expressionContext, numberPointsForThisFeature );
260 desiredNumberOfPoints += numberPointsForThisFeature;
261 int maxAttemptsForThisFeature = mMaxAttempts;
262 if ( mDynamicMaxAttempts )
263 maxAttemptsForThisFeature = mMaxAttemptsProperty.
valueAsInt( expressionContext, maxAttemptsForThisFeature );
264 double minDistanceForThisFeature = mMinDistance;
265 if ( mDynamicMinDistance )
266 minDistanceForThisFeature = mMinDistanceProperty.
valueAsDouble( expressionContext, minDistanceForThisFeature );
267 const double pointProgressIncrement = featureProgressStep / ( numberPointsForThisFeature * maxAttemptsForThisFeature );
268 double pointProgress = 0.0;
270 if ( ( minDistanceForThisFeature == 0 ) && ( mMinDistanceGlobal == 0 ) )
272 QVector< QgsPointXY > newPoints = polyGeom.randomPointsInPolygon( numberPointsForThisFeature, mUseRandomSeed ? uniformIntDist( mt ) : 0 );
273 for (
int i = 0; i < newPoints.length(); i++ )
279 pAttrs.append( totNPoints );
280 if ( mIncludePolygonAttr )
290 pointsAddedForThisFeature++;
291 pointProgress += pointProgressIncrement * ( maxAttemptsForThisFeature );
293 feedback->
setProgress( baseFeatureProgress + pointProgress );
299 QVector< QgsPointXY > newPoints = polyGeom.randomPointsInPolygon( numberPointsForThisFeature, [ & ](
const QgsPointXY & newPoint ) ->
bool
305 if ( minDistanceForThisFeature != 0 && mMinDistanceGlobal < minDistanceForThisFeature && localIndexPoints > 0 )
307 const QList<QgsFeatureId> neighbors = localIndex.
nearestNeighbor( newPoint, 1, minDistanceForThisFeature );
309 if ( !neighbors.empty() )
315 if ( mMinDistanceGlobal != 0.0 && indexPoints > 0 )
317 const QList<QgsFeatureId> neighbors = globalIndex.
nearestNeighbor( newPoint, 1, mMinDistanceGlobal );
319 if ( !neighbors.empty() )
327 pAttrs.append( attempts );
334 if ( minDistanceForThisFeature != 0 )
340 if ( mMinDistanceGlobal != 0.0 )
347 }, mUseRandomSeed ? uniformIntDist( mt ) : 0, feedback, maxAttemptsForThisFeature );
350 for (
int i = 0; i < newPoints.length(); i++ )
355 pAttrs.append( totNPoints );
356 if ( mIncludePolygonAttr )
366 pointsAddedForThisFeature++;
367 pointProgress += pointProgressIncrement * ( maxAttemptsForThisFeature );
369 feedback->
setProgress( baseFeatureProgress + pointProgress );
372 baseFeatureProgress += featureProgressStep;
373 if ( pointsAddedForThisFeature < numberPointsForThisFeature )
380 missedPoints = desiredNumberOfPoints - totNPoints;
381 feedback->
pushInfo( QObject::tr(
"Total number of points generated: "
382 "%1\nNumber of missed points: "
383 "%2\nPolygons with missing points: "
384 "%3\nFeatures with empty or missing "
386 ).arg( totNPoints ).arg( missedPoints ).arg( missedPolygons ).arg( emptyOrNullGeom ) );
388 outputs.insert( OUTPUT, ldest );
389 outputs.insert( OUTPUT_POINTS, totNPoints );
390 outputs.insert( POINTS_MISSED, missedPoints );
391 outputs.insert( POLYGONS_WITH_MISSED_POINTS, missedPolygons );
392 outputs.insert( FEATURES_WITH_EMPTY_OR_NO_GEOMETRY, emptyOrNullGeom );