Skip to content

Commit

Permalink
Fix OffsetCurve to handle end seg issue (#1029)
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-jts authored Jan 11, 2024
1 parent 39fa4a7 commit 872bc44
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.util.GeometryCombiner;
import org.locationtech.jts.operation.buffer.BufferParameters;
import org.locationtech.jts.operation.buffer.OffsetCurve;
import org.locationtech.jtstest.geomfunction.Metadata;

Expand Down Expand Up @@ -75,4 +76,25 @@ public static Geometry rawCurve(Geometry geom, double distance)
return curve;
}

public static Geometry rawCurveWithParams(Geometry geom,
Double distance,
@Metadata(title="Quadrant Segs")
Integer quadrantSegments,
@Metadata(title="NOT USED")
Integer capStyle,
@Metadata(title="Join style")
Integer joinStyle,
@Metadata(title="Mitre limit")
Double mitreLimit)
{
BufferParameters bufferParams = new BufferParameters();
if (quadrantSegments >= 0) bufferParams.setQuadrantSegments(quadrantSegments);
if (joinStyle >= 0) bufferParams.setJoinStyle(joinStyle);
if (mitreLimit >= 0) bufferParams.setMitreLimit(mitreLimit);
Coordinate[] pts = OffsetCurve.rawOffset((LineString) geom, distance, bufferParams);
Geometry curve = geom.getFactory().createLineString(pts);
return curve;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.util.GeometryMapper;
Expand Down Expand Up @@ -421,20 +422,22 @@ private int matchSegments(Coordinate raw0, Coordinate raw1, int rawCurveIndex,
private static class MatchCurveSegmentAction
extends MonotoneChainSelectAction
{
private Coordinate p0;
private Coordinate p1;
private Coordinate raw0;
private Coordinate raw1;
private double rawLen;
private int rawCurveIndex;
private Coordinate[] bufferRingPts;
private double matchDistance;
private double[] rawCurveLoc;
private double minRawLocation = -1;
private int bufferRingMinIndex = -1;

public MatchCurveSegmentAction(Coordinate p0, Coordinate p1,
public MatchCurveSegmentAction(Coordinate raw0, Coordinate raw1,
int rawCurveIndex,
double matchDistance, Coordinate[] bufferRingPts, double[] rawCurveLoc) {
this.p0 = p0;
this.p1 = p1;
this.raw0 = raw0;
this.raw1 = raw1;
rawLen = raw0.distance(raw1);
this.rawCurveIndex = rawCurveIndex;
this.bufferRingPts = bufferRingPts;
this.matchDistance = matchDistance;
Expand All @@ -448,34 +451,61 @@ public int getBufferMinIndex() {
public void select(MonotoneChain mc, int segIndex)
{
/**
* A curveRingPt segment may match all or only a portion of a single raw segment.
* There may be multiple curve ring segs that match along the raw segment.
* Generally buffer segments are no longer than raw curve segments,
* since the final buffer line likely has node points added.
* So a buffer segment may match all or only a portion of a single raw segment.
* There may be multiple buffer ring segs that match along the raw segment.
*
* HOWEVER, in some cases the buffer construction may contain
* a matching buffer segment which is slightly longer than a raw curve segment.
* Specifically, at the endpoint of a closed line with nearly parallel end segments
* - the closing fillet line is very short so is heuristically removed in the buffer.
* In this case, the buffer segment must still be matched.
* This produces closed offset curves, which is technically
* an anomaly, but only happens in rare cases.
*/
double frac = segmentMatchFrac(bufferRingPts[segIndex], bufferRingPts[segIndex+1],
p0, p1, matchDistance);
raw0, raw1, matchDistance);
//-- no match
if (frac < 0) return;

//-- location is used to sort segments along raw curve
double location = rawCurveIndex + frac;
rawCurveLoc[segIndex] = location;
//-- record lowest index
//-- buffer seg index at lowest raw location is the curve start
if (minRawLocation < 0 || location < minRawLocation) {
minRawLocation = location;
bufferRingMinIndex = segIndex;
}
}
}

private static double segmentMatchFrac(Coordinate p0, Coordinate p1,
Coordinate seg0, Coordinate seg1, double matchDistance) {
if (matchDistance < Distance.pointToSegment(p0, seg0, seg1))
private double segmentMatchFrac(Coordinate buf0, Coordinate buf1,
Coordinate raw0, Coordinate raw1, double matchDistance) {
if (! isMatch(buf0, buf1, raw0, raw1, matchDistance))
return -1;
if (matchDistance < Distance.pointToSegment(p1, seg0, seg1))
return -1;
//-- matched - determine position as fraction along segment
LineSegment seg = new LineSegment(seg0, seg1);
return seg.segmentFraction(p0);

//-- matched - determine location as fraction along raw segment
LineSegment seg = new LineSegment(raw0, raw1);
return seg.segmentFraction(buf0);
}

private boolean isMatch(Coordinate buf0, Coordinate buf1, Coordinate raw0, Coordinate raw1, double matchDistance) {
double bufSegLen = buf0.distance(buf1);
if (rawLen <= bufSegLen) {
if (matchDistance < Distance.pointToSegment(raw0, buf0, buf1))
return false;
if (matchDistance < Distance.pointToSegment(raw1, buf0, buf1))
return false;
}
else {
//TODO: only match longer buf segs at raw curve end segs?
if (matchDistance < Distance.pointToSegment(buf0, raw0, raw1))
return false;
if (matchDistance < Distance.pointToSegment(buf1, raw0, raw1))
return false;
}
return true;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ public void testClosedCurve() {

public void testOverlapTriangleInside() {
checkOffsetCurve(
"LINESTRING (70 80, 10 80, 50 10, 90 80, 40 80))", 10,
"LINESTRING (70 80, 10 80, 50 10, 90 80, 40 80)", 10,
"LINESTRING (70 70, 40 70, 27.23 70, 50 30.15, 72.76 70, 70 70)"
);
}

public void testOverlapTriangleOutside() {
checkOffsetCurve(
"LINESTRING (70 80, 10 80, 50 10, 90 80, 40 80))", -10,
"LINESTRING (70 80, 10 80, 50 10, 90 80, 40 80)", -10,
"LINESTRING (70 90, 40 90, 10 90, 8.11 89.82, 6.29 89.29, 4.6 88.42, 3.11 87.25, 1.87 85.82, 0.91 84.18, 0.29 82.39, 0.01 80.51, 0.1 78.61, 0.54 76.77, 1.32 75.04, 41.32 5.04, 42.42 3.48, 43.8 2.16, 45.4 1.12, 47.17 0.41, 49.05 0.05, 50.95 0.05, 52.83 0.41, 54.6 1.12, 56.2 2.16, 57.58 3.48, 58.68 5.04, 98.68 75.04, 99.46 76.77, 99.9 78.61, 99.99 80.51, 99.71 82.39, 99.09 84.18, 98.13 85.82, 96.89 87.25, 95.4 88.42, 93.71 89.29, 91.89 89.82, 90 90, 70 90)"
);
}
Expand Down Expand Up @@ -293,6 +293,15 @@ public void testInfiniteLoop() {
);
}

// see https://github.com/shapely/shapely/issues/820
public void testOffsetError() {
checkOffsetCurve(
"LINESTRING (12 20, 60 68, 111 114, 151 159, 210 218)",
3,
"LINESTRING (9.878679656440358 22.121320343559642, 57.878679656440355 70.12132034355965, 57.99069368916718 70.22770917070595, 108.86775926900314 116.11682714467565, 148.75777204394902 160.99309151648976, 148.87867965644037 161.12132034355963, 207.87867965644037 220.12132034355963)"
);
}

//---------------------------------------

public void testQuadSegs() {
Expand Down Expand Up @@ -337,6 +346,33 @@ public void testMinQuadrantSegments_QGIS() {
);
}

// See https://trac.osgeo.org/postgis/ticket/4072
public void testMitreJoinError() {
checkOffsetCurve(
"LINESTRING(362194.505 5649993.044,362197.451 5649994.125,362194.624 5650001.876,362189.684 5650000.114,362192.542 5649992.324,362194.505 5649993.044)",
-0.045, 0, BufferParameters.JOIN_MITRE, -1,
"LINESTRING (362194.52050157124 5649993.001754275, 362197.5086649931 5649994.098225646, 362194.65096611937 5650001.933395073, 362189.626113625 5650000.141129872, 362192.51525161567 5649992.266257602, 362194.5204958858 5649993.001752188)"
);
}

// See https://trac.osgeo.org/postgis/ticket/4072
public void testMitreJoinErrorSimple() {
checkOffsetCurve(
"LINESTRING (4.821 0.72, 7.767 1.801, 4.94 9.552, 0 7.79, 2.858 0, 4.821 0.72)",
-0.045, 0, BufferParameters.JOIN_MITRE, -1,
"LINESTRING (4.83650157122754 0.6777542748970088, 7.824664993161384 1.7742256459460533, 4.966966119329371 9.6093950732796, -0.057886375241824 7.817129871774653, 2.8312516154153906 -0.0577423980712891, 4.836495885800319 0.6777521891305186)"
);
}

// See https://trac.osgeo.org/postgis/ticket/3279
public void testMitreJoinSingleLine() {
checkOffsetCurve(
"LINESTRING (0.39 -0.02, 0.4650008997915482 -0.02, 0.4667128891457749 -0.0202500016082272, 0.4683515425280024 -0.0210000000000019, 0.4699159706879993 -0.0222499999999996, 0.4714061701120011 -0.0240000000000018, 0.4929087886040002 -0.0535958153351002, 0.4968358395870001 -0.0507426457862002, 0.4774061701119963 -0.0239999999999952, 0.476353470688 -0.0222500000000011, 0.4761015425280001 -0.0210000000000007, 0.4766503813740676 -0.0202500058185111, 0.4779990890331232 -0.02, 0.6189999999999996 -0.02, 0.619 -0.0700000000000002, 0.634 -0.0700000000000002, 0.6339999999999998 -0.02, 0.65 -0.02)",
-0.002, 0, BufferParameters.JOIN_MITRE, -1,
"LINESTRING (0.39 -0.022, 0.4648556402268155 -0.022, 0.4661407414895839 -0.0221876631893964, 0.4672953866748729 -0.022716134946407, 0.4685176359449585 -0.0236927292232623, 0.4698334593862525 -0.0252379526243584, 0.4924663251198579 -0.0563894198284619, 0.499629444080312 -0.0511851092703384, 0.479075235654203 -0.022894668402962, 0.4785370545613636 -0.022, 0.6169999999999995 -0.022, 0.617 -0.0720000000000002, 0.636 -0.0720000000000002, 0.6359999999999998 -0.022, 0.65 -0.022)"
);
}

//=======================================

private static final double EQUALS_TOL = 0.05;
Expand Down

0 comments on commit 872bc44

Please sign in to comment.