diff --git a/pom.xml b/pom.xml index a0dae88b8..6ff21b87f 100644 --- a/pom.xml +++ b/pom.xml @@ -192,6 +192,9 @@ Matthias Hummel + + Curtis Jensen + Ismael Juma diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 58ad537aa..006a7da7b 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -52,8 +52,11 @@ If the output is not quite correct, check for invisible trailing spaces! + + Added distance to point to 2D Line and Segment. + - "PowellOptimizer" (package "o.a.c.m.optimization.direct") uses + "PowellOptimizer" (package "o.a.c.m.optimization.direct") uses "BrentOptimizer" as its internal line search optimizer. The fix forces the convergence criterion of "BrentOptimizer" to use function values (instead of domain values). diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java index f12fa86f9..eea06ee7e 100644 --- a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java +++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/Line.java @@ -263,6 +263,18 @@ public class Line implements Hyperplane, Embedding + * If the perpendicular extension from the point to the line does not + * cross in the bounds of the line segment, the shortest distance to + * the two end points will be returned. + *

+ * + * Algorithm adapted from: http://www.codeguru.com/forum/printthread.php?s=cc8cf0596231f9a7dba4da6e77c29db3&t=194400&pp=15&page=1 + */ + public double distance(final Vector2D p) { + final double deltaX = end.getX() - start.getX(); + final double deltaY = end.getY() - start.getY(); + + final double r = ((p.getX() - start.getX()) * deltaX + (p.getY() - start.getY()) * deltaY) / + (deltaX * deltaX + deltaY * deltaY); + + // r == 0 => P = startPt + // r == 1 => P = endPt + // r < 0 => P is on the backward extension of the segment + // r > 1 => P is on the forward extension of the segment + // 0 < r < 1 => P is on the segment + + // if point isn't on the line segment, just return the shortest distance to the end points + if (r < 0 || r > 1) { + final double dist1 = getStart().distance(p); + final double dist2 = getEnd().distance(p); + + return FastMath.min(dist1, dist2); + } + else { + // find point on line and see if it is in the line segment + final double px = start.getX() + r * deltaX; + final double py = start.getY() + r * deltaY; + + final Vector2D interPt = new Vector2D(px, py); + return interPt.distance(p); + } + } } diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/LineTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/LineTest.java index d59b67ca5..e54d776f6 100644 --- a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/LineTest.java +++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/LineTest.java @@ -63,6 +63,13 @@ public class LineTest { Assert.assertEquals(+5.0, l.getOffset(new Vector2D(-5, 2)), 1.0e-10); } + @Test + public void testDistance() { + Line l = new Line(new Vector2D(2, 1), new Vector2D(-2, -2)); + Assert.assertEquals(+5.0, l.distance(new Vector2D(5, -3)), 1.0e-10); + Assert.assertEquals(+5.0, l.distance(new Vector2D(-5, 2)), 1.0e-10); + } + @Test public void testPointAt() { Line l = new Line(new Vector2D(2, 1), new Vector2D(-2, -2)); diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/SegmentTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/SegmentTest.java new file mode 100644 index 000000000..22f46f2e4 --- /dev/null +++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/SegmentTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.math3.geometry.euclidean.twod; + +import org.apache.commons.math3.geometry.euclidean.twod.Line; +import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; +import org.apache.commons.math3.util.FastMath; +import org.junit.Assert; +import org.junit.Test; + +public class SegmentTest { + + @Test + public void testDistance() { + Vector2D start = new Vector2D(2, 2); + Vector2D end = new Vector2D(-2, -2); + Segment segment = new Segment(start, end, new Line(start, end)); + + // distance to center of segment + Assert.assertEquals(FastMath.sqrt(2), segment.distance(new Vector2D(1, -1)), 1.0e-10); + + // distance a point on segment + Assert.assertEquals(FastMath.sin(Math.PI / 4.0), segment.distance(new Vector2D(0, -1)), 1.0e-10); + + // distance to end point + Assert.assertEquals(FastMath.sqrt(8), segment.distance(new Vector2D(0, 4)), 1.0e-10); + + // distance to start point + Assert.assertEquals(FastMath.sqrt(8), segment.distance(new Vector2D(0, -4)), 1.0e-10); + } +}