19

There is a Plugin in QGIS 2.18 called "Polygon Divider" which divides an irregular polygon into smaller equal-area polygons of a specified/custom-sized but in QGIS 3.x version there is no "Polygon Divider" Plugin or tool to perform that task.

How do I divide/split the irregular polygon into equal area/parts polygons in QGIS 3.6?

As shown in the below picture:

example

4

7 Answers 7

29

All of the necessary tools can be found from processing toolbox and you have to be a bit innovative to achieve this. Basically by adopting this method you could achieve pretty good results, although it doesn't give you 90 degree angles in all places, but roughly equal area:

1.Create enough random points inside a polygon. I did 10 000 10 000 random points inside a polygon

  1. Run k-means clustering for the random points. Use as many clusters as many areas you want:

Clustered points colored based on cluster_id

  1. Run Vector Geometry --> Aggregate and use the cluster id as the aggregation field. This way you will get a multipoint layer from the clusters.

  2. Run Vector geometry --> centroids for the aggregated points Centroids for each new area

  3. Run Voronoi polygons for the centroids. Use enough buffer to cover the whole original polygon. For me this was around 40% Split layer

  4. Create lines from the voronoi polygons (Vector geometry --> polygons to lines). Currently I think you can only split a polygon layer with lines and not with another polygon layer (not 100% sure about this, but this works for sure). Line layer used for splitting

  5. Finally run Split with Lines tool and use your original polygon layer as input and voronoi lines as the split layer:

Final result!

6
  • Great solution. Though I was able to reach to the step of generating points. But when I use k-means clustering tool under Vector Analysis and set my desired clusters, I get same colored points (only one cluster) in an output layer
    – Waleed93
    Oct 6, 2021 at 12:43
  • Did you set the number of clusters correctly? Don't mind about the colors. Look at the attributes of the newly generated layer.
    – tjukanov
    Oct 8, 2021 at 9:34
  • Yes, I checked. There's only one. In fact, I even did aggregation and finding centroid but there comes only one centroid (point) at the center. Strange. Btw, I'm generating 50000 random points with min distance apart of 0 meters, and creating 12 clusters
    – Waleed93
    Oct 8, 2021 at 17:08
  • This method worked reasonably well on all the polygons I ran it against. It works on concave polygons which the QGIS plugin Polygon Divider crashes on. Thanks for sharing!
    – Rob Irwin
    Dec 8, 2021 at 16:34
  • Hey. this method does not work to divide an area into two or? The Part 5 The Voronoi - Polygone requires at leadt tree points and I just have two in my case gis.stackexchange.com/questions/428067/…
    – miraculix
    Apr 10, 2022 at 17:20
6

In this attempt, I tried to accomplish @Christophe's idea suggested in comments. Please don't be harsh, it is definitely askew and fragile nevertheless it is somehow working. The script does not include the debugging part.

Proceed with Plugins > Python Console > Show Editor and paste the script below

# defining required imports
import sys

