一点与线段之间的最短距离

444

我需要一个基本函数来查找点与线段之间最短的距离。您可以使用任何编程语言编写解决方案,我可以将其转换为我正在使用的语言(Javascript)。

编辑:我的线段由两个端点定义。因此,我的线段AB由两个点A(x1,y1)B(x2,y2)定义。我正在尝试查找该线段与点C(x3,y3)之间的距离。我的几何技能有些生疏,所以我看到的示例很令人困惑,对此我感到抱歉。


4
@ArthurKalliokoski:那个链接失效了,但我找到了一份副本:http://paulbourke.net/geometry/pointline/ - Gunther Struyf
我不得不自己寻找并从谷歌上偶然发现了这个 -- 如果有人正在寻找一种实现方式,并且可以使用Python,那么Shapely是一个不错的选择。你需要寻找路径的LineString类。 - TC1
12
@GuntherStruyf说的链接也失效了,但这个类似的链接有效:http://paulbourke.net/geometry/pointlineplane/。 - Michael
1
如果有人想要查找点与直线之间的距离,而不是点与线段之间的距离,请查看此链接:https://gist.github.com/rhyolight/2846020 - Nick Budden
1
上面的链接已经失效。这里提供伪代码和C++示例,详细解释和推导,就像一本教科书一样,http://geomalgorithms.com/a02-_lines.html - Eric
显示剩余3条评论
56个回答

2

这里没有看到Java的实现,因此我将已接受答案的Javascript函数翻译成了Java代码:

static double sqr(double x) {
    return x * x;
}
static double dist2(DoublePoint v, DoublePoint w) {
    return sqr(v.x - w.x) + sqr(v.y - w.y);
}
static double distToSegmentSquared(DoublePoint p, DoublePoint v, DoublePoint w) {
    double l2 = dist2(v, w);
    if (l2 == 0) return dist2(p, v);
    double t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
    if (t < 0) return dist2(p, v);
    if (t > 1) return dist2(p, w);
    return dist2(p, new DoublePoint(
            v.x + t * (w.x - v.x),
            v.y + t * (w.y - v.y)
    ));
}
static double distToSegment(DoublePoint p, DoublePoint v, DoublePoint w) {
    return Math.sqrt(distToSegmentSquared(p, v, w));
}
static class DoublePoint {
    public double x;
    public double y;

