Skip to content

Commit ecb26e1

Browse files
authored
Fix LineSegment.project to handle segments projecting onto a single endpoint (#1179)
1 parent f1b596a commit ecb26e1

File tree

4 files changed

+123
-11
lines changed

4 files changed

+123
-11
lines changed

modules/app/src/main/java/org/locationtech/jtstest/function/LineSegmentFunctions.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,25 @@ public static Geometry reflectPoint(Geometry g1, Geometry g2)
116116
return g1.getFactory().createPoint(reflectPt);
117117
}
118118

119+
public static Geometry project(Geometry g1, Geometry g2)
120+
{
121+
LineSegment seg1 = toLineSegment(g1);
122+
Coordinate[] line2 = g2.getCoordinates();
123+
if (line2.length == 1) {
124+
Coordinate pt = line2[0];
125+
Coordinate result = seg1.project(pt);
126+
return g1.getFactory().createPoint(result);
127+
}
128+
LineSegment seg2 = new LineSegment(line2[0], line2[1]);
129+
LineSegment result = seg1.project(seg2);
130+
if (result == null)
131+
return g1.getFactory().createLineString();
132+
return result.toGeometry(g1.getFactory());
133+
}
134+
135+
private static LineSegment toLineSegment(Geometry g) {
136+
Coordinate[] line = g.getCoordinates();
137+
return new LineSegment(line[0], line[1]);
138+
}
139+
119140
}

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -468,16 +468,30 @@ public LineSegment project(LineSegment seg)
468468
double pf0 = projectionFactor(seg.p0);
469469
double pf1 = projectionFactor(seg.p1);
470470
// check if segment projects at all
471-
if (pf0 >= 1.0 && pf1 >= 1.0) return null;
472-
if (pf0 <= 0.0 && pf1 <= 0.0) return null;
471+
if (pf0 > 1.0 && pf1 > 1.0) return null;
472+
if (pf0 < 0.0 && pf1 < 0.0) return null;
473473

474-
Coordinate newp0 = project(seg.p0, pf0);
475-
if (pf0 < 0.0) newp0 = p0;
476-
if (pf0 > 1.0) newp0 = p1;
474+
Coordinate newp0;
475+
if (pf0 < 0.0) {
476+
newp0 = p0;
477+
}
478+
else if (pf0 > 1.0) {
479+
newp0 = p1;
480+
}
481+
else {
482+
newp0 = project(seg.p0, pf0);
483+
}
477484

478-
Coordinate newp1 = project(seg.p1, pf1);
479-
if (pf1 < 0.0) newp1 = p0;
480-
if (pf1 > 1.0) newp1 = p1;
485+
Coordinate newp1;
486+
if (pf1 < 0.0) {
487+
newp1 = p0;
488+
}
489+
else if (pf1 > 1.0) {
490+
newp1 = p1;
491+
}
492+
else {
493+
newp1 = project(seg.p1, pf1);
494+
}
481495

482496
return new LineSegment(newp0, newp1);
483497
}

modules/core/src/test/java/org/locationtech/jts/geom/LineSegmentTest.java

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111
*/
1212
package org.locationtech.jts.geom;
1313

14-
import junit.framework.TestCase;
14+
import static org.junit.Assert.assertEquals;
15+
import static org.junit.Assert.assertTrue;
16+
1517
import junit.textui.TestRunner;
18+
import test.jts.GeometryTestCase;
1619

1720

