Creating a buffer of a feature is quite simple – it is often one of the very first things that a student learns in an “Intro to GIS” class. You input your features, how far outward (or inward) to buffer, and bam, you’re done.
But what if you want to buffer a polygon feature in such a way that you want the resultant polygon to be a percentage bigger or smaller than the source polygon? What distance would you enter for that?
The truth is that for all non-trivial geometric shapes (circles, ellipses, rectangles), it is infeasible to try and figure it out mathematically. We need to employ an iterative solution – one that tries different buffer distances, figuring out how close it is to the correct answer, and trying again and again, until it finds a value that produces a result similar enough to the desired one to be considered correct.
I have written a PostGIS function called ST_Dilate that does exactly that – it takes the following parameters:
in_geom– The geometry field for the polygon to be buffered.
scale_factor– How much bigger or smaller the final buffered polygon should be than
in_geom. A value of 1.5 means that it should be 50% bigger.
tol– How similar the final buffered polygon’s area must be to the desired area, which itself it calculated by multiplying
in_geom‘s area by the scale factor. Default is 0.o01, or 0.1%. A smaller value means that the final buffered polygon will be much more precise, but at a cost of usually requiring more iterations.
guess– The first value to try when buffering the source polygon. Default is 1.
safety– A maximum number of attempts before the function gives up and outputs a NULL geometry. As the solution is iterative, it is good to have this kind of mechanism to stop infinite loops in case of unintended behaviour. Default is 1000
How it works
The function first calculates the desired final area by multiplying
in_geom‘s area by
scale_factor. It then buffers
in_geom using a distance value of
guess. After comparing the area of the resultant buffered polygon with the desired final area, it increments (if the resultant buffered polygon’s area was too small) or decrements (if it was too big)
guess by half of itself and tries again. If the resultant buffer polygon’s area is bigger than the desired final area on the last run, and is small on the current run, the amount by which
guess is incremented or decremented is halved. Once the resultant buffered polygon’s area is sufficiently similar to the desired final area (if the normalized difference is less than
tol), then this resultant buffered polygon is returned.
safety is a parameter that indicates the maximum number of attempts to find the correct final buffered polygon before giving up and returning a NULL geometry. This is intended to be a failsafe measure to avoid creating an infinite loop and causing damage to the greater system in the event of unintended behaviour.
To get a better feel for how the iteration works in this solution, we can look at the below graph, which shows the discrepancy between the area of the resultant buffered polygon and the desired final area. The blue line is the first attempt at dilating a polygon, using a
guess value of 25. The orange line is the same polygon, but with a
guess value of 100.
We can clearly see that changing this initial guess can have significant effects on the amount of iterations needed to arrive at the correctly buffered polygon. This is important to keep in mind when dilating your polygons. Changing the tolerance would have an effect, too – but at the cost of precision.
As usual, the source code, as well as installation instructions, is available on github