# defining a class
class LayerIntoParts:

    # initiating layer's name to split and number of parts provided from user
    def __init__(self, layer_name, segments):
        self.layer_name = layer_name
        self.segments = segments

    # setting up the input layer and checking it's type
    def layer_geometry_check(self):
        # loading layer
        layer = QgsProject.instance().mapLayersByName(self.layer_name)[0]
        if not layer.isValid():
            raise ValueError("Layer failed to load!")
            sys.exit

        # checking geometry type, will work only with polygons and multipolygons
        if layer.wkbType() == QgsWkbTypes.Polygon:
            layer_valid = layer
        # converting multipart into single part
        elif layer.wkbType() == QgsWkbTypes.MultiPolygon:
            layer_valid = processing.run('native:multiparttosingleparts',
                {'INPUT': layer,
                'OUTPUT': "memory:"}
            )["OUTPUT"]
        # giving an error for not polygons and multipolygons
        else:
            raise ValueError("This geometry type is not supported!\Only Polygons/Multipolygons are allowed.")
            sys.exit

        # returning the validated layer
        return layer_valid

    # defining required parameters
    def layer_and_parameters(self):
        # inheriting the output from 'layer_geometry_check' method
        layer = self.layer_geometry_check()

        # getting the layer's area
        area_total = [f.geometry().area() for f in layer.getFeatures()]
        area_total = area_total[0]

        # getting the layer's extent and adjusting it for processing functions
        layer_extent = layer.extent()
        layer_extent_string = "{},{},{},{}".format(layer_extent.xMinimum(), layer_extent.xMaximum(), layer_extent.yMinimum(), layer_extent.yMaximum())

        # building a dictionary to store the output parameters
        param = {
        'layer': layer,
        'layer_crs': layer.crs(),
        'layer_extent': layer_extent_string,
        'layer_area_total': area_total,
        'area_segment': area_total / self.segments
        }

        # returning the dictionary with output parameters
        return param

    # creating grid for the input layer
    def layer_2_grid(self):
        # inheriting the output from 'layer_and_parameters' method
        param = self.layer_and_parameters()

        # creating grid for the layer with 'qgis:creategrid' algorithm
        layer_grid = processing.run('qgis:creategrid',
            {'TYPE': 2,
            'CRS': param['layer_crs'],
            'EXTENT': param['layer_extent'],
            'HSPACING': 10000,
            'VSPACING': 10000,
            'HOVERLAY': 0,
            'VOVERLAY': 0,
            'OUTPUT': "memory:"}
        )["OUTPUT"]

        # deleting redundant attributes (appear by default when using the algorithm to create a grid)
        layer_grid_clean = processing.run('qgis:deletecolumn',
            {'INPUT': layer_grid,
            'COLUMN': ['left', 'right', 'top', 'bottom'],
            'OUTPUT': "memory:"}
        )["OUTPUT"]

        # returning layer as a grid
        return layer_grid_clean

    # clipping the grid with the input layer
    def clip_grid_and_layer(self):
        # inheriting the output from 'layer_2_grid' method
        grid = self.layer_2_grid()
        # inheriting the output from 'layer_and_parameters' method
        layer = self.layer_and_parameters()['layer']

        # clipping the grid with the layer by means of 'qgis:clip' algorithm
        layer_clip = processing.run('qgis:clip',
            {'INPUT': grid,
            'OVERLAY': layer,
            'OUTPUT': "memory:"}
        )["OUTPUT"]

        # returning the clipped grid
        return layer_clip

    # categorizing the clipped grid layer
    def categorize(self):
        # inheriting the output from 'clip_grid_and_layer' method
        layer_clip = self.clip_grid_and_layer()
        # inheriting the output from 'layer_and_parameters' method
        param = self.layer_and_parameters()

        # transforming clipped grid layer's elements into a list with
        # id, calculated area, and geometry      
        elements = []
        for f in layer_clip.getFeatures():
            case = {"f_id": f.id(), "area": f.geometry().area(), "geom": f.geometry()}
            elements.append(case)

        # lists for used cells' ids
        used_ids_i = []
        used_ids_j = []
        # variable for categories
        n = 0

        # looping through all cells
        for i in elements:
            area_calc = i['area']
            used_ids_i.append(i['f_id'])

            for j in elements:

                # conditions: calculated area less than a "golden" area of a segment, no duplicates of cells, and sufficient intersection of edges between cells
                if area_calc <= param['area_segment'] and j['f_id'] not in used_ids_i and i['f_id'] not in used_ids_j and (i['geom'].intersection(i['geom'])).length() > 0:
                    used_ids_j.append(j['f_id'])
                    area_calc += j['area']
                    n += 1

                # signing a category to each cell
                i['cat'] = 'cat_' + str(n)
                i['area_calc'] = area_calc

        # returning categorized cells as list
        return elements

   # dissolving categorized cells and outputting the result
    def dissolve_result(self):
        # inheriting the output from 'layer_and_parameters' method
        param = self.layer_and_parameters()
        # inheriting the output from 'categorize' method
        elements_raw = self.categorize()

        # creating an empty later for output
        vl = QgsVectorLayer("Polygon?crs={}".format(param['layer_crs'].authid()), "output", "memory")

        # accessing the empty layer
        pr = vl.dataProvider()

        # embedding new field values
        pr.addAttributes([QgsField("id", QVariant.Int),
            QgsField("cat", QVariant.String),
            QgsField("area_calc", QVariant.Double),
            QgsField("area_gold", QVariant.Double)
            ])

        # updating the empty layer      
        vl.updateFields()

        # looping through a list of categorized cells and inserting them into the empty layer
        for elem in elements_raw:
            feat = QgsFeature()
            feat.setGeometry(elem['geom'])
            feat.setAttributes([elem['f_id'], elem['cat'], round(elem['area_calc'], 8), round(param['area_segment'], 8)])
            pr.addFeature(feat)

        vl.updateExtents()

        # dissolving by category by means of 'qgis:dissolve' algorithm
        layer_dissolved = processing.run('qgis:dissolve',
            {'FIELD': ['cat'],
            'INPUT': vl,
            'OUTPUT': 'memory:'}
        )["OUTPUT"]

        # deleting 'cat' attribute using 'qgis:deletecolumn' algorithm
        layer_dissolved_clean = processing.run('qgis:deletecolumn',
            {'INPUT': layer_dissolved,
            'COLUMN': ['cat'],
            'OUTPUT': "memory:"}
        )["OUTPUT"]

        # updating 'id' attribute
        with edit(layer_dissolved_clean):
            for feature in layer_dissolved_clean.getFeatures():
                feature.setAttribute(feature.fieldNameIndex('id'), feature.id())
                layer_dissolved_clean.updateFeature(feature)

        # returning function output
        return QgsProject.instance().addMapLayer(layer_dissolved_clean)

