I had a go at this in ArcMap since i dont know how to do it in QGIS. Maybe someone else can translate the ideas into QGIS. Im using ArcGIS 10.4 with Advanced License (Avanced is needed for Erase Tool which is used in script).
Script below will divide all polygons in an input feature class in five almost equal area parts. Almost equal area because the script does not check that all parts are exactly the same area, only that the area of each part is within a specified margin of error.
If the polygon being processed is round(ish) it will be divided like a cake:
If it's not round, it's elongated and will be divided like this:
But not like this (divisions not parallel):
Example:
Code:
import arcpy,math
arcpy.env.workspace = r'C:\TEST.gdb'
arcpy.env.overwriteOutput = 1
infc = r'polygons'
outfc = r'divided_polygons'
totalpolycount = arcpy.GetCount_management(infc)
polygoncount = 0
partcount = 1
with arcpy.da.SearchCursor(infc,['SHAPE@AREA','SHAPE@LENGTH','SHAPE@XY','SHAPE@']) as scursor1:
for srow1 in scursor1:
polygoncount+=1
print "Processing polygon {} of {}".format(polygoncount,totalpolycount)
#Find out if polygon is round (compactness >=0.045) or elongated
compactness = srow1[0]/(srow1[1]**2)
if compactness >= 0.045:
print "It's round, dividing like a cake"
dist=srow1[1] #/2
radian=0.0
trailingcoord = [srow1[2][0],srow1[2][1]+dist]
for j in range(1,6):
unloopcounter=0
areadiff=99999.0
#Keep increasing angle of cakepiece until difference in area between piece and 1/5 of polygon is below 1/2000 of polygon area
while abs(areadiff)>(srow1[0]/2000) and unloopcounter<5000:
radian+=0.002
feature_info = [list(srow1[2]), trailingcoord]
if j<5:
feature_info.append([srow1[2][0]+dist*math.sin(radian),srow1[2][1]+dist*math.cos(radian)])
else:
feature_info.append([srow1[2][0],srow1[2][1]+dist])
unloopcounter=9999
polygontemp=arcpy.Polygon(arcpy.Array([arcpy.Point(*coords) for coords in feature_info]))
arcpy.Clip_analysis(srow1[3],polygontemp, r'in_memory\polygonpart')
with arcpy.da.SearchCursor(r'in_memory\polygonpart',['SHAPE@AREA']) as scursor2:
for srow2 in scursor2:
areadiff=srow1[0]/5-srow2[0]
unloopcounter+=1
print 'Part {} took {} attempts'.format(j, unloopcounter)
trailingcoord=[srow1[2][0]+dist*math.sin(radian),srow1[2][1]+dist*math.cos(radian)]
arcpy.CopyFeatures_management(r'in_memory\polygonpart','Polygon_'+str(polygoncount)+'_Part_'+str(j))
else:
print "It's elongated, dividing like a bread loaf"
arcpy.MinimumBoundingGeometry_management(srow1[3],r'in_memory\bounding','RECTANGLE_BY_WIDTH')
arcpy.SplitLine_management(r'in_memory\bounding', r'in_memory\splitline')
linelist=[]
with arcpy.da.SearchCursor(r'in_memory\splitline',['SHAPE@LENGTH','SHAPE@']) as scursor3:
for srow3 in scursor3:
linelist.append(srow3)
linelist=sorted(linelist,key=lambda x: x[0])
deltax = linelist[3][1].firstPoint.X-linelist[3][1].lastPoint.X
deltay = linelist[3][1].firstPoint.Y-linelist[3][1].lastPoint.Y
with arcpy.da.UpdateCursor(r'in_memory\bounding',['SHAPE@XY']) as ucursor1:
for urow1 in ucursor1:
shiftx=0.0
shifty=0.0
for i in range(1,6):
areadiff = 99999
unloopcounter = 0
arcpy.Clip_analysis(srow1[3], r'in_memory\bounding', r'in_memory\boundingclip')
#keep moving bounding polygon until the differnce between piece left after erase and 1/5 of polygon is below 1/2000 of polygon area
while abs(areadiff) > (srow1[0]/2000) and unloopcounter < 5000:
if i==5:
shiftx+=deltax
shifty+=deltay
unloopcounter=9999
ucursor1.updateRow([[urow1[0][0]+shiftx,urow1[0][1]+shifty]])
shiftx+=deltax/5000
shifty+=deltay/5000
arcpy.Erase_analysis(r'in_memory\boundingclip', r'in_memory\bounding', r'in_memory\polygonpart')
with arcpy.da.SearchCursor(r'in_memory\polygonpart',['SHAPE@AREA']) as scursor4:
for srow4 in scursor4:
areadiff = (srow1[0]/5)-srow4[0]
unloopcounter+=1
print "Part {} took {} attempts".format(i,unloopcounter)
arcpy.CopyFeatures_management(r'in_memory\polygonpart', out_feature_class='Polygon_'+str(polygoncount)+'_Part_'+str(i))
print "Merging all the parts together, deleting temp data and repairing small gaps"
partlist = arcpy.ListFeatureClasses(wild_card='Polygon_*')
arcpy.Merge_management(partlist, outfc)
for part in partlist:
arcpy.Delete_management(part)
arcpy.Integrate_management(outfc, 0.001) #The dividing sometime creates small gaps between parts. Gaps smaller than 0.001 m are repaired (change the 0.001 if needed).
print "Finished"