I have modified the QGIS version of the Buffer by percentage script in https://github.com/jdugge/BufferByPercentage/blob/master/bufferbypercentage.py and all credits goes to Juernjakob Dugge.
The process is quite simple.
- Create a new float field, say
scale_factor
and input the scale factor (this could be the same for all or the ratio to reach a certain/target area), say your polygon is 728363 sqm and you want to shrink it to 125967 sqm, in this case this factor is 17.294%. The input factor should be given as fraction, say 17.294% should be 0.17294.
- Create another double field, say
buffer_length
and use the field calculator in Python parser to input the expression and code below, where applicable:
Pre-Logic Script Code
def find_buffer_length(geometry, target_factor):
"""Find the buffer length that scales a geometry by a certain factor."""
area_unscaled = geometry.area
buffer_initial = 0.1 * (geometry.extent.width +
geometry.extent.height)
buffer_length = secant(calculateError, buffer_initial,
2 * buffer_initial, geometry,
area_unscaled, target_factor)
return buffer_length
def calculateError(buffer_length, geometry, area_unscaled,
target_factor):
"""Calculate the difference between the current and the target factor."""
geometry_scaled = geometry.buffer(buffer_length)
area_scaled = geometry_scaled.area
return area_scaled / area_unscaled - target_factor
# Secant method for iteratively finding the root of a function
# Taken from
# http://www.physics.rutgers.edu/~masud/computing/WPark_recipes_in_python.html
def secant(func, oldx, x, *args, **kwargs):
"""Find the root of a function"""
tolerance = kwargs.pop('tolerance', 1e-6)
max_steps = kwargs.pop('max_steps', 100)
steps = 0
oldf, f = func(oldx, *args), func(x, *args)
if (abs(f) > abs(oldf)): # Determine the initial search direction
oldx, x = x, oldx
oldf, f = f, oldf
while (f - oldf) != 0 and steps < max_steps:
dx = f * (x - oldx) / float(f - oldf)
if abs(dx) < tolerance * (1 + abs(x)): # Converged
return x - dx
oldx, x = x, x - dx
oldf, f = f, func(x, *args)
while f <= 0:
# Buffer length resulted in flipped polygon, reduce step size
x = oldx # Undo current step
f = oldf
dx *= 0.5 # Halve the step size
oldx, x = x, x - dx
oldf, f = f, func(x, *args)
steps += 1
# Did not converge
return x - dx
Field to be calculated (buffer_length =
)
find_buffer_length( !SHAPE!, !scaling_factor! )
- Buffer the features based on the lastly calculated field.