Wednesday 13 July 2016

pyqgis - Clip the result of my QGIS 3.x Processing python script before returning the output?


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

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...