【问题标题】:Shortest distance between a point and a line segment点到线段的最短距离
【发布时间】:2010-10-25 08:29:44
【问题描述】:

我需要一个基本函数来找到点和线段之间的最短距离。随意用您想要的任何语言编写解决方案;我可以把它翻译成我正在使用的(Javascript)。

编辑:我的线段由两个端点定义。所以我的线段ABA (x1,y1)B (x2,y2)这两个点定义。我试图找出这条线段和点C (x3,y3) 之间的距离。我的几何技能生疏了,所以我看到的例子很混乱,很抱歉。

【问题讨论】:

标签: language-agnostic geometry distance line-segment


【解决方案1】:

上述功能不适用于垂直线。这是一个运行良好的功能! 与点 p1、p2 连线。检查点是 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;
}

【讨论】:

  • 不回答问题。这仅适用于线(在空间中无限延伸的线)而不是线段(具有有限长度)。
  • "above function" 是一个模棱两可的引用。 (让我很生气,因为有时这个答案会显示在我的答案下方。)
【解决方案2】:

这与 C++ 答案相同,但移植到帕斯卡。 point 参数的顺序已更改以适合我的代码,但相同。

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;

【讨论】:

  • 这个答案有几个问题: PointF 类型没有声明(也许这是某些 Pascal 实现中的标准类型)。这可能是一个记录 x,y: double;结尾; 2. 函数 Distance 和 MPower 没有被声明,也没有解释它们做了什么(我们可以猜测,是的)。 3. 变量 projection 已声明但从未使用过。总的来说,这是一个相当糟糕的答案。
【解决方案3】:
%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))));