my_class = LayerIntoParts('test_area_4', 2)
my_class.dissolve_result()

Adjust the my_class = LayerIntoParts('name of your layer', num_of_parts)

Press Run script run script and get the output


Testing. I am using a polygon of Corsica that was taken from Natural Earth | Admin 0 – Countries

test_input

Splitting into 2 parts with

my_class = LayerIntoParts('test_area_4', 2)
my_class.dissolve_result()

test_split_2

Splitting into 3 parts with

my_class = LayerIntoParts('test_area_4', 3)
my_class.dissolve_result()

test_split_3


Notes: (TODO)

  • Parameters 'HSPACING' and 'VSPACING' have to be defined in advance
  • Does not work with Multipolygons

References:

0
5

Since 24 September 2020 the "Polygon Divider" Plugin is again supported under the hood of the QGIS 3.x versions.

example

P.S. It was also mentioned as a comment

0
2

This is an approach that could be tried:

1) So far, it is most likely impossible to accurately separate a complex polygon by horizontal and vertical lines.

2) The problem lies in the combination of straight and curved lines.

3) It is possible to separate a complex polygon accurately only with curved lines.

4) Yes, with small errors it is possible to divide the polygon by horizontal and vertical lines and they will accumulate at the second (third, etc.) level, i.e. the 1st level will allow me to precisely divide the curvilinear polygon into 2 exactly equal parts, and from the second level, when we have sides with straight and curved lines, the geoinstrument will start to accumulate errors...

"...until the road is overwhelmed" :-).

5) An example of the study is shown in the figures below

enter image description here

and

enter image description here 6) Maybe one day I'll reconsider my point of view, but that's what came to me now :-)...

Next step to think about is how to find the average value of the polygon and then bring the area of sub-plots to the average value of the polygon.

0
1

I recently "partially" updated the Split Polygon plugin to QGIS 3, created by Uroc Ilic. It is now available in the QGIS repository.

But it works only for convex polygons, that is, not very irregular.

0

I have made a QGIS model, and saved it as a script, that divides the polygon in equal parts, lengthwise. I have uploaded the script. See ticket and answer here: Dividing polygon in equal parts, number of parts as attribute in QGIS

-3

Now, there is a ‘Polygon Divider’ plugin for QGIS 3.x version, which is the solution.

1
  • 1
    Please edit your question to include a detailed description of the steps necessary to accomplish the tasks. Otherwise, it is likely that the link will be obsolete in the future and, as a result, the answer becomes obsolete.
    – Aaron
    Sep 27, 2020 at 20:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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