2

I have edited the QGISPolygonSplitting.py script of Pedro Venancio (https://github.com/PedroVenancio/qgis_polygon_splitting) to use values of a selected field as input parameters rather than introducing a constant numeric value. However, I keep getting "Incorrect parameter value" warning.

Any thoughts on what the problem might be?

The original and edited scripts can be found below:

Original Script:

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterFeatureSource
from qgis.core import QgsProcessingParameterNumber
from qgis.core import QgsProcessingParameterFeatureSink
import processing


class PolygonSplitting(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFeatureSource('poligonfeatures', 'Polygon to Split', types=[QgsProcessing.TypeVectorPolygon], defaultValue=None))
        self.addParameter(QgsProcessingParameterNumber('numberofpoints', 'Number of Random Points', type=QgsProcessingParameterNumber.Integer, minValue=0, maxValue=100000, defaultValue=10000))
        self.addParameter(QgsProcessingParameterNumber('numberofparts', 'Number of Parts to Split Polygon', type=QgsProcessingParameterNumber.Integer, minValue=0, maxValue=100, defaultValue=5))
        self.addParameter(QgsProcessingParameterFeatureSink('splittedpolygon', 'Splitted Polygon', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, model_feedback):
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
        feedback = QgsProcessingMultiStepFeedback(6, model_feedback)
        results = {}
        outputs = {}

        # Random points inside polygons
        alg_params = {
            'INPUT': parameters['poligonfeatures'],
            'MIN_DISTANCE': None,
            'STRATEGY': 0,
            'VALUE': parameters['numberofpoints'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['RandomPointsInsidePolygons'] = processing.run('qgis:randompointsinsidepolygons', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # K-means clustering
        alg_params = {
            'CLUSTERS': parameters['numberofparts'],
            'FIELD_NAME': 'CLUSTER_ID',
            'INPUT': outputs['RandomPointsInsidePolygons']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['KmeansClustering'] = processing.run('native:kmeansclustering', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # Concave hull (k-nearest neighbor)
        alg_params = {
            'FIELD': 'CLUSTER_ID',
            'INPUT': outputs['KmeansClustering']['OUTPUT'],
            'KNEIGHBORS': 3,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ConcaveHullKnearestNeighbor'] = processing.run('qgis:knearestconcavehull', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}

        # Centroids
        alg_params = {
            'ALL_PARTS': False,
            'INPUT': outputs['ConcaveHullKnearestNeighbor']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['Centroids'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}

        # Voronoi polygons
        alg_params = {
            'BUFFER': 100,
            'INPUT': outputs['Centroids']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['VoronoiPolygons'] = processing.run('qgis:voronoipolygons', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(5)
        if feedback.isCanceled():
            return {}

        # Intersection
        alg_params = {
            'INPUT': parameters['poligonfeatures'],
            'INPUT_FIELDS': [''],
            'OVERLAY': outputs['VoronoiPolygons']['OUTPUT'],
            'OVERLAY_FIELDS': [''],
            'OVERLAY_FIELDS_PREFIX': '',
            'OUTPUT': parameters['splittedpolygon']
        }
        outputs['Intersection'] = processing.run('native:intersection', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['splittedpolygon'] = outputs['Intersection']['OUTPUT']
        return results

    def name(self):
        return 'Polygon Splitting'

    def displayName(self):
        return 'Polygon Splitting'

    def group(self):
        return 'Polygon Splitting'

    def groupId(self):
        return 'Polygon Splitting'

    def createInstance(self):
        return PolygonSplitting()

Edited Script:

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterFeatureSource
from qgis.core import QgsProcessingParameterNumber
from qgis.core import QgsProcessingParameterFeatureSink
from qgis.core import QgsProcessingParameterField
import processing


class PolygonSplittingEdited(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFeatureSource('poligonfeatures', 'Polygon to Split', types=[QgsProcessing.TypeVectorPolygon], defaultValue=None))
        self.addParameter(QgsProcessingParameterNumber('numberofpoints', 'Number of Random Points', type=QgsProcessingParameterNumber.Integer, minValue=0, maxValue=10000, defaultValue=300))
        self.addParameter(QgsProcessingParameterField('numberofparts', 'Number of Parts to Split Polygon', type=QgsProcessingParameterField.Numeric, parentLayerParameterName='poligonfeatures', allowMultiple=False, defaultValue=None))
        self.addParameter(QgsProcessingParameterFeatureSink('splittedpolygon', 'Splitted Polygon', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, model_feedback):
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
        feedback = QgsProcessingMultiStepFeedback(6, model_feedback)
        results = {}
        outputs = {}

        # Random points inside polygons
        alg_params = {
            'INPUT': parameters['poligonfeatures'],
            'MIN_DISTANCE': None,
            'STRATEGY': 0,
            'VALUE': parameters['numberofpoints'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['RandomPointsInsidePolygons'] = processing.run('qgis:randompointsinsidepolygons', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # K-means clustering
        alg_params = {
            'CLUSTERS': parameters['numberofparts'],
            'FIELD_NAME': 'CLUSTER_ID',
            'INPUT': outputs['RandomPointsInsidePolygons']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['KmeansClustering'] = processing.run('native:kmeansclustering', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # Concave hull (k-nearest neighbor)
        alg_params = {
            'FIELD': 'CLUSTER_ID',
            'INPUT': outputs['KmeansClustering']['OUTPUT'],
            'KNEIGHBORS': 3,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ConcaveHullKnearestNeighbor'] = processing.run('qgis:knearestconcavehull', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}

        # Centroids
        alg_params = {
            'ALL_PARTS': False,
            'INPUT': outputs['ConcaveHullKnearestNeighbor']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['Centroids'] = processing.run('native:centroids', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}

        # Voronoi polygons
        alg_params = {
            'BUFFER': 100,
            'INPUT': outputs['Centroids']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['VoronoiPolygons'] = processing.run('qgis:voronoipolygons', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(5)
        if feedback.isCanceled():
            return {}

        # Intersection
        alg_params = {
            'INPUT': parameters['poligonfeatures'],
            'INPUT_FIELDS': [''],
            'OVERLAY': outputs['VoronoiPolygons']['OUTPUT'],
            'OVERLAY_FIELDS': [''],
            'OVERLAY_FIELDS_PREFIX': '',
            'OUTPUT': parameters['splittedpolygon']
        }
        outputs['Intersection'] = processing.run('native:intersection', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['splittedpolygon'] = outputs['Intersection']['OUTPUT']
        return results

    def name(self):
        return 'Polygon Splitting Edited'

    def displayName(self):
        return 'Polygon Splitting Edited'

    def group(self):
        return 'Polygon Splitting Edited'

    def groupId(self):
        return 'Polygon Splitting Edited'

    def createInstance(self):
        return PolygonSplittingEdited()

Simple test data can be reached below:
https://we.tl/t-jo2lXpSBKQ

4
  • The issue is that the script is using QGIS processing algorithms. One of them is K-Means-Clustering which expects a number as input, not a field. This makes sense as this algorithms builds clusters from a layers features. This value needs to be fixed by means of it logic. So I am afraid there is no answer to your question, except "not possible". Maybe describe what you want to do and what the final script is meant to do. There should be a solution to that. For me, the original script does not work as well (hangs on 36% with your example data and crashes QGIS 10 minutes later)
    – MrXsquared
    May 21 at 17:47
  • Thanks for the reply. The original script probably crashes because of the high default value (10.000) of "Number of Random Points". When it is set to 100 etc, it should work. However, the main problem is that the original script assumes all features in the input data (4 polygons in the attached test file) as a single geometry and divides using the introduced num. My aim is to subdivide each polygon in the input data with respect to values located in the "Divisions" field, like dividing the first one into 10 new polys, second one into 8 new, third one into 6, and fourth one into 4.
    – Melanie
    May 21 at 18:36
  • You could remove parts of the script and see if that is already what you are looking for. Basically build N number ("divisions") of points inside the polygon (random points), create voronoi polygons (with buffer) and finally the intersection of voronoi and original polygon. i.stack.imgur.com/7RrKp.png
    – MrXsquared
    May 21 at 19:04
  • This is indeed what I need (i.stack.imgur.com/7RrKp.png), thank you very much! Which parts should be removed?
    – Melanie
    May 21 at 19:34

1 Answer 1

2

Based on your comments you can use this modified script:

from qgis.core import QgsProcessing, QgsProcessingAlgorithm, QgsProcessingMultiStepFeedback, QgsProcessingParameterFeatureSource, QgsProcessingParameterNumber, QgsProcessingParameterFeatureSink, QgsProcessingParameterField, QgsProperty
import processing


class PolygonSplittingEdited(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFeatureSource('poligonfeatures', 'Polygon to Split', types=[QgsProcessing.TypeVectorPolygon], defaultValue='Polygons'))
        self.addParameter(QgsProcessingParameterField('numberofparts', 'Number of Parts to Split Polygon', type=QgsProcessingParameterField.Numeric, parentLayerParameterName='poligonfeatures', allowMultiple=False, defaultValue='Divisions'))
        self.addParameter(QgsProcessingParameterFeatureSink('splittedpolygon', 'Splitted Polygon', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, model_feedback):
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
        feedback = QgsProcessingMultiStepFeedback(3, model_feedback)
        results = {}
        outputs = {}

        # Random points inside polygons
        alg_params = {
            'INPUT': parameters['poligonfeatures'],
            'MIN_DISTANCE': None,
            'STRATEGY': 0,
            'VALUE': QgsProperty.fromExpression(parameters['numberofparts']),
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['RandomPointsInsidePolygons'] = processing.run('qgis:randompointsinsidepolygons', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Voronoi polygons
        alg_params = {
            'BUFFER': 100,
            'INPUT': outputs['RandomPointsInsidePolygons']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['VoronoiPolygons'] = processing.run('qgis:voronoipolygons', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        
        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # Intersection
        alg_params = {
            'INPUT': parameters['poligonfeatures'],
            'INPUT_FIELDS': [''],
            'OVERLAY': outputs['VoronoiPolygons']['OUTPUT'],
            'OVERLAY_FIELDS': [''],
            'OVERLAY_FIELDS_PREFIX': '',
            'OUTPUT': parameters['splittedpolygon']
        }
        outputs['Intersection'] = processing.run('native:intersection', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['splittedpolygon'] = outputs['Intersection']['OUTPUT']
        return results

    def name(self):
        return 'Polygon Splitting Edited'

    def displayName(self):
        return 'Polygon Splitting Edited'

    def group(self):
        return 'Polygon Splitting Edited'

    def groupId(self):
        return 'Polygon Splitting Edited'

    def createInstance(self):
        return PolygonSplittingEdited()

Changes made:

  • Import of QgsProperty added
  • Removed number input
  • Changed number of MultiStepFeedback (6 to 3)
  • Changed Value-Input of random points inside polygons algorithm to QgsProperty.fromExpression(parameters['numberofparts']). This will take the field (or better the values this field contains) chosen in input parameters as the number of random points for each feature.
  • Removed K-Means-Clustering
  • Removed Concave hull (k-nearest neighbor)
  • Removed Centroids
  • Changed input of voronoi polygons to outputs['RandomPointsInsidePolygons']['OUTPUT']

For means of completeness the answer to the original question:

The issue is that the script is using QGIS processing algorithms. One of them is K-Means-Clustering which expects a number as input, not a field. This makes sense as this algorithms builds clusters from a layers features. This value needs to be fixed by means of it logic. So I am afraid there is no answer to your question, except "not possible".

1
  • Thank you very much for your rapid response and solution! Your approach also works faster since it directly makes the division using the field values. The only drawback is losing the ability to get equalish subdivisions. Still, I'm very grateful. Cheers...
    – Melanie
    May 21 at 20:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.