pyqgis – Adding ‘Draw on Canvas’ Option for Input Box in QGIS Graphical Modeler

pyqgisqgis-3qgis-modeler

I have a very simple model created in Qgis Graphical Modeler. It takes polygon vector data as input and generates random points inside. What I want is to add an option to introduce input data using "Draw on Canvas" as present in "Create layer from extent" tool in addition to the "Select File" option. Is there a way to do that in Graphical Modeler? Or is it somehow possible to integrate drawing option in pyqgis? My model and requested option can be seen in the images below:

Model:

enter image description here

Option to be added:

enter image description here

Best Answer

Here is a PyQGIS solution (in the form of a custom processing script) which should do what you are asking.

This script algorithm has two optional input parameters. The first is a QgsProcessingParameterVectorLayer, the second a QgsProcessingParameterExtent. Therefore the input can either be a polygon vector layer, or an extent (layer, canvas, or draw on canvas). If both inputs are filled, the layer will be used and the extent ignored, if neither are filled, a helpful error dialog will be shown.

If a layer input is used, a processing.run() call is made to "native:randompointsinpolygons" as a child algorithm.

if an extent is used, the call is made to "native:randompointsinextent" as a child algorithm.

In this way your requirement is satisfied, that the user can select either an input layer or file, or draw a rectangle extent on the canvas, and the script will handle and process the input accordingly.

If you save this script as a .py file, you can add it to the Processing Toolbox by clicking the Python icon on the toolbar at the top of the Processing Toolbox dock widget, and selecting 'Add Script to Toolbox...'. You can then find and run the algorithm under Scripts -> Examples.

enter image description here

The dialog for the algorithm will look like this:

enter image description here

The full processing script is shown below:

"""
Name : Random_points_from_input
Group : Examples

INSTRUCTIONS: Save this script as .py file. Open the QGIS Processing Toolbox,
click the 'Scripts' tool button and select 'Add Script to Toolbox', navigate to
where you saved this script and click 'Open'. You can then double click the script
in the toolbox to run this model like any other processing algorithm.
"""
from qgis.PyQt.QtCore import QVariant

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingFeedback
from qgis.core import QgsProcessingParameterExtent
from qgis.core import QgsProcessingParameterVectorLayer
from qgis.core import QgsProcessingParameterVectorDestination
from qgis.core import QgsProject

import processing


class Random_points_from_input(QgsProcessingAlgorithm):
    INPUT = 'INPUT'
    EXTENT = 'EXTENT'
    OUTPUT = 'OUTPUT'

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, 'Input Polygon Layer', types=[QgsProcessing.TypeVectorPolygon], defaultValue=None, optional=True))
        self.addParameter(QgsProcessingParameterExtent(self.EXTENT, 'Extent (Will be ignored if input polygon layer is supplied)',
                            defaultValue=None,
                            optional=True))
        self.addParameter(QgsProcessingParameterVectorDestination(self.OUTPUT, 'Random Points', type=QgsProcessing.TypeVectorPoint, createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, feedback):
        results = {}
        outputs = {}
        
        poly_source = self.parameterAsVectorLayer(parameters, self.INPUT, context)
        extent_source = self.parameterAsExtent(parameters, self.EXTENT, context)
        
        if poly_source is not None:
            alg_params = {'INPUT': poly_source,
                        'POINTS_NUMBER': 25,
                        'MIN_DISTANCE': 0,
                        'MIN_DISTANCE_GLOBAL': 0,
                        'MAX_TRIES_PER_POINT': 10,
                        'SEED': None,
                        'INCLUDE_POLYGON_ATTRIBUTES': True,
                        'OUTPUT': parameters[self.OUTPUT]}
            
            outputs['RandomPointsInPolygons'] = processing.run("native:randompointsinpolygons", alg_params, context=context, feedback=feedback, is_child_algorithm=True)
            
            results['RandomPoints'] = outputs['RandomPointsInPolygons']['OUTPUT']
            return results
                        
        if extent_source is not None:
            # Random points in extent
            alg_params = {
                'EXTENT': extent_source,
                'MAX_ATTEMPTS': 200,
                'MIN_DISTANCE': 0,
                'POINTS_NUMBER': 25,
                'TARGET_CRS': context.project().crs(),
                'OUTPUT': parameters[self.OUTPUT]
            }
            outputs['RandomPointsInExtent'] = processing.run('native:randompointsinextent', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        
            results['RandomPoints'] = outputs['RandomPointsInExtent']['OUTPUT']
            return results
            
        return {}

    def name(self):
        return 'Random_points_from_input'

    def displayName(self):
        return 'Random_points_from_input'

    def group(self):
        return 'Examples'

    def groupId(self):
        return 'Examples'
        
    def checkParameterValues(self, parameters, context):
        if parameters[self.INPUT] is None and parameters[self.EXTENT] is None:
            return (False, 'Missing parameter value for input Layer or Extent')
        return (True, '')
        
    def createInstance(self):
        return Random_points_from_input()
Related Question