【讨论】:

    【解决方案4】:

    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);
    }
    

    【讨论】:

      【解决方案5】:

      该算法基于找到指定线与包含指定点的正交线的交点,并计算其距离。如果是线段,我们必须检查交点是否在线段的点之间,如果不是,则最小距离在指定点和线段的端点之一之间。这是一个 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 中找到交集,这个翻译完美!
      【解决方案6】:

      这是一个基于向量数学的;此解决方案也适用于更高维度并且还报告交点(在线段上)。

      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/

      【讨论】:

        【解决方案7】:

        飞镖和颤振的解决方案:

        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);
        }
        

        【讨论】:

        • 你拯救了我的一天。你能告诉我你在这里使用了哪个等式吗?我尝试过从线到点的最短距离,但它不适用于线段。
        【解决方案8】:

        二维坐标数组的 Python Numpy 实现:

        import numpy as np
        
        
        def dist2d(p1, p2, coords):
            ''' Distance from points to a finite line btwn p1 -> p2 '''
            assert coords.ndim == 2 and coords.shape[1] == 2, 'coords is not 2 dim'
            dp = p2 - p1
            st = dp[0]**2 + dp[1]**2
            u = ((coords[:, 0] - p1[0]) * dp[0] + (coords[:, 1] - p1[1]) * dp[1]) / st
        
            u[u > 1.] = 1.
            u[u < 0.] = 0.
        
            dx = (p1[0] + u * dp[0]) - coords[:, 0]
            dy = (p1[1] + u * dp[1]) - coords[:, 1]
        
            return np.sqrt(dx**2 + dy**2)
        
        
        # Usage:
        p1 = np.array([0., 0.])
        p2 = np.array([0., 10.])
        
        # List of coordinates
        coords = np.array(
            [[0., 0.],
             [5., 5.],
             [10., 10.],
             [20., 20.]
             ])
        
        d = dist2d(p1, p2, coords)
        
        # Single coordinate
        coord = np.array([25., 25.])
        d = dist2d(p1, p2, coord[np.newaxis, :])
        

        【讨论】:

          【解决方案9】:

          Matlab 直接 Grumdrig 实现

          function ans=distP2S(px,py,vx,vy,wx,wy)
          % [px py vx vy wx wy]
            t=( (px-vx)*(wx-vx)+(py-vy)*(wy-vy) )/idist(vx,wx,vy,wy)^2;
            [idist(px,vx,py,vy) idist(px,vx+t*(wx-vx),py,vy+t*(wy-vy)) idist(px,wx,py,wy) ];
            ans(1+(t>0)+(t>1)); % <0 0<=t<=1 t>1     
           end
          
          function d=idist(a,b,c,d)
           d=abs(a-b+1i*(c-d));
          end
          

          【讨论】:

            【解决方案10】:

            如果它是一条无限线,而不是线段,最简单的方法是这个(在 ruby​​ 中),其中 mx + b 是线,(x1, y1) 是已知点

            (y1 - mx1 - b).abs / Math.sqrt(m**2 + 1)
            

            【讨论】:

            • 您尚未定义mx1bm,您的解决方案不适用于线段。
            【解决方案11】:

            刚遇到这个,我想我会添加一个 Lua 实现。它假定点以表格 {x=xVal, y=yVal} 的形式给出,而线或线段由包含两个点的表格给出(参见下面的示例):

            function distance( P1, P2 )
                return math.sqrt((P1.x-P2.x)^2 + (P1.y-P2.y)^2)
            end
            
            -- Returns false if the point lies beyond the reaches of the segment
            function distPointToSegment( line, P )
                if line[1].x == line[2].x and line[1].y == line[2].y then
                    print("Error: Not a line!")
                    return false
                end
            
                local d = distance( line[1], line[2] )
            
                local t = ((P.x - line[1].x)*(line[2].x - line[1].x) + (P.y - line[1].y)*(line[2].y - line[1].y))/(d^2)
            
                local projection = {}
                projection.x = line[1].x + t*(line[2].x-line[1].x)
                projection.y = line[1].y + t*(line[2].y-line[1].y)
            
                if t >= 0 and t <= 1 then   -- within line segment?
                    return distance( projection, {x=P.x, y=P.y} )
                else
                    return false
                end
            end
            
            -- Returns value even if point is further down the line (outside segment)
            function distPointToLine( line, P )
                if line[1].x == line[2].x and line[1].y == line[2].y then
                    print("Error: Not a line!")
                    return false
                end
            
                local d = distance( line[1], line[2] )
            
                local t = ((P.x - line[1].x)*(line[2].x - line[1].x) + (P.y - line[1].y)*(line[2].y - line[1].y))/(d^2)
            
                local projection = {}
                projection.x = line[1].x + t*(line[2].x-line[1].x)
                projection.y = line[1].y + t*(line[2].y-line[1].y)
            
                return distance( projection, {x=P.x, y=P.y} )
            end
            

            示例用法:

            local P1 = {x = 0, y = 0}
            local P2 = {x = 10, y = 10}
            local line = { P1, P2 }
            local P3 = {x = 7, y = 15}
            print(distPointToLine( line, P3 ))  -- prints 5.6568542494924
            print(distPointToSegment( line, P3 )) -- prints false
            

            【讨论】:

              【解决方案12】:

              基于 formula 的 JavaScript 中更简洁的解决方案:

              distToSegment: function (point, linePointA, linePointB){
              
                  var x0 = point.X;
                  var y0 = point.Y;
              
                  var x1 = linePointA.X;
                  var y1 = linePointA.Y;
              
                  var x2 = linePointB.X;
                  var y2 = linePointB.Y;
              
                  var Dx = (x2 - x1);
                  var Dy = (y2 - y1);
              
                  var numerator = Math.abs(Dy*x0 - Dx*y0 - x1*y2 + x2*y1);
                  var denominator = Math.sqrt(Dx*Dx + Dy*Dy);
                  if (denominator == 0) {
                      return this.dist2(point, linePointA);
                  }
              
                  return numerator/denominator;
              
              }
              

              【讨论】:

              • 感谢您实际写下公式。但是,这是计算到一条线的距离的公式,而不是到一条线段的距离。选择 (x0,y0)=(-10,0), (x1,y1)=(0,0) 和 (x2,y2)=(10,0) 给出的距离为 0,而它应该是 10 .
              【解决方案13】:

              想在 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 仍然使用这个无条件版本给出正确的结果(但这是使用 GLSL 除以零的 pre-OpenGL 4.1 未定义) :

              // 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's code for a capsule distance function

              【讨论】:

                【解决方案14】:

                this answer 相同,但在 Visual Basic 中除外。使其可用作 Microsoft Excel 和 VBA/宏中的用户定义函数。

                函数返回点(x,y)到(x1,y1)和(x2,y2)定义的线段的最近距离

                Function DistanceToSegment(x As Double, y As Double, x1 As Double, y1 As Double, x2 As Double, y2 As Double)
                
                  Dim A As Double
                  A = x - x1
                  Dim B As Double
                  B = y - y1
                  Dim C  As Double
                  C = x2 - x1
                  Dim D As Double
                  D = y2 - y1
                
                  Dim dot As Double
                  dot = A * C + B * D
                  Dim len_sq As Double
                  len_sq = C * C + D * D
                  Dim param As Double
                  param = -1
                
                  If (len_sq <> 0) Then
                      param = dot / len_sq
                  End If
                
                  Dim xx As Double
                  Dim yy As Double
                
                  If (param < 0) Then
                    xx = x1
                    yy = y1
                  ElseIf (param > 1) Then
                    xx = x2
                    yy = y2
                  Else
                    xx = x1 + param * C
                    yy = y1 + param * D
                  End If
                
                  Dim dx As Double
                  dx = x - xx
                  Dim dy As Double
                  dy = y - yy
                
                  DistanceToSegment = Math.Sqr(dx * dx + dy * dy)
                
                End Function
                

                【讨论】:

                  【解决方案15】:

                  卢亚: 查找线段(不是整条线)和点之间的最小距离

                  function solveLinearEquation(A1,B1,C1,A2,B2,C2)
                  --it is the implitaion of a method of solving linear equations in x and y
                    local f1 = B1*C2 -B2*C1
                    local f2 = A2*C1-A1*C2
                    local f3 = A1*B2 -A2*B1
                    return {x= f1/f3, y= f2/f3}
                  end
                  
                  
                  function pointLiesOnLine(x,y,x1,y1,x2,y2)
                    local dx1 = x-x1
                    local  dy1 = y-y1
                    local dx2 = x-x2
                    local  dy2 = y-y2
                    local crossProduct = dy1*dx2 -dx1*dy2
                  
                  if crossProduct ~= 0  then  return  false
                  else
                    if ((x1>=x) and (x>=x2)) or ((x2>=x) and (x>=x1)) then
                      if ((y1>=y) and (y>=y2)) or ((y2>=y) and (y>=y1)) then
                        return true
                      else return false end
                    else  return false end
                  end
                  end
                  
                  
                  function dist(x1,y1,x2,y2)
                    local dx = x1-x2
                    local dy = y1-y2
                    return math.sqrt(dx*dx + dy* dy)
                   end
                  
                  
                  function findMinDistBetnPointAndLine(x1,y1,x2,y2,x3,y3)
                  -- finds the min  distance between (x3,y3) and line (x1,y2)--(x2,y2)
                     local A2,B2,C2,A1,B1,C1
                     local dx = y2-y1
                     local dy = x2-x1
                     if dx == 0 then A2=1 B2=0 C2=-x3 A1=0 B1=1 C1=-y1 
                     elseif dy == 0 then A2=0 B2=1 C2=-y3 A1=1 B1=0 C1=-x1
                     else
                        local m1 = dy/dx
                        local m2 = -1/m1
                        A2=m2 B2=-1 C2=y3-m2*x3 A1=m1 B1=-1 C1=y1-m1*x1
                     end
                   local intsecPoint= solveLinearEquation(A1,B1,C1,A2,B2,C2)
                  if pointLiesOnLine(intsecPoint.x, intsecPoint.y,x1,y1,x2,y2) then
                     return dist(intsecPoint.x, intsecPoint.y, x3,y3)
                   else
                     return math.min(dist(x3,y3,x1,y1),dist(x3,y3,x2,y2))
                  end
                  end
                  

                  【讨论】:

                    【解决方案16】:

                    此答案基于accepted answer 的 JavaScript 解决方案。 它主要只是格式更好,函数名称更长,当然函数语法更短,因为它在 ES6 + CoffeeScript 中。

                    JavaScript 版本 (ES6)

                    distanceSquared = (v, w)=> Math.pow(v.x - w.x, 2) + Math.pow(v.y - w.y, 2);
                    distance = (v, w)=> Math.sqrt(distanceSquared(v, w));
                    
                    distanceToLineSegmentSquared = (p, v, w)=> {
                        l2 = distanceSquared(v, w);
                        if (l2 === 0) {
                            return distanceSquared(p, v);
                        }
                        t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
                        t = Math.max(0, Math.min(1, t));
                        return distanceSquared(p, {
                            x: v.x + t * (w.x - v.x),
                            y: v.y + t * (w.y - v.y)
                        });
                    }
                    distanceToLineSegment = (p, v, w)=> {
                        return Math.sqrt(distanceToLineSegmentSquared(p, v));
                    }
                    

                    CoffeeScript 版本

                    distanceSquared = (v, w)-> (v.x - w.x) ** 2 + (v.y - w.y) ** 2
                    distance = (v, w)-> Math.sqrt(distanceSquared(v, w))
                    
                    distanceToLineSegmentSquared = (p, v, w)->
                        l2 = distanceSquared(v, w)
                        return distanceSquared(p, v) if l2 is 0
                        t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2
                        t = Math.max(0, Math.min(1, t))
                        distanceSquared(p, {
                            x: v.x + t * (w.x - v.x)
                            y: v.y + t * (w.y - v.y)
                        })
                    
                    distanceToLineSegment = (p, v, w)->
                        Math.sqrt(distanceToLineSegmentSquared(p, v, w))
                    

                    【讨论】:

                    • 在JS版本中,distance()是多余的,不是吗?
                    • 是的,在两个版本中都没有使用它。随意删除它。
                    【解决方案17】:

                    在R中

                         #distance beetween segment ab and point c in 2D space
                    getDistance_ort_2 <- function(a, b, c){
                      #go to complex numbers
                      A<-c(a[1]+1i*a[2],b[1]+1i*b[2])
                      q=c[1]+1i*c[2]
                      
                      #function to get coefficients of line (ab)
                      getAlphaBeta <- function(A)
                      { a<-Re(A[2])-Re(A[1])
                        b<-Im(A[2])-Im(A[1])
                        ab<-as.numeric()
                        ab[1] <- -Re(A[1])*b/a+Im(A[1])
                        ab[2] <-b/a
                        if(Im(A[1])==Im(A[2])) ab<- c(Im(A[1]),0)
                        if(Re(A[1])==Re(A[2])) ab <- NA
                        return(ab)
                      }
                      
                      #function to get coefficients of line ortogonal to line (ab) which goes through point q
                      getAlphaBeta_ort<-function(A,q)
                      { ab <- getAlphaBeta(A) 
                      coef<-c(Re(q)/ab[2]+Im(q),-1/ab[2])
                      if(Re(A[1])==Re(A[2])) coef<-c(Im(q),0)
                      return(coef)
                      }
                      
                      #function to get coordinates of interception point 
                      #between line (ab) and its ortogonal which goes through point q
                      getIntersection_ort <- function(A, q){
                        A.ab <- getAlphaBeta(A)
                        q.ab <- getAlphaBeta_ort(A,q)
                        if (!is.na(A.ab[1])&A.ab[2]==0) {
                          x<-Re(q)
                          y<-Im(A[1])}
                        if (is.na(A.ab[1])) {
                          x<-Re(A[1])
                          y<-Im(q)
                        } 
                        if (!is.na(A.ab[1])&A.ab[2]!=0) {
                          x <- (q.ab[1] - A.ab[1])/(A.ab[2] - q.ab[2])
                          y <- q.ab[1] + q.ab[2]*x}
                        xy <- x + 1i*y  
                        return(xy)
                      }
                      
                      intersect<-getIntersection_ort(A,q)
                      if ((Mod(A[1]-intersect)+Mod(A[2]-intersect))>Mod(A[1]-A[2])) {dist<-min(Mod(A[1]-q),Mod(A[2]-q))
                      } else dist<-Mod(q-intersect)
                      return(dist)
                    }
                    
                    
                    
                     
                    

                    【讨论】:

                      【解决方案18】:

                      在javascript中使用几何:

                      var a = { x:20, y:20};//start segment    
                      var b = { x:40, y:30};//end segment   
                      var c = { x:37, y:14};//point   
                      
                      // magnitude from a to c    
                      var ac = Math.sqrt( Math.pow( ( a.x - c.x ), 2 ) + Math.pow( ( a.y - c.y ), 2) );    
                      // magnitude from b to c   
                      var bc = Math.sqrt( Math.pow( ( b.x - c.x ), 2 ) + Math.pow( ( b.y - c.y ), 2 ) );    
                      // magnitude from a to b (base)     
                      var ab = Math.sqrt( Math.pow( ( a.x - b.x ), 2 ) + Math.pow( ( a.y - b.y ), 2 ) );    
                       // perimeter of triangle     
                      var p = ac + bc + ab;    
                       // area of the triangle    
                      var area = Math.sqrt( p/2 * ( p/2 - ac) * ( p/2 - bc ) * ( p/2 - ab ) );    
                       // height of the triangle = distance    
                      var h = ( area * 2 ) / ab;    
                      console.log ("height: " + h);
                      

                      【讨论】:

                      • 好主意,但高度将是到线的距离,在某些情况下,到线段的距离 = 到线段开始或结束的距离。
                      【解决方案19】:

                      用于 3D 线段和点的 Eigen C++ 版本

                      // Return minimum distance between line segment: head--->tail and point
                      double MinimumDistance(Eigen::Vector3d head, Eigen::Vector3d tail,Eigen::Vector3d point)
                      {
                          double l2 = std::pow((head - tail).norm(),2);
                          if(l2 ==0.0) return (head - point).norm();// head == tail case
                      
                          // Consider the line extending the segment, parameterized as head + t (tail - point).
                          // We find projection of point onto the line.
                          // It falls where t = [(point-head) . (tail-head)] / |tail-head|^2
                          // We clamp t from [0,1] to handle points outside the segment head--->tail.
                      
                          double t = max(0,min(1,(point-head).dot(tail-head)/l2));
                          Eigen::Vector3d projection = head + t*(tail-head);
                      
                          return (point - projection).norm();
                      }
                      

                      【讨论】:

                        【解决方案20】:

                        根据上面 Joshua 的回答,这是一个自包含的 Delphi / Pascal 版本的函数。对 VCL 屏幕图形使用 TPoint,但应该很容易根据需要进行调整。

                        function DistancePtToSegment( pt, pt1, pt2: TPoint): double;
                        var
                           a, b, c, d: double;
                           len_sq: double;
                           param: double;
                           xx, yy: double;
                           dx, dy: double;
                        begin
                           a := pt.x - pt1.x;
                           b := pt.y - pt1.y;
                           c := pt2.x - pt1.x;
                           d := pt2.y - pt1.y;
                        
                           len_sq := (c * c) + (d * d);
                           param := -1;
                        
                           if (len_sq <> 0) then
                           begin
                              param := ((a * c) + (b * d)) / len_sq;
                           end;
                        
                           if param < 0 then
                           begin
                              xx := pt1.x;
                              yy := pt1.y;
                           end
                           else if param > 1 then
                           begin
                              xx := pt2.x;
                              yy := pt2.y;
                           end
                           else begin
                              xx := pt1.x + param * c;
                              yy := pt1.y + param * d;
                           end;
                        
                           dx := pt.x - xx;
                           dy := pt.y - yy;
                           result := sqrt( (dx * dx) + (dy * dy))
                        end;
                        

                        【讨论】:

                          【解决方案21】:

                          Swift 实现http://paulbourke.net/geometry/pointlineplane/source.c

                              static func magnitude(p1: CGPoint, p2: CGPoint) -> CGFloat {
                                  let vector = CGPoint(x: p2.x - p1.x, y: p2.y - p1.y)
                                  return sqrt(pow(vector.x, 2) + pow(vector.y, 2))
                              }
                          
                              /// http://paulbourke.net/geometry/pointlineplane/
                              /// http://paulbourke.net/geometry/pointlineplane/source.c
                              static func pointDistanceToLine(point: CGPoint, lineStart: CGPoint, lineEnd: CGPoint) -> CGFloat? {
                          
                                  let lineMag = magnitude(p1: lineEnd, p2: lineStart)
                                  let u = (((point.x - lineStart.x) * (lineEnd.x - lineStart.x)) +
                                          ((point.y - lineStart.y) * (lineEnd.y - lineStart.y))) /
                                          (lineMag * lineMag)
                          
                                  if u < 0 || u > 1 {
                                      // closest point does not fall within the line segment
                                      return nil
                                  }
                          
                                  let intersectionX = lineStart.x + u * (lineEnd.x - lineStart.x)
                                  let intersectionY = lineStart.y + u * (lineEnd.y - lineStart.y)
                          
                                  return magnitude(p1: point, p2: CGPoint(x: intersectionX, y: intersectionY))
                              }
                          

                          【讨论】:

                            【解决方案22】:

                            Lua 解决方案

                            -- distance from point (px, py) to line segment (x1, y1, x2, y2)
                            function distPointToLine(px,py,x1,y1,x2,y2) -- point, start and end of the segment
                                local dx,dy = x2-x1,y2-y1
                                local length = math.sqrt(dx*dx+dy*dy)
                                dx,dy = dx/length,dy/length -- normalization
                                local p = dx*(px-x1)+dy*(py-y1)
                                if p < 0 then
                                    dx,dy = px-x1,py-y1
                                    return math.sqrt(dx*dx+dy*dy), x1, y1 -- distance, nearest point
                                elseif p > length then
                                    dx,dy = px-x2,py-y2
                                    return math.sqrt(dx*dx+dy*dy), x2, y2 -- distance, nearest point
                                end
                                return math.abs(dy*(px-x1)-dx*(py-y1)), x1+dx*p, y1+dy*p -- distance, nearest point
                            end
                            

                            对于折线(多于两段的线):

                            -- if the (poly-)line has several segments, just iterate through all of them:
                            function nearest_sector_in_line (x, y, line)
                                local x1, y1, x2, y2, min_dist
                                local ax,ay = line[1], line[2]
                                for j = 3, #line-1, 2 do
                                    local bx,by = line[j], line[j+1]
                                    local dist = distPointToLine(x,y,ax,ay,bx,by)
                                    if not min_dist or dist < min_dist then
                                        min_dist = dist
                                        x1, y1, x2, y2 = ax,ay,bx,by
                                    end
                                    ax, ay = bx, by
                                end
                                return x1, y1, x2, y2
                            end
                            

                            例子:

                            -- call it:
                            local x1, y1, x2, y2 = nearest_sector_in_line (7, 4, {0,0, 10,0, 10,10, 0,10})
                            

                            【讨论】:

                              【解决方案23】:

                              这是 HSQLDB 的 SQL 实现:

                              CREATE FUNCTION dist_to_segment(px double, py double, vx double, vy double, wx double, wy double)
                                RETURNS double
                              BEGIN atomic
                                 declare l2 double;
                                 declare t double;
                                 declare nx double;
                                 declare ny double;
                                 set l2 =(vx - wx)*(vx - wx) + (vy - wy)*(vy - wy);
                                 IF l2 = 0 THEN
                                   RETURN sqrt((vx - px)*(vx - px) + (vy - py)*(vy - py));
                                 ELSE
                                   set t = ((px - vx) * (wx - vx) + (py - vy) * (wy - vy)) / l2;
                                   set t = GREATEST(0, LEAST(1, t));
                                   set nx=vx + t * (wx - vx);
                                   set ny=vy + t * (wy - vy);
                                   RETURN sqrt((nx - px)*(nx - px) + (ny - py)*(ny - py));
                                 END IF;
                              END;
                              

                              Postgres 的实现:

                              CREATE FUNCTION dist_to_segment(px numeric, py numeric, vx numeric, vy numeric, wx numeric, wy numeric)
                                RETURNS numeric
                              AS $$
                                 declare l2 numeric;
                                 declare t numeric;
                                 declare nx numeric;
                                 declare ny numeric;
                              BEGIN 
                                 l2 := (vx - wx)*(vx - wx) + (vy - wy)*(vy - wy);
                                 IF l2 = 0 THEN
                                   RETURN sqrt((vx - px)*(vx - px) + (vy - py)*(vy - py));
                                 ELSE
                                   t := ((px - vx) * (wx - vx) + (py - vy) * (wy - vy)) / l2;
                                   t := GREATEST(0, LEAST(1, t));
                                   nx := vx + t * (wx - vx);
                                   ny := vy + t * (wy - vy);
                                   RETURN sqrt((nx - px)*(nx - px) + (ny - py)*(ny - py));
                                 END IF;
                              END;
                              $$ LANGUAGE plpgsql;
                              

                              【讨论】:

                                【解决方案24】:

                                我需要一个 Godot (GDscript) 实现,所以我根据grumdrig's 接受的答案写了一个:

                                func minimum_distance(v: Vector2, w: Vector2, p: Vector2):
                                    # Return minimum distance between line segment vw and point p
                                    var l2: float = (v - w).length_squared()  # i.e. |w-v|^2 -  avoid a sqrt
                                    if l2 == 0.0:
                                        return p.distance_to(v) # v == w case
                                
                                    # 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
                                    # We clamp t from [0,1] to handle points outside the segment vw.
                                    var t: float = max(0, min(1, (p - v).dot(w - v) / l2))
                                    var projection: Vector2 = v + t * (w - v)  # Projection falls on the segment
                                    
                                    return p.distance_to(projection)
                                

                                【讨论】:

                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2015-01-25
                                  • 2021-11-05
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2011-02-18
                                  相关资源
                                  最近更新 更多