Skip to content

Commit ac7a165

Browse files
authored
Improve LineStringSnapper performance by using squared distance (#1111)
1 parent 16b055f commit ac7a165

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

modules/core/src/main/java/org/locationtech/jts/algorithm/Distance.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,64 @@ public static double pointToSegment(Coordinate p, Coordinate A,
188188
/ len2;
189189
return Math.abs(s) * Math.sqrt(len2);
190190
}
191+
192+
/**
193+
* Computes the squared distance from a point p to a line segment AB.
194+
*
195+
* Note: NON-ROBUST!
196+
*
197+
* @param p
198+
* the point to compute the distance for
199+
* @param A
200+
* one point of the line
201+
* @param B
202+
* another point of the line (must be different to A)
203+
* @return the distance from p to line segment AB
204+
*/
205+
public static double pointToSegmentSq(Coordinate p, Coordinate A,
206+
Coordinate B)
207+
{
208+
// if start = end, then just compute distance to one of the endpoints
209+
if (A.x == B.x && A.y == B.y)
210+
return p.distanceSq(A);
211+
212+
// otherwise use comp.graphics.algorithms Frequently Asked Questions method
213+
/*
214+
* (1) r = AC dot AB
215+
* ---------
216+
* ||AB||^2
217+
*
218+
* r has the following meaning:
219+
* r=0 P = A
220+
* r=1 P = B
221+
* r<0 P is on the backward extension of AB
222+
* r>1 P is on the forward extension of AB
223+
* 0<r<1 P is interior to AB
224+
*/
225+
226+
double len2 = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y);
227+
double r = ((p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y))
228+
/ len2;
229+
230+
if (r <= 0.0)
231+
return p.distanceSq(A);
232+
if (r >= 1.0)
233+
return p.distanceSq(B);
234+
235+
/*
236+
* (2) s = (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
237+
* -----------------------------
238+
* L^2
239+
*
240+
* Then the distance from C to P = |s|*L.
241+
*
242+
* This is the same calculation as {@link #distancePointLinePerpendicular}.
243+
* Unrolled here for performance.
244+
*/
245+
double s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y))
246+
/ len2;
247+
return s*s*len2;
248+
}
191249

192250
/**
193251
* Computes the perpendicular distance from a point p to the (infinite) line

modules/core/src/main/java/org/locationtech/jts/geom/Coordinate.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,19 @@ public double distance(Coordinate c) {
438438
double dy = y - c.y;
439439
return MathUtil.hypot(dx, dy);
440440
}
441+
442+
/**
443+
* Computes the 2-dimensional squared Euclidean distance to another location.
444+
* The Z-ordinate is ignored.
445+
*
446+
* @param c a point
447+
* @return the 2-dimensional squared Euclidean distance between the locations
448+
*/
449+
public double distanceSq(Coordinate c) {
450+
double dx = x - c.x;
451+
double dy = y - c.y;
452+
return dx * dx + dy * dy;
453+
}
441454

442455
/**
443456
* Computes the 3-dimensional Euclidean distance to another location.

modules/core/src/main/java/org/locationtech/jts/operation/overlay/snap/LineStringSnapper.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
package org.locationtech.jts.operation.overlay.snap;
1414

15+
import org.locationtech.jts.algorithm.Distance;
1516
import org.locationtech.jts.geom.Coordinate;
1617
import org.locationtech.jts.geom.CoordinateList;
1718
import org.locationtech.jts.geom.LineSegment;
@@ -30,9 +31,9 @@
3031
public class LineStringSnapper
3132
{
3233
private double snapTolerance = 0.0;
34+
private double snapToleranceSq = 0.0;
3335

3436
private Coordinate[] srcPts;
35-
private LineSegment seg = new LineSegment(); // for reuse during snapping
3637
private boolean allowSnappingToSourceVertices = false;
3738
private boolean isClosed = false;
3839

@@ -60,6 +61,7 @@ public LineStringSnapper(Coordinate[] srcPts, double snapTolerance)
6061
this.srcPts = srcPts;
6162
isClosed = isClosed(srcPts);
6263
this.snapTolerance = snapTolerance;
64+
this.snapToleranceSq = snapTolerance*snapTolerance;
6365
}
6466

6567
public void setAllowSnappingToSourceVertices(boolean allowSnappingToSourceVertices)
@@ -191,23 +193,23 @@ private int findSegmentIndexToSnap(Coordinate snapPt, CoordinateList srcCoords)
191193
double minDist = Double.MAX_VALUE;
192194
int snapIndex = -1;
193195
for (int i = 0; i < srcCoords.size() - 1; i++) {
194-
seg.p0 = (Coordinate) srcCoords.get(i);
195-
seg.p1 = (Coordinate) srcCoords.get(i + 1);
196+
final Coordinate p0 = srcCoords.get(i);
197+
final Coordinate p1 = srcCoords.get(i+1);
196198

197199
/**
198200
* Check if the snap pt is equal to one of the segment endpoints.
199201
*
200202
* If the snap pt is already in the src list, don't snap at all.
201203
*/
202-
if (seg.p0.equals2D(snapPt) || seg.p1.equals2D(snapPt)) {
204+
if (p0.equals2D(snapPt) || p1.equals2D(snapPt)) {
203205
if (allowSnappingToSourceVertices)
204206
continue;
205207
else
206208
return -1;
207209
}
208210

209-
double dist = seg.distance(snapPt);
210-
if (dist < snapTolerance && dist < minDist) {
211+
double dist = Distance.pointToSegmentSq(snapPt, p0, p1);
212+
if (dist < snapToleranceSq && dist < minDist) {
211213
minDist = dist;
212214
snapIndex = i;
213215
}

0 commit comments

Comments
 (0)