    public DoublePoint(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

1

上面的函数对垂直线不起作用。这是一个运行良好的函数! 带有点p1、p2的线,以及检查点CheckPoint为p;

public float DistanceOfPointToLine2(PointF p1, PointF p2, PointF p)
{
  //          (y1-y2)x + (x2-x1)y + (x1y2-x2y1)
  //d(P,L) = --------------------------------
  //         sqrt( (x2-x1)pow2 + (y2-y1)pow2 )

  double ch = (p1.Y - p2.Y) * p.X + (p2.X - p1.X) * p.Y + (p1.X * p2.Y - p2.X * p1.Y);
  double del = Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
  double d = ch / del;
  return (float)d;
}

1
不回答问题。这仅适用于线(在空间中无限延伸的那些)而不是线段(具有有限长度的线段)。 - Trinidad
1
“above function” 是一个模糊的引用。(这让我很烦恼,因为有时这个答案会显示在我的答案下面。) - RenniePet

1
GLSL版本:
// line (a -> b ) point p[enter image description here][1]
float distanceToLine(vec2 a, vec2 b, vec2 p) {
    float aside = dot((p - a),(b - a));
    if(aside< 0.0) return length(p-a);
    float bside = dot((p - b),(a - b));
    if(bside< 0.0) return length(p-b);
    vec2 pointOnLine = (bside*a + aside*b)/pow(length(a-b),2.0);
    return length(p - pointOnLine);
}

1
这是一种基于向量数学的解决方案;该解决方案也适用于更高维度,并且还会报告线段上的交点。
def dist(x1,y1,x2,y2,px,py):
    a = np.array([[x1,y1]]).T
    b = np.array([[x2,y2]]).T
    x = np.array([[px,py]]).T
    tp = (np.dot(x.T, b) - np.dot(a.T, b)) / np.dot(b.T, b)
    tp = tp[0][0]
    tmp = x - (a + tp*b)
    d = np.sqrt(np.dot(tmp.T,tmp)[0][0])
    return d, a+tp*b

x1,y1=2.,2.
x2,y2=5.,5.
px,py=4.,1.

d, inters = dist(x1,y1, x2,y2, px,py)
print (d)
print (inters)

结果是。
2.1213203435596424
[[2.5]
 [2.5]]

这里解释了数学问题

https://brilliant.org/wiki/distance-between-point-and-line/


1
这是我最终编写的代码。该代码假设点的定义形式为 {x:5, y:7}。请注意,这不是绝对最有效的方法,但这是我能想到的最简单和最易于理解的代码。
// a, b, and c in the code below are all points

function distance(a, b)
{
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return Math.sqrt(dx*dx + dy*dy);
}

function Segment(a, b)
{
    var ab = {
        x: b.x - a.x,
        y: b.y - a.y
    };
    var length = distance(a, b);

    function cross(c) {
        return ab.x * (c.y-a.y) - ab.y * (c.x-a.x);
    };

    this.distanceFrom = function(c) {
        return Math.min(distance(a,c),
                        distance(b,c),
                        Math.abs(cross(c) / length));
    };
}

1
这段代码有一个错误。在线段所在的直线附近的一个点,但远离线段的一端,会被错误地判断为靠近该线段。 - Grumdrig
有趣,下次我在这个代码库上工作时会调查一下以确认你的说法。谢谢你的提示。 - Eli Courtwright

1
%Matlab solution by Tim from Cody
function ans=distP2S(x0,y0,x1,y1,x2,y2)
% Point is x0,y0
z=complex(x0-x1,y0-y1);
complex(x2-x1,y2-y1);
abs(z-ans*min(1,max(0,real(z/ans))));

1

这里是与C++答案相同的内容,但已移植到Pascal。点参数的顺序已更改以适应我的代码,但是本质相同。

function Dot(const p1, p2: PointF): double;
begin
  Result := p1.x * p2.x + p1.y * p2.y;
end;
function SubPoint(const p1, p2: PointF): PointF;
begin
  result.x := p1.x - p2.x;
  result.y := p1.y - p2.y;
end;

function ShortestDistance2(const p,v,w : PointF) : double;
var
  l2,t : double;
  projection,tt: PointF;
begin
  // Return minimum distance between line segment vw and point p
  //l2 := length_squared(v, w);  // i.e. |w-v|^2 -  avoid a sqrt
  l2 := Distance(v,w);
  l2 := MPower(l2,2);
  if (l2 = 0.0) then begin
    result:= Distance(p, v);   // v == w case
    exit;
  end;
  // Consider the line extending the segment, parameterized as v + t (w - v).
  // We find projection of point p onto the line.
  // It falls where t = [(p-v) . (w-v)] / |w-v|^2
  t := Dot(SubPoint(p,v),SubPoint(w,v)) / l2;
  if (t < 0.0) then begin
    result := Distance(p, v);       // Beyond the 'v' end of the segment
    exit;
  end
  else if (t > 1.0) then begin
    result := Distance(p, w);  // Beyond the 'w' end of the segment
    exit;
  end;
  //projection := v + t * (w - v);  // Projection falls on the segment
  tt.x := v.x + t * (w.x - v.x);
  tt.y := v.y + t * (w.y - v.y);
  result := Distance(p, tt);
end;

这个答案存在几个问题:1.类型PointF未声明(可能是某些Pascal实现中的标准类型)。它很可能是一个记录x,y:double; end; 2.函数Distance和MPower未声明,并且没有解释它们的作用(我们可以猜测)。3.变量projection已声明但从未使用。总体而言,这使得它成为一个相当糟糕的答案。 - dummzeuch

1
该算法基于找到指定直线与同时包含指定点的正交线之间的交点,并计算它们之间的距离。在线段的情况下,我们必须检查交点是否在线段的两个端点之间,如果不是这种情况,则最小距离在指定点和线段端点之一之间。这是一个C#实现。
Double Distance(Point a, Point b)
{
    double xdiff = a.X - b.X, ydiff = a.Y - b.Y;
    return Math.Sqrt((long)xdiff * xdiff + (long)ydiff * ydiff);
}

Boolean IsBetween(double x, double a, double b)
{
    return ((a <= b && x >= a && x <= b) || (a > b && x <= a && x >= b));
}

Double GetDistance(Point pt, Point pt1, Point pt2, out Point intersection)
{
    Double a, x, y, R;

    if (pt1.X != pt2.X) {
        a = (double)(pt2.Y - pt1.Y) / (pt2.X - pt1.X);
        x = (a * (pt.Y - pt1.Y) + a * a * pt1.X + pt.X) / (a * a + 1);
        y = a * x + pt1.Y - a * pt1.X; }
    else { x = pt1.X;  y = pt.Y; }

    if (IsBetween(x, pt1.X, pt2.X) && IsBetween(y, pt1.Y, pt2.Y)) {
        intersection = new Point((int)x, (int)y);
        R = Distance(intersection, pt); }
    else {
        double d1 = Distance(pt, pt1), d2 = Distance(pt, pt2);
        if (d1 < d2) { intersection = pt1; R = d1; }
        else { intersection = pt2; R = d2; }}

    return R;
}

谢谢!我需要在Java中找到交集,这个翻译得非常完美! - Alcamtar

1

解决Dart和Flutter的方案:

import 'dart:math' as math;
 class Utils {
   static double shortestDistance(Point p1, Point p2, Point p3){
      double px = p2.x - p1.x;
      double py = p2.y - p1.y;
      double temp = (px*px) + (py*py);
      double u = ((p3.x - p1.x)*px + (p3.y - p1.y)* py) /temp;
      if(u>1){
        u=1;
      }
      else if(u<0){
        u=0;
      }
      double x = p1.x + u*px;
      double y = p1.y + u*py;
      double dx = x - p3.x;
      double dy = y - p3.y;
      double dist = math.sqrt(dx*dx+dy*dy);
      return dist;
   }
}

class Point {
  double x;
  double y;
  Point(this.x, this.y);
}

你救了我的一天。请问你在这里使用了哪个方程式?我尝试过从直线到点的最短距离,但对于线段来说它不起作用。 - Kamran Bashir

0
想用GLSL实现这个,但是如果可能的话最好避免所有那些条件语句。使用clamp()可以避免两个端点的情况:
// find closest point to P on line segment AB:
vec3 closest_point_on_line_segment(in vec3 P, in vec3 A, in vec3 B) {
    vec3 AP = P - A, AB = B - A;
    float l = dot(AB, AB);
    if (l <= 0.0000001) return A;    // A and B are practically the same
    return AP - AB*clamp(dot(AP, AB)/l, 0.0, 1.0);  // do the projection
}

如果您可以确定A和B永远不会非常接近彼此,那么可以简化为删除if()。事实上,即使A和B是相同的,在没有条件的情况下,我的GPU仍然可以使用这个版本来给出正确的结果(但这是使用OpenGL 4.1之前的版本,在GLSL除以零未定义的情况下):

// find closest point to P on line segment AB:
vec3 closest_point_on_line_segment(in vec3 P, in vec3 A, in vec3 B) {
    vec3 AP = P - A, AB = B - A;
    return AP - AB*clamp(dot(AP, AB)/dot(AB, AB), 0.0, 1.0);
}

计算距离是微不足道的 - GLSL提供了一个distance()函数,您可以在最近点和P上使用它。
Iñigo Quilez的胶囊距离函数代码启发。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接