I am not sure if this is the most elegant approach, but it worked for me and maybe it will help somebody get started in the right direction. If you have 1.5+ of PostgreSQL then use ST_Buffer with different endcaps - I am sure it performs this in a much more efficient manner).
Because I have the two rectangles and all their coordinates, the biggest problem was insuring that I am joining the exposed open parts and not touching the other side (which side is the kink on?)
How do I know which points to join?
I've built a "Rectangle" class that has the points TL, TR, BL, BR. They are always oriented along the line.
That way I always know that a TL will link to a BL and a TR will link to a BR.
How do I know whether to join BL to TL or BR to TR?
Based on the difference in angle between the two rectangles I can determine what side has the kink.
In my Rectangle class I am also keeping track of the angle and normalizing it to 0->360 degrees. I don't think this was the best approach but it made it easier for me to understand the problem in my head. I had to do some basic math when the difference in angle was greater then 180 degrees as it causes the kink to flip to the other side.
How do I build the curve?
Once I have identified which points I wish to join (j1 from rectangle 1, j2 from rectangle 2), I need to find a third point somewhere out in the middle (see example above (pa1)). This is used for quadratic bezier curves which I use to build the arc between the points. This third point is used to "pull" the curve away from the rectangles.
The python function I used to build the curves is:
def get_bezier_curve(j1, j2, pa1, segments=8):
Given 2 points (j1, and j2) calculate the bezier curve
between them - using point pa1 to pull the curve away
from the points.
Segments determines the number of points calculated for
# j1 is always first item
coords = [j1]
step = 1.0 / segments
# Quadratic bezier curve
for t in drange(step, 1.0-step, step):
x = ((1-t)*(1-t)*j1 + 2*(1-t)*t*pa1+t*t*j2)
y = ((1-t)*(1-t)*j1 + 2*(1-t)*t*pa1+t*t*j2)
# j2 is always last item
To find this, I find the intersection between the inner lines (lines on the side of a rectangle that doesnt have a kink) (i1). Then I create a vector from this towards the original point (p1) that both rectangles share (the original point before buffering). I then increase the length of this to find my point to pull the curve (pa1).
I then build the curve using the above algorithm between j1, and j2 using pa1 pull out the curve.
I then create a polygon out of i1 -> j1 ... bezier curve points ... j2 -> i1.
I then take all the kink polygons, and the rectangle polygons I have and perform a cascaded union (using GEOS from GeoDjango - although you could use Shaply) to collapse these polygons into a single polygon.
Note: To reasons I do not quite fully understand when taking a MultiPolygon of all this data and performing a cascaded union I am not always given a Polygon. Sometimes I am given a GeometryCollection of a useless LineString (no idea where this is coming from) and the combined Polygon. I am guessing this is some side-effect to collapsing certain geometries and stuff not fitting correctly - maybe rounding errors - not sure. If anybody knows why please let me know.