Expanding and shrinking polygons with ST_Dilate

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.

The Solution

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