I have written a simple QgsProcessingAlgorithm
that populates a QgsFeatureSink
.
Before outputting the result, I need to call another algorithm to clip my features.
Everything seems to work just fine, the clip algorithm
is called and its output shows in the log, except my actual output is not clipped.
It seems the sink is returned no matter what I specify to be actually returned by the function.
What I am doing wrong ?
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.processing import alg
from qgis.core import (
QgsField,
QgsFields,
QgsFeature,
QgsFeatureSink,
QgsGeometry,
QgsPointXY,
QgsProcessing,
QgsProcessingAlgorithm,
QgsWkbTypes
)
import processing
import random
@alg(name='test_alg', label=alg.tr('Test'), group='test', group_label=alg.tr('Test'))
@alg.input(type=alg.SOURCE, name='INPUT', label='Input')
@alg.input(type=int, name='NB_POINTS', label='Number of points to create', default=1000, minValue=10, maxValue=100000)
@alg.input(type=alg.SINK, name='OUTPUT', label='Output')
def testAlg(instance, parameters, context, feedback, inputs):
"""
Test script to randomly generate n points unniformly distributed within a polygon extent
and clip the result with regards to the polygon itself.
"""
# Define some attributes
fields = QgsFields()
fields.append(QgsField('Point_No', QVariant.Double))
# Initialise variables
source = instance.parameterAsSource(parameters, 'INPUT', context)
nb_points = instance.parameterAsInt(parameters, 'NB_POINTS', context)
# Setup the sink
(sink, dest_id) = instance.parameterAsSink(parameters, 'OUTPUT', context, fields, QgsWkbTypes.Point, source.sourceCrs())
# Define the Points generator
def genPoints(n, extent, attributes):
"""
Generator to produce the Points yielded as Features with attribute
"""
feat = QgsFeature()
xmin, xmax, ymin, ymax = extent.xMinimum(), extent.xMaximum(), extent.yMinimum(), extent.yMaximum()
total = 100 / n
for i in range(n):
feedback.setProgress(i * total)
attributes[0] = i
x, y = random.uniform(xmin, xmax), random.uniform(ymin, ymax)
feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
feat.setAttributes(attributes)
yield feat
feedback.pushInfo(f'Generating {nb_points} Points')
sink.addFeatures(genPoints(nb_points, source.sourceExtent(), [0]),
QgsFeatureSink.FastInsert)
feedback.pushInfo('Clipping to Input Polygon...')
clipped_points = processing.run('native:clip', {
'INPUT': dest_id,
'OVERLAY': parameters['INPUT'],
'OUTPUT': 'memory:ClippedPoints',
}, context=context, feedback=feedback)['OUTPUT']
return {'OUTPUT': clipped_points.id()}
Answer
My feeling is that the QgsProcessingAlgorithm
will return the sink
no matter what because it is setup in the parameters. Incidentally, the way to define a sink output is as an input, but that's another story.
I resorted to create a temporary QgsVectorLayer
to populate with my features, then clip it and only then populate my sink
. It works but does feel a bit stupid to populate the sink at this stage.
It turns out the temporary QgsVectorLayer
is the way to go, but in order to be clipped properly, it is critical to set the Crs otherwise there won't be anything left after the clipping.
The trick here (or at least this is my understanding) is only to direct the output of the clipping algorithm to the sink
and it will be returned just fine (note the is_child_algorithm=True
, this seems to be needed as well, as I've experienced a few crashes without it).
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.processing import alg
from qgis.core import (
QgsField,
QgsFields,
QgsFeature,
QgsGeometry,
QgsPointXY,
QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingUtils,
QgsVectorLayer
)
import processing
import random
@alg(name='test_alg3', label=alg.tr('Test3'), group='test', group_label=alg.tr('Test'))
@alg.input(type=alg.SOURCE, name='INPUT', label='Input')
@alg.input(type=int, name='NB_POINTS', label='Number of points to create', default=1000, minValue=10, maxValue=100000)
@alg.input(type=alg.SINK, name='OUTPUT', label='Output')
def testAlg(instance, parameters, context, feedback, inputs):
"""
Test script to randomly generate n points unniformly distributed within a polygon extent
and clip the result with regards to the polygon itself.
"""
# Define some attributes
fields = QgsFields()
fields.append(QgsField('Point_No', QVariant.Double))
# Initialise variables
source = instance.parameterAsSource(parameters, 'INPUT', context)
nb_points = instance.parameterAsInt(parameters, 'NB_POINTS', context)
# Define the Points generator
def genPoints(n, extent, attributes):
"""
Generator to produce the Points yielded as Features with attribute
"""
feat = QgsFeature()
xmin, xmax, ymin, ymax = extent.xMinimum(), extent.xMaximum(), extent.yMinimum(), extent.yMaximum()
total = 100 / n
for i in range(n):
feedback.setProgress(i * total)
attributes[0] = i
x, y = random.uniform(xmin, xmax), random.uniform(ymin, ymax)
feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
feat.setAttributes(attributes)
yield feat
feedback.pushInfo(f'Generating {nb_points} Points...')
temp = QgsVectorLayer('Point', 'temp', 'memory')
temp.setCrs(source.sourceCrs())
temp.dataProvider().addFeatures(genPoints(nb_points, source.sourceExtent(), [0]))
feedback.pushInfo('Clipping to Input Polygon...')
results = processing.run('native:clip', {
'INPUT': temp,
'OVERLAY': parameters['INPUT'],
'OUTPUT': parameters['OUTPUT']
}, context=context, feedback=feedback, is_child_algorithm=True)
return results
No comments:
Post a Comment