Offset curves (also known as parallel curves) are an oft-requested feature in JTS. They are a natural extension to the concept of buffering, and are useful for things like placing labels along rivers. As far as I know there is no hard-and-fast definition for how an offset curve should be constructed, but a reasonable semantic would seem to be "a line lying on one side of another line at a given offset distance".
Here's an image of offset curves on both sides of a river reach from a US rivers dataset:
GEOS acquired an implementation to produce offset curves a few years ago. However, it has some known bugs (such as producing disconnected curves, and including extraneous segments). It also has the feature (or quirk?) of potentially producing a result with multiple disjoint curves for a single input line.
The GEOS implementation is based on the concept that an offset curve is just a portion of the corresponding buffer boundary. So to generate an offset curve the algorithm extracts a portion of the buffer polygon boundary. The trick is deciding which portion!
The GEOS implementation generates a raw offset curve (potentially full of self-intersections and unwanted linework) and then determines the intersection of that curve with the buffer boundary. However, the use of intersection on linework is always tricky. The buffer geometry is necessarily only a (close) approximation, and the buffer algorithm takes advantage of this to use various heuristics to improve the quality and robustness of the generated buffer linework. This can cause the buffer linework to diverge from the raw offset curve. The divergence makes the intersection result susceptible to errors caused by slight differences between the generated curves. The two issues above are caused by this limitation.
Instead of using intersection, an effective technique is to match geometry linework using a distance tolerance. This is the approach taken in the new JTS Offset Curve algorithm. The high-level design is
- The buffer is generated at the offset distance
- The raw offset curve is generated at the same distance
- The raw curve segments are matched to the buffer boundary using a distance tolerance
- The offset curve is the section of the buffer boundary between the first and last matching points.
- The offset curve of a line is a single LineString
- The offset curve lies on one side of the input line (to the left if the offset distance is positive, and to the right if negative)
- The offset curve has the same direction as the input line
- The distance between the input line and the offset curve equals the offset distance (up to the limits of curve quantization and numerical precision)