1821
/**
1922
* Test LineSegment methods
2023
*/
21-
public class LineSegmentTest extends TestCase {
24+
public class LineSegmentTest extends GeometryTestCase {
2225

2326
public static void main(String args[]) {
2427
TestRunner.run(LineSegmentTest.class);
@@ -53,6 +56,60 @@ public void testProjectionFactor()
5356
assertTrue(seg2.projectionFactor(new Coordinate(11, 0)) == 0.1);
5457
}
5558

59+
public void testProjectPoint() {
60+
//-- interior point
61+
checkProjectPoint("LINESTRING (4 0, 8 0)", "POINT (5 2)", 5, 0);
62+
//-- endpoint
63+
checkProjectPoint("LINESTRING (4 0, 8 0)", "POINT (8 2)", 8, 0);
64+
//-- beyond end
65+
checkProjectPoint("LINESTRING (4 0, 8 0)", "POINT (9 2)", 9, 0);
66+
//-- before end
67+
checkProjectPoint("LINESTRING (4 0, 8 0)", "POINT (3 2)", 3, 0);
68+
//-- collinear
69+
checkProjectPoint("LINESTRING (4 0, 8 0)", "POINT (2 0)", 2, 0);
70+
}
71+
72+
private void checkProjectPoint(String wkt1, String wkt2, double x, double y) {
73+
LineSegment seg1 = readLineSegment(wkt1);
74+
Point pt = (Point) read(wkt2);
75+
Coordinate p = pt.getCoordinate();
76+
Coordinate actual = seg1.project(p);
77+
78+
checkEqualXY(new Coordinate(x, y), actual, 0.0001);
79+
}
80+
81+
public void testProjectSegment() {
82+
//-- project onto interior segment
83+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (1 2, 2 3)", "LINESTRING(1 0, 2 0)");
84+
//-- project onto interior point
85+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (1 2, 1 4)", "LINESTRING(1 0, 1 0)");
86+
//-- projection includes endpoint
87+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (0 2, 1 4)", "LINESTRING(0 0, 1 0)");
88+
//- projection onto endpoint
89+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (8 2, 8 4)", "LINESTRING(8 0, 8 0)");
90+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (0 2, 0 4)", "LINESTRING(0 0, 0 0)");
91+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (0 2, -1 4)", "LINESTRING(0 0, 0 0)");
92+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (9 1, 8 0)", "LINESTRING(8 0, 8 0)");
93+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (9 1, 8 1)", "LINESTRING(8 0, 8 0)");
94+
//-- no projection
95+
checkProjectSegment("LINESTRING (0 0, 8 0)", "LINESTRING (9 1, 9 2)", null);
96+
}
97+
98+
private void checkProjectSegment(String wkt1, String wkt2, String wktExpected) {
99+
LineSegment seg1 = readLineSegment(wkt1);
100+
LineSegment seg2 = readLineSegment(wkt2);
101+
LineSegment actual = seg1.project(seg2);
102+
103+
LineSegment expected = wktExpected == null ? null : readLineSegment(wktExpected);
104+
checkEqual(expected, actual, 0.0001);
105+
}
106+
107+
private LineSegment readLineSegment(String wkt) {
108+
Geometry g = read(wkt);
109+
LineString line = (LineString) g;
110+
return new LineSegment(line.getCoordinateN(0), line.getCoordinateN(1));
111+
}
112+
56113
public void testLineIntersection() {
57114
// simple case
58115
checkLineIntersection(

modules/core/src/test/java/test/jts/GeometryTestCase.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,27 @@ protected void checkEqualXY(String message, Coordinate expected, Coordinate actu
233233
assertEquals(message + " X", expected.getX(), actual.getX(), tolerance);
234234
assertEquals(message + " Y", expected.getY(), actual.getY(), tolerance);
235235
}
236-
236+
237+
protected void checkEqual(LineSegment expected, LineSegment actual, double tolerance) {
238+
boolean equal;
239+
if (actual == null || expected == null) {
240+
equal = actual == null && expected == null;
241+
}
242+
else {
243+
equal = isEqual(actual, expected, tolerance);
244+
}
245+
if (! equal) {
246+
System.out.format(CHECK_EQUAL_FAIL_MSG, expected, actual );
247+
}
248+
assertTrue(equal);
249+
}
250+
251+
private boolean isEqual(LineSegment actual, LineSegment expected, double tolerance) {
252+
return expected.getCoordinate(0).equals2D(actual.getCoordinate(0), tolerance)
253+
&& expected.getCoordinate(1).equals2D(actual.getCoordinate(1), tolerance);
254+
255+
}
256+
237257
protected void checkNoAlias(Geometry geom, Geometry geom2) {
238258
Geometry geom2Copy = geom2.copy();
239259
geom.apply(new CoordinateFilter() {

0 commit comments

Comments
 (0)