Khoảng cách ngắn nhất giữa một điểm và một đoạn thẳng


359

Tôi cần một hàm cơ bản để tìm khoảng cách ngắn nhất giữa một điểm và một đoạn thẳng. Hãy viết giải pháp bằng bất kỳ ngôn ngữ nào bạn muốn; Tôi có thể dịch nó thành những gì tôi đang sử dụng (Javascript).

EDIT: Đoạn đường của tôi được xác định bởi hai điểm cuối. Vì vậy, phân khúc dòng của tôi ABđược xác định bởi hai điểm A (x1,y1)B (x2,y2). Tôi đang cố gắng tìm khoảng cách giữa đoạn đường này và một điểm C (x3,y3). Kỹ năng hình học của tôi rất yếu, vì vậy những ví dụ tôi đã thấy rất khó hiểu, tôi rất tiếc phải thừa nhận.


Tôi không biết làm thế nào bạn đại diện cho các dòng và điểm, nhưng đây là tất cả các toán học bạn cần để bắt đầu. Không nên quá khó để tìm ra những gì bạn cần làm.
mandaleeka

4
@ArthurKalliokoski: liên kết đó đã chết, nhưng tôi đã tìm thấy một bản sao: paulbourke.net/geometry/pointline
Gunther Struyf

11
@GuntherStruyf: liên kết đó cũng đã chết, nhưng liên kết tương tự này hoạt động: paulbourke.net/geometry/pointlineplane
Michael

1
Nếu ai đó đang tìm khoảng cách giữa một điểm và một đường, không phải là một điểm và một đường SEGMENT, hãy kiểm tra liên kết này: gist.github.com/rhyolight/2846020
Nick Budden

1
Các liên kết ở trên là chết. Đây là mẫu mã giả và mã c ++, được giải thích và xuất phát chi tiết như sách giáo khoa, địa kỹ thuật.com / a02
Eric

Câu trả lời:


447

Eli, mã bạn đã giải quyết không chính xác. Một điểm gần đường mà phân khúc nằm nhưng cách xa một đầu của phân khúc sẽ được đánh giá không chính xác gần phân khúc. Cập nhật: Câu trả lời không chính xác được đề cập không còn là câu trả lời được chấp nhận.

Đây là một số mã chính xác, trong C ++. Nó giả định một vectơ 2D lớp class vec2 {float x,y;}, về cơ bản, với các toán tử để thêm, trừ, chia tỷ lệ, v.v., và một hàm sản phẩm khoảng cách và dấu chấm (nghĩa là x1 x2 + y1 y2).

float minimum_distance(vec2 v, vec2 w, vec2 p) {
  // Return minimum distance between line segment vw and point p
  const float l2 = length_squared(v, w);  // i.e. |w-v|^2 -  avoid a sqrt
  if (l2 == 0.0) return distance(p, 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.
  const float t = max(0, min(1, dot(p - v, w - v) / l2));
  const vec2 projection = v + t * (w - v);  // Projection falls on the segment
  return distance(p, projection);
}

EDIT: Tôi cần một triển khai Javascript, vì vậy đây là, không có phụ thuộc (hoặc nhận xét, nhưng đó là một cổng trực tiếp ở trên). Điểm được biểu diễn dưới dạng đối tượng xythuộc tính.

function sqr(x) { return x * x }
function dist2(v, w) { return sqr(v.x - w.x) + sqr(v.y - w.y) }
function distToSegmentSquared(p, v, w) {
  var l2 = dist2(v, w);
  if (l2 == 0) return dist2(p, v);
  var 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 dist2(p, { x: v.x + t * (w.x - v.x),
                    y: v.y + t * (w.y - v.y) });
}
function distToSegment(p, v, w) { return Math.sqrt(distToSegmentSquared(p, v, w)); }

EDIT 2: Tôi cần một phiên bản Java, nhưng quan trọng hơn, tôi cần nó ở dạng 3d thay vì 2d.

float dist_to_segment_squared(float px, float py, float pz, float lx1, float ly1, float lz1, float lx2, float ly2, float lz2) {
  float line_dist = dist_sq(lx1, ly1, lz1, lx2, ly2, lz2);
  if (line_dist == 0) return dist_sq(px, py, pz, lx1, ly1, lz1);
  float t = ((px - lx1) * (lx2 - lx1) + (py - ly1) * (ly2 - ly1) + (pz - lz1) * (lz2 - lz1)) / line_dist;
  t = constrain(t, 0, 1);
  return dist_sq(px, py, pz, lx1 + t * (lx2 - lx1), ly1 + t * (ly2 - ly1), lz1 + t * (lz2 - lz1));
}

1
Tôi đã thêm một phiên bản xác thực về điều này như một câu trả lời riêng biệt.
M Katz

4
Cảm ơn @Grumdrig, giải pháp javascript của bạn đã được phát hiện và tiết kiệm thời gian rất lớn. Tôi đã chuyển giải pháp của bạn sang Objective-C và thêm nó vào bên dưới.
awolf

1
Chúng tôi thực sự chỉ đang cố gắng tránh chia cho số 0 ở đó.
Grumdrig

9
Hình chiếu của điểm plên một đường là điểm trên đường gần nhất p. (Và một vuông góc với dòng ở chiếu sẽ đi qua p.) Số tlà bao xa dọc theo đoạn thẳng từ vđể wmà chiếu rơi. Vì vậy, nếu tlà 0, hình chiếu rơi đúng vào v; nếu là 1, nó sẽ bật w; nếu là 0,5, chẳng hạn, thì giữa chừng. Nếu tnhỏ hơn 0 hoặc lớn hơn 1, nó rơi vào dòng qua một đầu hoặc đầu kia của phân khúc. Trong trường hợp đó, khoảng cách đến đoạn sẽ là khoảng cách đến gần cuối.
Grumdrig

1
Rất tiếc - không nhận thấy ai đó đã cung cấp phiên bản 3D. @RogiSolorzano, trước tiên bạn cần chuyển đổi tọa độ lat, long thành tọa độ x, y, z trong không gian 3 chiều.
Grumdrig

111

Đây là mã hoàn chỉnh đơn giản nhất trong Javascript.

x, y là điểm đích của bạn và x1, y1 đến x2, y2 là đoạn đường của bạn.

CẬP NHẬT: khắc phục sự cố 0 dòng từ ý kiến.

function pDistance(x, y, x1, y1, x2, y2) {

  var A = x - x1;
  var B = y - y1;
  var C = x2 - x1;
  var D = y2 - y1;

  var dot = A * C + B * D;
  var len_sq = C * C + D * D;
  var param = -1;
  if (len_sq != 0) //in case of 0 length line
      param = dot / len_sq;

  var xx, yy;

  if (param < 0) {
    xx = x1;
    yy = y1;
  }
  else if (param > 1) {
    xx = x2;
    yy = y2;
  }
  else {
    xx = x1 + param * C;
    yy = y1 + param * D;
  }

  var dx = x - xx;
  var dy = y - yy;
  return Math.sqrt(dx * dx + dy * dy);
}

Hình ảnh giúp trực quan hóa giải pháp


8
Trong tất cả các mã tôi đã thấy để giải quyết vấn đề này, tôi thích mã này nhất. Nó rất rõ ràng và dễ đọc. Toán học đằng sau nó, là một chút thần bí. Ví dụ, sản phẩm chấm chia cho chiều dài bình phương thực sự đại diện cho điều gì?
dùng1815201

2
Sản phẩm chấm chia cho bình phương chiều dài cung cấp cho bạn khoảng cách chiếu từ (x1, y1). Đây là phần của dòng mà điểm (x, y) gần nhất. Lưu ý mệnh đề khác cuối cùng trong đó (xx, yy) được tính - đây là hình chiếu của điểm (x, y) lên đoạn (x1, y1) - (x2, y2).
Pickup Logan

4
Việc kiểm tra các phân đoạn dòng có độ dài 0 quá xa trong mã. 'len_sq' sẽ bằng 0 và mã sẽ chia cho 0 trước khi đến kiểm tra an toàn.
HostedMetrics.com

17
Đồng hồ đo. Nó được trả lại trong mét.
Joshua

1
@nevermind, hãy gọi điểm của chúng tôi là p0 và các điểm xác định đường thẳng là p1 và p2. Sau đó, bạn nhận được các vectơ A = p0 - p1 và B = p2 - p1. Tham số là giá trị vô hướng mà khi nhân với B sẽ cho bạn điểm trên đường gần nhất với p0. Nếu param <= 0, điểm gần nhất là p1. Nếu param> = 1, điểm gần nhất là p1. Nếu nó nằm trong khoảng từ 0 đến 1, nó nằm ở đâu đó giữa p1 và p2 để chúng ta nội suy. XX và YY sau đó là điểm gần nhất trên đoạn thẳng, dx / dy là vectơ từ p0 đến điểm đó và cuối cùng chúng ta trả về độ dài của vectơ đó.
Sean

70

Đây là một triển khai được thực hiện cho FINITE LINE SEGMENT, không phải là dòng vô hạn như hầu hết các chức năng khác ở đây dường như (đó là lý do tại sao tôi thực hiện điều này).

Thực hiện lý thuyết của Paul Bourke .

Con trăn

def dist(x1, y1, x2, y2, x3, y3): # x3,y3 is the point
    px = x2-x1
    py = y2-y1

    norm = px*px + py*py

    u =  ((x3 - x1) * px + (y3 - y1) * py) / float(norm)

    if u > 1:
        u = 1
    elif u < 0:
        u = 0

    x = x1 + u * px
    y = y1 + u * py

    dx = x - x3
    dy = y - y3

    # Note: If the actual distance does not matter,
    # if you only want to compare what this function
    # returns to other results of this function, you
    # can just return the squared distance instead
    # (i.e. remove the sqrt) to gain a little performance

    dist = (dx*dx + dy*dy)**.5

    return dist

AS3:

public static function segmentDistToPoint(segA:Point, segB:Point, p:Point):Number
{
    var p2:Point = new Point(segB.x - segA.x, segB.y - segA.y);
    var something:Number = p2.x*p2.x + p2.y*p2.y;
    var u:Number = ((p.x - segA.x) * p2.x + (p.y - segA.y) * p2.y) / something;

    if (u > 1)
        u = 1;
    else if (u < 0)
        u = 0;

    var x:Number = segA.x + u * p2.x;
    var y:Number = segA.y + u * p2.y;

    var dx:Number = x - p.x;
    var dy:Number = y - p.y;

    var dist:Number = Math.sqrt(dx*dx + dy*dy);

    return dist;
}

Java

private double shortestDistance(float x1,float y1,float x2,float y2,float x3,float y3)
    {
        float px=x2-x1;
        float py=y2-y1;
        float temp=(px*px)+(py*py);
        float u=((x3 - x1) * px + (y3 - y1) * py) / (temp);
        if(u>1){
            u=1;
        }
        else if(u<0){
            u=0;
        }
        float x = x1 + u * px;
        float y = y1 + u * py;

        float dx = x - x3;
        float dy = y - y3;
        double dist = Math.sqrt(dx*dx + dy*dy);
        return dist;

    }

2
Xin lỗi, nhưng tôi đã thử điều này và nó vẫn cho tôi kết quả như thể dòng đang kéo dài vô tận. Tôi đã tìm thấy câu trả lời của Grumdig để làm việc, mặc dù.
Frederik

1
Trong trường hợp đó, bạn đang sử dụng nó sai hoặc có nghĩa là một cái gì đó khác với vô hạn. Xem ví dụ về mã này tại đây: boomie.se/upload/Drawdebug.swf
quano

Trông giống như một lỗi trong mã hoặc một cái gì đó, tôi nhận được kết quả tương tự như Frederik /
Kromster

30
Sự lựa chọn tên biến khác xa (p2, một cái gì đó, u, ...)
MiguelSantirso

2
Tôi đã thử phiên bản Python của hàm và thấy rằng nó hiển thị kết quả không chính xác nếu các tham số là số nguyên. distAnother(0, 0, 4, 0, 2, 2)cho 2.8284271247461903 (không chính xác). distAnother(0., 0., 4., 0., 2., 2.)cho 2.0 (đúng). Xin lưu ý về điều này. Tôi nghĩ rằng mã có thể được cải thiện để có chuyển đổi float ở đâu đó.
Vladimir Obrizan

22

Trong chủ đề câu hỏi của riêng tôi, làm thế nào để tính khoảng cách 2D ngắn nhất giữa một điểm và một đoạn đường trong tất cả các trường hợp trong C, C # / .NET 2.0 hoặc Java? Tôi đã được yêu cầu đặt câu trả lời C # ở đây khi tôi tìm thấy câu trả lời: vì vậy đây là, được sửa đổi từ http://www.topcoder.com/tc?d1=tutorials&d2=geometry1&module=Static :

//Compute the dot product AB . BC
private double DotProduct(double[] pointA, double[] pointB, double[] pointC)
{
    double[] AB = new double[2];
    double[] BC = new double[2];
    AB[0] = pointB[0] - pointA[0];
    AB[1] = pointB[1] - pointA[1];
    BC[0] = pointC[0] - pointB[0];
    BC[1] = pointC[1] - pointB[1];
    double dot = AB[0] * BC[0] + AB[1] * BC[1];

    return dot;
}

//Compute the cross product AB x AC
private double CrossProduct(double[] pointA, double[] pointB, double[] pointC)
{
    double[] AB = new double[2];
    double[] AC = new double[2];
    AB[0] = pointB[0] - pointA[0];
    AB[1] = pointB[1] - pointA[1];
    AC[0] = pointC[0] - pointA[0];
    AC[1] = pointC[1] - pointA[1];
    double cross = AB[0] * AC[1] - AB[1] * AC[0];

    return cross;
}

//Compute the distance from A to B
double Distance(double[] pointA, double[] pointB)
{
    double d1 = pointA[0] - pointB[0];
    double d2 = pointA[1] - pointB[1];

    return Math.Sqrt(d1 * d1 + d2 * d2);
}

//Compute the distance from AB to C
//if isSegment is true, AB is a segment, not a line.
double LineToPointDistance2D(double[] pointA, double[] pointB, double[] pointC, 
    bool isSegment)
{
    double dist = CrossProduct(pointA, pointB, pointC) / Distance(pointA, pointB);
    if (isSegment)
    {
        double dot1 = DotProduct(pointA, pointB, pointC);
        if (dot1 > 0) 
            return Distance(pointB, pointC);

        double dot2 = DotProduct(pointB, pointA, pointC);
        if (dot2 > 0) 
            return Distance(pointA, pointC);
    }
    return Math.Abs(dist);
} 

Tôi không thể trả lời nhưng đặt câu hỏi vì vậy tôi hy vọng tôi không nhận được hàng triệu phiếu bầu vì một số lý do nhưng xây dựng nhà phê bình. Tôi chỉ muốn (và được khuyến khích) chia sẻ ý tưởng của người khác vì các giải pháp trong chủ đề này là với một số ngôn ngữ kỳ lạ (Fortran, Mathicala) hoặc bị ai đó gắn thẻ là lỗi. Cái duy nhất hữu ích (bởi Grumdrig) đối với tôi được viết bằng C ++ và không ai gắn thẻ nó bị lỗi. Nhưng nó thiếu các phương thức (dấu chấm, v.v.) được gọi.


1
Cảm ơn đã đăng bài này. Nhưng có vẻ như có một sự tối ưu hóa rõ ràng có thể có trong phương pháp cuối cùng: Đừng tính toán dist cho đến khi nó xác định rằng nó cần thiết.
RenniePet

2
Nhận xét trên Dot SẢNt cho biết đó là điện toán AB.AC, nhưng đó là điện toán AB.BC.
Metal450

Sản phẩm chéo theo định nghĩa trả về một vectơ nhưng trả về vô hướng ở đây.
SteakOverflow

21

Trong F #, khoảng cách từ điểm cđến đoạn thẳng giữa abđược cho bởi:

let pointToLineSegmentDistance (a: Vector, b: Vector) (c: Vector) =
  let d = b - a
  let s = d.Length
  let lambda = (c - a) * d / s
  let p = (lambda |> max 0.0 |> min s) * d / s
  (a + p - c).Length

Các vectơ dchỉ từ ađến bdọc theo đoạn đường. Sản phẩm chấm của d/svới c-acung cấp tham số của điểm tiếp cận gần nhất giữa đường vô hạn và điểm c. Hàm minmaxđược sử dụng để kẹp tham số này vào phạm vi 0..ssao cho điểm nằm giữa ab. Cuối cùng, độ dài a+p-clà khoảng cách từ cđến điểm gần nhất trên đoạn thẳng.

Ví dụ sử dụng:

pointToLineSegmentDistance (Vector(0.0, 0.0), Vector(1.0, 0.0)) (Vector(-1.0, 1.0))

1
Tôi nghĩ rằng dòng cuối cùng là không chính xác, và nên đọc:(a + p - c).Length
Blair Holloway

Điều đó vẫn không khắc phục hoàn toàn vấn đề. Một cách để sửa chữa chức năng sẽ xác định lại lambdapnhư let lambda = (c - a) * d / (s * s)let p = a + (lambda |> max 0.0 |> min 1.0) * d, tương ứng. Sau đó hàm trả về khoảng cách đúng ví dụ cho trường hợp a = (0,1), b = (1,0)c = (1,1).
mikkoma 3/2/2015

20

Đối với bất kỳ ai quan tâm, đây là một chuyển đổi tầm thường của mã Javascript của Joshua sang Objective-C:

- (double)distanceToPoint:(CGPoint)p fromLineSegmentBetween:(CGPoint)l1 and:(CGPoint)l2
{
    double A = p.x - l1.x;
    double B = p.y - l1.y;
    double C = l2.x - l1.x;
    double D = l2.y - l1.y;

    double dot = A * C + B * D;
    double len_sq = C * C + D * D;
    double param = dot / len_sq;

    double xx, yy;

    if (param < 0 || (l1.x == l2.x && l1.y == l2.y)) {
        xx = l1.x;
        yy = l1.y;
    }
    else if (param > 1) {
        xx = l2.x;
        yy = l2.y;
    }
    else {
        xx = l1.x + param * C;
        yy = l1.y + param * D;
    }

    double dx = p.x - xx;
    double dy = p.y - yy;

    return sqrtf(dx * dx + dy * dy);
}

Tôi cần giải pháp này để làm việc với MKMapPointvì vậy tôi sẽ chia sẻ nó trong trường hợp người khác cần nó. Chỉ cần một số thay đổi nhỏ và điều này sẽ trả về khoảng cách tính bằng mét:

- (double)distanceToPoint:(MKMapPoint)p fromLineSegmentBetween:(MKMapPoint)l1 and:(MKMapPoint)l2
{
    double A = p.x - l1.x;
    double B = p.y - l1.y;
    double C = l2.x - l1.x;
    double D = l2.y - l1.y;

    double dot = A * C + B * D;
    double len_sq = C * C + D * D;
    double param = dot / len_sq;

    double xx, yy;

    if (param < 0 || (l1.x == l2.x && l1.y == l2.y)) {
        xx = l1.x;
        yy = l1.y;
    }
    else if (param > 1) {
        xx = l2.x;
        yy = l2.y;
    }
    else {
        xx = l1.x + param * C;
        yy = l1.y + param * D;
    }

    return MKMetersBetweenMapPoints(p, MKMapPointMake(xx, yy));
}

Điều này dường như làm việc tốt cho tôi. Cảm ơn đã chuyển đổi.
Gregir

Điều đáng chú ý là (xx, yy) là vị trí của điểm gần nhất. Tôi đã chỉnh sửa một chút mã của bạn, để nó trả về cả điểm và khoảng cách, tên được cấu trúc lại để chúng mô tả ví dụ và cung cấp ví dụ tại: stackoverflow.com/a/28028023/849616 .
Vive

20

Trong toán học

Nó sử dụng một mô tả tham số của phân khúc và chiếu điểm vào dòng được xác định bởi phân khúc. Khi tham số đi từ 0 đến 1 trong phân khúc, nếu hình chiếu nằm ngoài giới hạn này, chúng tôi tính khoảng cách đến điểm tương ứng, thay vì đường thẳng bình thường đến đoạn.

Clear["Global`*"];
 distance[{start_, end_}, pt_] := 
   Module[{param},
   param = ((pt - start).(end - start))/Norm[end - start]^2; (*parameter. the "."
                                                       here means vector product*)

   Which[
    param < 0, EuclideanDistance[start, pt],                 (*If outside bounds*)
    param > 1, EuclideanDistance[end, pt],
    True, EuclideanDistance[pt, start + param (end - start)] (*Normal distance*)
    ]
   ];  

Kết quả âm mưu:

Plot3D[distance[{{0, 0}, {1, 0}}, {xp, yp}], {xp, -1, 2}, {yp, -1, 2}]

văn bản thay thế

Vẽ các điểm đó gần hơn khoảng cách cắt :

văn bản thay thế

Lô đất:

nhập mô tả hình ảnh ở đây


11

Này, tôi vừa mới viết cái này hôm qua. Đó là trong Actioncript 3.0, về cơ bản là Javascript, mặc dù bạn có thể không có cùng lớp Point.

//st = start of line segment
//b = the line segment (as in: st + b = end of line segment)
//pt = point to test
//Returns distance from point to line segment.  
//Note: nearest point on the segment to the test point is right there if we ever need it
public static function linePointDist( st:Point, b:Point, pt:Point ):Number
{
    var nearestPt:Point; //closest point on seqment to pt

    var keyDot:Number = dot( b, pt.subtract( st ) ); //key dot product
    var bLenSq:Number = dot( b, b ); //Segment length squared

    if( keyDot <= 0 )  //pt is "behind" st, use st
    {
        nearestPt = st  
    }
    else if( keyDot >= bLenSq ) //pt is "past" end of segment, use end (notice we are saving twin sqrts here cuz)
    {
        nearestPt = st.add(b);
    }
    else //pt is inside segment, reuse keyDot and bLenSq to get percent of seqment to move in to find closest point
    {
        var keyDotToPctOfB:Number = keyDot/bLenSq; //REM dot product comes squared
        var partOfB:Point = new Point( b.x * keyDotToPctOfB, b.y * keyDotToPctOfB );
        nearestPt = st.add(partOfB);
    }

    var dist:Number = (pt.subtract(nearestPt)).length;

    return dist;
}

Ngoài ra, có một cuộc thảo luận khá đầy đủ và dễ đọc về vấn đề ở đây: notejot.com


Cảm ơn - đây chính xác là loại mã tôi đang tìm kiếm. Tôi đã đăng câu trả lời của riêng mình bên dưới, vì tôi đã xoay sở để kết hợp một thứ gì đó hoạt động trong Javascript trình duyệt thời hiện tại, nhưng tôi đã đánh dấu câu trả lời của bạn là được chấp nhận vì nó đơn giản, được viết tốt, dễ hiểu, và được đánh giá cao.
Eli Courtwright

Đây không phải là thiếu phương pháp dấu chấm sao? Trong mọi trường hợp, thật dễ dàng để tính toán: vec1.x * vec2.x + vec1.y * vec2.y
quano

11

Đối với những người lười biếng, đây là cổng Objective-C của giải pháp @ Grumdrig của tôi ở trên:

CGFloat sqr(CGFloat x) { return x*x; }
CGFloat dist2(CGPoint v, CGPoint w) { return sqr(v.x - w.x) + sqr(v.y - w.y); }
CGFloat distanceToSegmentSquared(CGPoint p, CGPoint v, CGPoint w)
{
    CGFloat l2 = dist2(v, w);
    if (l2 == 0.0f) return dist2(p, v);

    CGFloat t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
    if (t < 0.0f) return dist2(p, v);
    if (t > 1.0f) return dist2(p, w);
    return dist2(p, CGPointMake(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)));
}
CGFloat distanceToSegment(CGPoint point, CGPoint segmentPointV, CGPoint segmentPointW)
{
    return sqrtf(distanceToSegmentSquared(point, segmentPointV, segmentPointW));
}

Tôi nhận được "nan" được trả lại từ dòng này. Bất cứ ý tưởng tại sao? (Nhân tiện, cảm ơn bạn đã gõ cái này lên Obj-C!) return dist2(p, CGPointMake(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)))
Gregir

sqrtf () là bình phương x, không nhận được căn bậc hai của nó
Senseful

@Senseful Không chắc ý bạn là gì. sqrtf là căn bậc hai. developer.apple.com/l
Library / mac / documentation / Darwin / Reference / Lỗi

@awolf: Hãy xem dòng mã đầu tiên ở trên. Nó định nghĩa phương thức sqrtf(x) = x*x.
Ý thức

@Senseful cảm ơn, nó đã được đặt tên sai thay vì thực hiện các hoạt động sai.
awolf

10

Không thể cưỡng lại việc mã hóa nó trong python :)

from math import sqrt, fabs
def pdis(a, b, c):
    t = b[0]-a[0], b[1]-a[1]           # Vector ab
    dd = sqrt(t[0]**2+t[1]**2)         # Length of ab
    t = t[0]/dd, t[1]/dd               # unit vector of ab
    n = -t[1], t[0]                    # normal unit vector to ab
    ac = c[0]-a[0], c[1]-a[1]          # vector ac
    return fabs(ac[0]*n[0]+ac[1]*n[1]) # Projection of ac to n (the minimum distance)

print pdis((1,1), (2,2), (2,0))        # Example (answer is 1.414)


Ditto cho fortran :)

real function pdis(a, b, c)
    real, dimension(0:1), intent(in) :: a, b, c
    real, dimension(0:1) :: t, n, ac
    real :: dd
    t = b - a                          ! Vector ab
    dd = sqrt(t(0)**2+t(1)**2)         ! Length of ab
    t = t/dd                           ! unit vector of ab
    n = (/-t(1), t(0)/)                ! normal unit vector to ab
    ac = c - a                         ! vector ac
    pdis = abs(ac(0)*n(0)+ac(1)*n(1))  ! Projection of ac to n (the minimum distance)
end function pdis


program test
    print *, pdis((/1.0,1.0/), (/2.0,2.0/), (/2.0,0.0/))   ! Example (answer is 1.414)
end program test

10
Đây không phải là tính toán khoảng cách của một điểm đến một dòng thay vì phân khúc?
balint.miklos

6
Đây thực sự là khoảng cách đến dòng phân khúc, không phải phân khúc.
Grumdrig

12
Điều này dường như không hoạt động. Nếu bạn đã có một phân đoạn (0,0) và (5,0) và thử với điểm (7,0), nó sẽ trả về 0, điều đó không đúng. Khoảng cách nên là 2.
quano

8
Anh ta đã không xem xét trường hợp hình chiếu của điểm lên đoạn nằm ngoài khoảng từ A đến B. Đó có thể là điều người hỏi muốn, nhưng không phải là điều anh ta hỏi.
phkahler

5
Đây không phải là những gì đã được yêu cầu ban đầu.
Sambatyon

10

Đây là một cách viết đầy đủ hơn từ giải pháp của Grumdrig. Phiên bản này cũng trả về điểm gần nhất của chính nó.

#include "stdio.h"
#include "math.h"

class Vec2
{
public:
    float _x;
    float _y;

    Vec2()
    {
        _x = 0;
        _y = 0;
    }

    Vec2( const float x, const float y )
    {
        _x = x;
        _y = y;
    }

    Vec2 operator+( const Vec2 &v ) const
    {
        return Vec2( this->_x + v._x, this->_y + v._y );
    }

    Vec2 operator-( const Vec2 &v ) const
    {
        return Vec2( this->_x - v._x, this->_y - v._y );
    }

    Vec2 operator*( const float f ) const
    {
        return Vec2( this->_x * f, this->_y * f );
    }

    float DistanceToSquared( const Vec2 p ) const
    {
        const float dX = p._x - this->_x;
        const float dY = p._y - this->_y;

        return dX * dX + dY * dY;
    }

    float DistanceTo( const Vec2 p ) const
    {
        return sqrt( this->DistanceToSquared( p ) );
    }

    float DotProduct( const Vec2 p ) const
    {
        return this->_x * p._x + this->_y * p._y;
    }
};

// return minimum distance between line segment vw and point p, and the closest point on the line segment, q
float DistanceFromLineSegmentToPoint( const Vec2 v, const Vec2 w, const Vec2 p, Vec2 * const q )
{
    const float distSq = v.DistanceToSquared( w ); // i.e. |w-v|^2 ... avoid a sqrt
    if ( distSq == 0.0 )
    {
        // v == w case
        (*q) = v;

        return v.DistanceTo( p );
    }

    // 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

    const float t = ( p - v ).DotProduct( w - v ) / distSq;
    if ( t < 0.0 )
    {
        // beyond the v end of the segment
        (*q) = v;

        return v.DistanceTo( p );
    }
    else if ( t > 1.0 )
    {
        // beyond the w end of the segment
        (*q) = w;

        return w.DistanceTo( p );
    }

    // projection falls on the segment
    const Vec2 projection = v + ( ( w - v ) * t );

    (*q) = projection;

    return p.DistanceTo( projection );
}

float DistanceFromLineSegmentToPoint( float segmentX1, float segmentY1, float segmentX2, float segmentY2, float pX, float pY, float *qX, float *qY )
{
    Vec2 q;

    float distance = DistanceFromLineSegmentToPoint( Vec2( segmentX1, segmentY1 ), Vec2( segmentX2, segmentY2 ), Vec2( pX, pY ), &q );

    (*qX) = q._x;
    (*qY) = q._y;

    return distance;
}

void TestDistanceFromLineSegmentToPoint( float segmentX1, float segmentY1, float segmentX2, float segmentY2, float pX, float pY )
{
    float qX;
    float qY;
    float d = DistanceFromLineSegmentToPoint( segmentX1, segmentY1, segmentX2, segmentY2, pX, pY, &qX, &qY );
    printf( "line segment = ( ( %f, %f ), ( %f, %f ) ), p = ( %f, %f ), distance = %f, q = ( %f, %f )\n",
            segmentX1, segmentY1, segmentX2, segmentY2, pX, pY, d, qX, qY );
}

void TestDistanceFromLineSegmentToPoint()
{
    TestDistanceFromLineSegmentToPoint( 0, 0, 1, 1, 1, 0 );
    TestDistanceFromLineSegmentToPoint( 0, 0, 20, 10, 5, 4 );
    TestDistanceFromLineSegmentToPoint( 0, 0, 20, 10, 30, 15 );
    TestDistanceFromLineSegmentToPoint( 0, 0, 20, 10, -30, 15 );
    TestDistanceFromLineSegmentToPoint( 0, 0, 10, 0, 5, 1 );
    TestDistanceFromLineSegmentToPoint( 0, 0, 0, 10, 1, 5 );
}

Cảm ơn đã đăng bài này. Cấu trúc rất tốt và nhận xét và định dạng - gần như khiến tôi quên mất mình không thích C ++ đến mức nào. Tôi đã sử dụng điều này để tạo một phiên bản C # tương ứng, mà tôi đã đăng ở đây.
RenniePet

10

Giải pháp một dòng sử dụng arctangents:

Ý tưởng là di chuyển A đến (0, 0) và xoay tam giác theo chiều kim đồng hồ để làm C nằm trên trục X, khi điều này xảy ra, By sẽ là khoảng cách.

  1. một góc = Atan (Cy - Ay, Cx - Axe);
  2. b góc = Atan (By - Ay, Bx - Axe);
  3. Độ dài AB = Sqrt ((Bx - Axe) ^ 2 + (Bởi - Ay) ^ 2)
  4. By = Sin (bAngle - aAngle) * ABLpm

C #

public double Distance(Point a, Point b, Point c)
{
    // normalize points
    Point cn = new Point(c.X - a.X, c.Y - a.Y);
    Point bn = new Point(b.X - a.X, b.Y - a.Y);

    double angle = Math.Atan2(bn.Y, bn.X) - Math.Atan2(cn.Y, cn.X);
    double abLength = Math.Sqrt(bn.X*bn.X + bn.Y*bn.Y);

    return Math.Sin(angle)*abLength;
}

Một dòng C # (sẽ được chuyển đổi thành SQL)

double distance = Math.Sin(Math.Atan2(b.Y - a.Y, b.X - a.X) - Math.Atan2(c.Y - a.Y, c.X - a.X)) * Math.Sqrt((b.X - a.X) * (b.X - a.X) + (b.Y - a.Y) * (b.Y - a.Y))

7

Xem xét sửa đổi này để trả lời của Grumdrig ở trên. Nhiều lần bạn sẽ thấy rằng sự thiếu chính xác có thể gây ra vấn đề. Tôi đang sử dụng gấp đôi trong phiên bản bên dưới, nhưng bạn có thể dễ dàng thay đổi thành phao. Phần quan trọng là nó sử dụng một epsilon để xử lý "slop". Ngoài ra, bạn sẽ nhiều lần muốn biết nơi giao nhau xảy ra, hoặc nếu nó xảy ra. Nếu t trả về là <0,0 hoặc> 1,0, không xảy ra va chạm. Tuy nhiên, ngay cả khi không có va chạm xảy ra, nhiều lần bạn sẽ muốn biết điểm gần nhất trên đoạn tới P là gì và do đó tôi sử dụng qx và qy để trả về vị trí này.

double PointSegmentDistanceSquared( double px, double py,
                                    double p1x, double p1y,
                                    double p2x, double p2y,
                                    double& t,
                                    double& qx, double& qy)
{
    static const double kMinSegmentLenSquared = 0.00000001;  // adjust to suit.  If you use float, you'll probably want something like 0.000001f
    static const double kEpsilon = 1.0E-14;  // adjust to suit.  If you use floats, you'll probably want something like 1E-7f
    double dx = p2x - p1x;
    double dy = p2y - p1y;
    double dp1x = px - p1x;
    double dp1y = py - p1y;
    const double segLenSquared = (dx * dx) + (dy * dy);
    if (segLenSquared >= -kMinSegmentLenSquared && segLenSquared <= kMinSegmentLenSquared)
    {
        // segment is a point.
        qx = p1x;
        qy = p1y;
        t = 0.0;
        return ((dp1x * dp1x) + (dp1y * dp1y));
    }
    else
    {
        // Project a line from p to the segment [p1,p2].  By considering the line
        // extending the segment, parameterized as p1 + (t * (p2 - p1)),
        // we find projection of point p onto the line. 
        // It falls where t = [(p - p1) . (p2 - p1)] / |p2 - p1|^2
        t = ((dp1x * dx) + (dp1y * dy)) / segLenSquared;
        if (t < kEpsilon)
        {
            // intersects at or to the "left" of first segment vertex (p1x, p1y).  If t is approximately 0.0, then
            // intersection is at p1.  If t is less than that, then there is no intersection (i.e. p is not within
            // the 'bounds' of the segment)
            if (t > -kEpsilon)
            {
                // intersects at 1st segment vertex
                t = 0.0;
            }
            // set our 'intersection' point to p1.
            qx = p1x;
            qy = p1y;
            // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
            // we were doing PointLineDistanceSquared, then qx would be (p1x + (t * dx)) and qy would be (p1y + (t * dy)).
        }
        else if (t > (1.0 - kEpsilon))
        {
            // intersects at or to the "right" of second segment vertex (p2x, p2y).  If t is approximately 1.0, then
            // intersection is at p2.  If t is greater than that, then there is no intersection (i.e. p is not within
            // the 'bounds' of the segment)
            if (t < (1.0 + kEpsilon))
            {
                // intersects at 2nd segment vertex
                t = 1.0;
            }
            // set our 'intersection' point to p2.
            qx = p2x;
            qy = p2y;
            // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
            // we were doing PointLineDistanceSquared, then qx would be (p1x + (t * dx)) and qy would be (p1y + (t * dy)).
        }
        else
        {
            // The projection of the point to the point on the segment that is perpendicular succeeded and the point
            // is 'within' the bounds of the segment.  Set the intersection point as that projected point.
            qx = p1x + (t * dx);
            qy = p1y + (t * dy);
        }
        // return the squared distance from p to the intersection point.  Note that we return the squared distance
        // as an optimization because many times you just need to compare relative distances and the squared values
        // works fine for that.  If you want the ACTUAL distance, just take the square root of this value.
        double dpqx = px - qx;
        double dpqy = py - qy;
        return ((dpqx * dpqx) + (dpqy * dpqy));
    }
}

6

Tôi giả sử bạn muốn tìm ngắn nhấtkhoảng cách giữa điểm và đoạn thẳng; Để làm điều này, bạn cần tìm dòng (lineA) vuông góc với đoạn thẳng của bạn (lineB) đi qua điểm của bạn, xác định giao điểm giữa dòng đó (lineA) và dòng của bạn đi qua đoạn thẳng của bạn (lineB) ; nếu điểm đó nằm giữa hai điểm của đoạn thẳng của bạn, thì khoảng cách là khoảng cách giữa điểm của bạn và điểm bạn vừa tìm thấy là giao điểm của đườngA và đườngB; nếu điểm không nằm giữa hai điểm của đoạn đường của bạn, bạn cần lấy khoảng cách giữa điểm của bạn và điểm gần hơn của hai đầu đoạn đường; điều này có thể được thực hiện dễ dàng bằng cách lấy khoảng cách vuông (để tránh căn bậc hai) giữa điểm và hai điểm của đoạn thẳng; cái nào gần hơn, lấy căn bậc hai của cái đó.


6

Việc triển khai C ++ / JavaScript của Grumdrig rất hữu ích với tôi, vì vậy tôi đã cung cấp một cổng trực tiếp Python mà tôi đang sử dụng. Mã hoàn chỉnh ở đây .

class Point(object):
  def __init__(self, x, y):
    self.x = float(x)
    self.y = float(y)

def square(x):
  return x * x

def distance_squared(v, w):
  return square(v.x - w.x) + square(v.y - w.y)

def distance_point_segment_squared(p, v, w):
  # Segment length squared, |w-v|^2
  d2 = distance_squared(v, w) 
  if d2 == 0: 
    # v == w, return distance to v
    return distance_squared(p, v)
  # 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 = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / d2;
  if t < 0:
    # Beyond v end of the segment
    return distance_squared(p, v)
  elif t > 1.0:
    # Beyond w end of the segment
    return distance_squared(p, w)
  else:
    # Projection falls on the segment.
    proj = Point(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y))
    # print proj.x, proj.y
    return distance_squared(p, proj)

5

Mã Matlab, với "tự kiểm tra" tích hợp nếu họ gọi hàm không có đối số:

function r = distPointToLineSegment( xy0, xy1, xyP )
% r = distPointToLineSegment( xy0, xy1, xyP )

if( nargin < 3 )
    selfTest();
    r=0;
else
    vx = xy0(1)-xyP(1);
    vy = xy0(2)-xyP(2);
    ux = xy1(1)-xy0(1);
    uy = xy1(2)-xy0(2);
    lenSqr= (ux*ux+uy*uy);
    detP= -vx*ux + -vy*uy;

    if( detP < 0 )
        r = norm(xy0-xyP,2);
    elseif( detP > lenSqr )
        r = norm(xy1-xyP,2);
    else
        r = abs(ux*vy-uy*vx)/sqrt(lenSqr);
    end
end


    function selfTest()
        %#ok<*NASGU>
        disp(['invalid args, distPointToLineSegment running (recursive)  self-test...']);

        ptA = [1;1]; ptB = [-1;-1];
        ptC = [1/2;1/2];  % on the line
        ptD = [-2;-1.5];  % too far from line segment
        ptE = [1/2;0];    % should be same as perpendicular distance to line
        ptF = [1.5;1.5];      % along the A-B but outside of the segment

        distCtoAB = distPointToLineSegment(ptA,ptB,ptC)
        distDtoAB = distPointToLineSegment(ptA,ptB,ptD)
        distEtoAB = distPointToLineSegment(ptA,ptB,ptE)
        distFtoAB = distPointToLineSegment(ptA,ptB,ptF)
        figure(1); clf;
        circle = @(x, y, r, c) rectangle('Position', [x-r, y-r, 2*r, 2*r], ...
            'Curvature', [1 1], 'EdgeColor', c);
        plot([ptA(1) ptB(1)],[ptA(2) ptB(2)],'r-x'); hold on;
        plot(ptC(1),ptC(2),'b+'); circle(ptC(1),ptC(2), 0.5e-1, 'b');
        plot(ptD(1),ptD(2),'g+'); circle(ptD(1),ptD(2), distDtoAB, 'g');
        plot(ptE(1),ptE(2),'k+'); circle(ptE(1),ptE(2), distEtoAB, 'k');
        plot(ptF(1),ptF(2),'m+'); circle(ptF(1),ptF(2), distFtoAB, 'm');
        hold off;
        axis([-3 3 -3 3]); axis equal;
    end

end

Cảm ơn, mã Matlab này thực sự tính toán khoảng cách ngắn nhất đến dòng SEGMENT và không phải khoảng cách đến dòng vô hạn mà đoạn nằm trên đó.
Rudolf Meijering

4

Và bây giờ giải pháp của tôi cũng vậy ...... (Javascript)

Nó rất nhanh vì tôi cố gắng tránh mọi hàm Math.pow.

Như bạn có thể thấy, ở cuối hàm tôi có khoảng cách của dòng.

mã được lấy từ lib http://www.draw2d.org/graphiti/jsdoc/#!/example

/**
 * Static util function to determine is a point(px,py) on the line(x1,y1,x2,y2)
 * A simple hit test.
 * 
 * @return {boolean}
 * @static
 * @private
 * @param {Number} coronaWidth the accepted corona for the hit test
 * @param {Number} X1 x coordinate of the start point of the line
 * @param {Number} Y1 y coordinate of the start point of the line
 * @param {Number} X2 x coordinate of the end point of the line
 * @param {Number} Y2 y coordinate of the end point of the line
 * @param {Number} px x coordinate of the point to test
 * @param {Number} py y coordinate of the point to test
 **/
graphiti.shape.basic.Line.hit= function( coronaWidth, X1, Y1,  X2,  Y2, px, py)
{
  // Adjust vectors relative to X1,Y1
  // X2,Y2 becomes relative vector from X1,Y1 to end of segment
  X2 -= X1;
  Y2 -= Y1;
  // px,py becomes relative vector from X1,Y1 to test point
  px -= X1;
  py -= Y1;
  var dotprod = px * X2 + py * Y2;
  var projlenSq;
  if (dotprod <= 0.0) {
      // px,py is on the side of X1,Y1 away from X2,Y2
      // distance to segment is length of px,py vector
      // "length of its (clipped) projection" is now 0.0
      projlenSq = 0.0;
  } else {
      // switch to backwards vectors relative to X2,Y2
      // X2,Y2 are already the negative of X1,Y1=>X2,Y2
      // to get px,py to be the negative of px,py=>X2,Y2
      // the dot product of two negated vectors is the same
      // as the dot product of the two normal vectors
      px = X2 - px;
      py = Y2 - py;
      dotprod = px * X2 + py * Y2;
      if (dotprod <= 0.0) {
          // px,py is on the side of X2,Y2 away from X1,Y1
          // distance to segment is length of (backwards) px,py vector
          // "length of its (clipped) projection" is now 0.0
          projlenSq = 0.0;
      } else {
          // px,py is between X1,Y1 and X2,Y2
          // dotprod is the length of the px,py vector
          // projected on the X2,Y2=>X1,Y1 vector times the
          // length of the X2,Y2=>X1,Y1 vector
          projlenSq = dotprod * dotprod / (X2 * X2 + Y2 * Y2);
      }
  }
    // Distance to line is now the length of the relative point
    // vector minus the length of its projection onto the line
    // (which is zero if the projection falls outside the range
    //  of the line segment).
    var lenSq = px * px + py * py - projlenSq;
    if (lenSq < 0) {
        lenSq = 0;
    }
    return Math.sqrt(lenSq)<coronaWidth;
};

4

được mã hóa trong t-sql

điểm là (@px, @py) và đoạn đường chạy từ (@ax, @ay) đến (@bx, @by)

create function fn_sqr (@NumberToSquare decimal(18,10)) 
returns decimal(18,10)
as 
begin
    declare @Result decimal(18,10)
    set @Result = @NumberToSquare * @NumberToSquare
    return @Result
end
go

create function fn_Distance(@ax decimal (18,10) , @ay decimal (18,10), @bx decimal(18,10),  @by decimal(18,10)) 
returns decimal(18,10)
as
begin
    declare @Result decimal(18,10)
    set @Result = (select dbo.fn_sqr(@ax - @bx) + dbo.fn_sqr(@ay - @by) )
    return @Result
end
go

create function fn_DistanceToSegmentSquared(@px decimal(18,10), @py decimal(18,10), @ax decimal(18,10), @ay decimal(18,10), @bx decimal(18,10), @by decimal(18,10)) 
returns decimal(18,10)
as 
begin
    declare @l2 decimal(18,10)
    set @l2 = (select dbo.fn_Distance(@ax, @ay, @bx, @by))
    if @l2 = 0
        return dbo.fn_Distance(@px, @py, @ax, @ay)
    declare @t decimal(18,10)
    set @t = ((@px - @ax) * (@bx - @ax) + (@py - @ay) * (@by - @ay)) / @l2
    if (@t < 0) 
        return dbo.fn_Distance(@px, @py, @ax, @ay);
    if (@t > 1) 
        return dbo.fn_Distance(@px, @py, @bx, @by);
    return dbo.fn_Distance(@px, @py,  @ax + @t * (@bx - @ax),  @ay + @t * (@by - @ay))
end
go

create function fn_DistanceToSegment(@px decimal(18,10), @py decimal(18,10), @ax decimal(18,10), @ay decimal(18,10), @bx decimal(18,10), @by decimal(18,10)) 
returns decimal(18,10)
as 
begin
    return sqrt(dbo.fn_DistanceToSegmentSquared(@px, @py , @ax , @ay , @bx , @by ))
end
go

--example execution for distance from a point at (6,1) to line segment that runs from (4,2) to (2,1)
select dbo.fn_DistanceToSegment(6, 1, 4, 2, 2, 1) 
--result = 2.2360679775

--example execution for distance from a point at (-3,-2) to line segment that runs from (0,-2) to (-2,1)
select dbo.fn_DistanceToSegment(-3, -2, 0, -2, -2, 1) 
--result = 2.4961508830

--example execution for distance from a point at (0,-2) to line segment that runs from (0,-2) to (-2,1)
select dbo.fn_DistanceToSegment(0,-2, 0, -2, -2, 1) 
--result = 0.0000000000

4

Có vẻ như mọi người khác trên StackOverflow đã đóng góp một câu trả lời (23 câu trả lời cho đến nay), vì vậy đây là đóng góp của tôi cho C #. Điều này chủ yếu dựa trên câu trả lời của M. Katz, lần lượt dựa trên câu trả lời của Grumdrig.

   public struct MyVector
   {
      private readonly double _x, _y;


      // Constructor
      public MyVector(double x, double y)
      {
         _x = x;
         _y = y;
      }


      // Distance from this point to another point, squared
      private double DistanceSquared(MyVector otherPoint)
      {
         double dx = otherPoint._x - this._x;
         double dy = otherPoint._y - this._y;
         return dx * dx + dy * dy;
      }


      // Find the distance from this point to a line segment (which is not the same as from this 
      //  point to anywhere on an infinite line). Also returns the closest point.
      public double DistanceToLineSegment(MyVector lineSegmentPoint1, MyVector lineSegmentPoint2,
                                          out MyVector closestPoint)
      {
         return Math.Sqrt(DistanceToLineSegmentSquared(lineSegmentPoint1, lineSegmentPoint2, 
                          out closestPoint));
      }


      // Same as above, but avoid using Sqrt(), saves a new nanoseconds in cases where you only want 
      //  to compare several distances to find the smallest or largest, but don't need the distance
      public double DistanceToLineSegmentSquared(MyVector lineSegmentPoint1, 
                                              MyVector lineSegmentPoint2, out MyVector closestPoint)
      {
         // Compute length of line segment (squared) and handle special case of coincident points
         double segmentLengthSquared = lineSegmentPoint1.DistanceSquared(lineSegmentPoint2);
         if (segmentLengthSquared < 1E-7f)  // Arbitrary "close enough for government work" value
         {
            closestPoint = lineSegmentPoint1;
            return this.DistanceSquared(closestPoint);
         }

         // Use the magic formula to compute the "projection" of this point on the infinite line
         MyVector lineSegment = lineSegmentPoint2 - lineSegmentPoint1;
         double t = (this - lineSegmentPoint1).DotProduct(lineSegment) / segmentLengthSquared;

         // Handle the two cases where the projection is not on the line segment, and the case where 
         //  the projection is on the segment
         if (t <= 0)
            closestPoint = lineSegmentPoint1;
         else if (t >= 1)
            closestPoint = lineSegmentPoint2;
         else 
            closestPoint = lineSegmentPoint1 + (lineSegment * t);
         return this.DistanceSquared(closestPoint);
      }


      public double DotProduct(MyVector otherVector)
      {
         return this._x * otherVector._x + this._y * otherVector._y;
      }

      public static MyVector operator +(MyVector leftVector, MyVector rightVector)
      {
         return new MyVector(leftVector._x + rightVector._x, leftVector._y + rightVector._y);
      }

      public static MyVector operator -(MyVector leftVector, MyVector rightVector)
      {
         return new MyVector(leftVector._x - rightVector._x, leftVector._y - rightVector._y);
      }

      public static MyVector operator *(MyVector aVector, double aScalar)
      {
         return new MyVector(aVector._x * aScalar, aVector._y * aScalar);
      }

      // Added using ReSharper due to CodeAnalysis nagging

      public bool Equals(MyVector other)
      {
         return _x.Equals(other._x) && _y.Equals(other._y);
      }

      public override bool Equals(object obj)
      {
         if (ReferenceEquals(null, obj)) return false;
         return obj is MyVector && Equals((MyVector) obj);
      }

      public override int GetHashCode()
      {
         unchecked
         {
            return (_x.GetHashCode()*397) ^ _y.GetHashCode();
         }
      }

      public static bool operator ==(MyVector left, MyVector right)
      {
         return left.Equals(right);
      }

      public static bool operator !=(MyVector left, MyVector right)
      {
         return !left.Equals(right);
      }
   }

Và đây là một chương trình thử nghiệm nhỏ.

   public static class JustTesting
   {
      public static void Main()
      {
         Stopwatch stopwatch = new Stopwatch();
         stopwatch.Start();

         for (int i = 0; i < 10000000; i++)
         {
            TestIt(1, 0, 0, 0, 1, 1, 0.70710678118654757);
            TestIt(5, 4, 0, 0, 20, 10, 1.3416407864998738);
            TestIt(30, 15, 0, 0, 20, 10, 11.180339887498949);
            TestIt(-30, 15, 0, 0, 20, 10, 33.541019662496844);
            TestIt(5, 1, 0, 0, 10, 0, 1.0);
            TestIt(1, 5, 0, 0, 0, 10, 1.0);
         }

         stopwatch.Stop();
         TimeSpan timeSpan = stopwatch.Elapsed;
      }


      private static void TestIt(float aPointX, float aPointY, 
                                 float lineSegmentPoint1X, float lineSegmentPoint1Y, 
                                 float lineSegmentPoint2X, float lineSegmentPoint2Y, 
                                 double expectedAnswer)
      {
         // Katz
         double d1 = DistanceFromPointToLineSegment(new MyVector(aPointX, aPointY), 
                                              new MyVector(lineSegmentPoint1X, lineSegmentPoint1Y), 
                                              new MyVector(lineSegmentPoint2X, lineSegmentPoint2Y));
         Debug.Assert(d1 == expectedAnswer);

         /*
         // Katz using squared distance
         double d2 = DistanceFromPointToLineSegmentSquared(new MyVector(aPointX, aPointY), 
                                              new MyVector(lineSegmentPoint1X, lineSegmentPoint1Y), 
                                              new MyVector(lineSegmentPoint2X, lineSegmentPoint2Y));
         Debug.Assert(Math.Abs(d2 - expectedAnswer * expectedAnswer) < 1E-7f);
          */

         /*
         // Matti (optimized)
         double d3 = FloatVector.DistanceToLineSegment(new PointF(aPointX, aPointY), 
                                                new PointF(lineSegmentPoint1X, lineSegmentPoint1Y), 
                                                new PointF(lineSegmentPoint2X, lineSegmentPoint2Y));
         Debug.Assert(Math.Abs(d3 - expectedAnswer) < 1E-7f);
          */
      }

      private static double DistanceFromPointToLineSegment(MyVector aPoint, 
                                             MyVector lineSegmentPoint1, MyVector lineSegmentPoint2)
      {
         MyVector closestPoint;  // Not used
         return aPoint.DistanceToLineSegment(lineSegmentPoint1, lineSegmentPoint2, 
                                             out closestPoint);
      }

      private static double DistanceFromPointToLineSegmentSquared(MyVector aPoint, 
                                             MyVector lineSegmentPoint1, MyVector lineSegmentPoint2)
      {
         MyVector closestPoint;  // Not used
         return aPoint.DistanceToLineSegmentSquared(lineSegmentPoint1, lineSegmentPoint2, 
                                                    out closestPoint);
      }
   }

Như bạn có thể thấy, tôi đã cố gắng đo lường sự khác biệt giữa việc sử dụng phiên bản tránh phương thức Sqrt () và phiên bản bình thường. Các thử nghiệm của tôi cho thấy bạn có thể tiết kiệm khoảng 2,5%, nhưng tôi thậm chí không chắc chắn về điều đó - các biến thể trong các lần chạy thử khác nhau có cùng độ lớn. Tôi cũng đã thử đo phiên bản được đăng bởi Matti (cộng với tối ưu hóa rõ ràng) và phiên bản đó dường như chậm hơn khoảng 4% so với phiên bản dựa trên mã Katz / Grumdrig.

Chỉnh sửa: Ngẫu nhiên, tôi cũng đã thử đo một phương pháp tìm khoảng cách đến một dòng vô hạn (không phải là một phân đoạn dòng) bằng cách sử dụng một sản phẩm chéo (và Sqrt ()), và nó nhanh hơn khoảng 32%.


3

Đây là phiên bản C ++ của devnullicus được chuyển đổi thành C #. Để thực hiện tôi cần biết điểm giao nhau và tìm giải pháp của mình để hoạt động tốt.

public static bool PointSegmentDistanceSquared(PointF point, PointF lineStart, PointF lineEnd, out double distance, out PointF intersectPoint)
{
    const double kMinSegmentLenSquared = 0.00000001; // adjust to suit.  If you use float, you'll probably want something like 0.000001f
    const double kEpsilon = 1.0E-14; // adjust to suit.  If you use floats, you'll probably want something like 1E-7f
    double dX = lineEnd.X - lineStart.X;
    double dY = lineEnd.Y - lineStart.Y;
    double dp1X = point.X - lineStart.X;
    double dp1Y = point.Y - lineStart.Y;
    double segLenSquared = (dX * dX) + (dY * dY);
    double t = 0.0;

    if (segLenSquared >= -kMinSegmentLenSquared && segLenSquared <= kMinSegmentLenSquared)
    {
        // segment is a point.
        intersectPoint = lineStart;
        t = 0.0;
        distance = ((dp1X * dp1X) + (dp1Y * dp1Y));
    }
    else
    {
        // Project a line from p to the segment [p1,p2].  By considering the line
        // extending the segment, parameterized as p1 + (t * (p2 - p1)),
        // we find projection of point p onto the line. 
        // It falls where t = [(p - p1) . (p2 - p1)] / |p2 - p1|^2
        t = ((dp1X * dX) + (dp1Y * dY)) / segLenSquared;
        if (t < kEpsilon)
        {
            // intersects at or to the "left" of first segment vertex (lineStart.X, lineStart.Y).  If t is approximately 0.0, then
            // intersection is at p1.  If t is less than that, then there is no intersection (i.e. p is not within
            // the 'bounds' of the segment)
            if (t > -kEpsilon)
            {
                // intersects at 1st segment vertex
                t = 0.0;
            }
            // set our 'intersection' point to p1.
            intersectPoint = lineStart;
            // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
            // we were doing PointLineDistanceSquared, then intersectPoint.X would be (lineStart.X + (t * dx)) and intersectPoint.Y would be (lineStart.Y + (t * dy)).
        }
        else if (t > (1.0 - kEpsilon))
        {
            // intersects at or to the "right" of second segment vertex (lineEnd.X, lineEnd.Y).  If t is approximately 1.0, then
            // intersection is at p2.  If t is greater than that, then there is no intersection (i.e. p is not within
            // the 'bounds' of the segment)
            if (t < (1.0 + kEpsilon))
            {
                // intersects at 2nd segment vertex
                t = 1.0;
            }
            // set our 'intersection' point to p2.
            intersectPoint = lineEnd;
            // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
            // we were doing PointLineDistanceSquared, then intersectPoint.X would be (lineStart.X + (t * dx)) and intersectPoint.Y would be (lineStart.Y + (t * dy)).
        }
        else
        {
            // The projection of the point to the point on the segment that is perpendicular succeeded and the point
            // is 'within' the bounds of the segment.  Set the intersection point as that projected point.
            intersectPoint = new PointF((float)(lineStart.X + (t * dX)), (float)(lineStart.Y + (t * dY)));
        }
        // return the squared distance from p to the intersection point.  Note that we return the squared distance
        // as an optimization because many times you just need to compare relative distances and the squared values
        // works fine for that.  If you want the ACTUAL distance, just take the square root of this value.
        double dpqX = point.X - intersectPoint.X;
        double dpqY = point.Y - intersectPoint.Y;

        distance = ((dpqX * dpqX) + (dpqY * dpqY));
    }

    return true;
}

Hoạt động như một bùa mê !! Cứu tôi vô số giờ. Cám ơn rất nhiều!!
Steve Johnson

3

Ở đây nó đang sử dụng Swift

    /* Distance from a point (p1) to line l1 l2 */
func distanceFromPoint(p: CGPoint, toLineSegment l1: CGPoint, and l2: CGPoint) -> CGFloat {
    let A = p.x - l1.x
    let B = p.y - l1.y
    let C = l2.x - l1.x
    let D = l2.y - l1.y

    let dot = A * C + B * D
    let len_sq = C * C + D * D
    let param = dot / len_sq

    var xx, yy: CGFloat

    if param < 0 || (l1.x == l2.x && l1.y == l2.y) {
        xx = l1.x
        yy = l1.y
    } else if param > 1 {
        xx = l2.x
        yy = l2.y
    } else {
        xx = l1.x + param * C
        yy = l1.y + param * D
    }

    let dx = p.x - xx
    let dy = p.y - yy

    return sqrt(dx * dx + dy * dy)
}

3

C #

Chuyển thể từ @Grumdrig

public static double MinimumDistanceToLineSegment(this Point p,
    Line line)
{
    var v = line.StartPoint;
    var w = line.EndPoint;

    double lengthSquared = DistanceSquared(v, w);

    if (lengthSquared == 0.0)
        return Distance(p, v);

    double t = Math.Max(0, Math.Min(1, DotProduct(p - v, w - v) / lengthSquared));
    var projection = v + t * (w - v);

    return Distance(p, projection);
}

public static double Distance(Point a, Point b)
{
    return Math.Sqrt(DistanceSquared(a, b));
}

public static double DistanceSquared(Point a, Point b)
{
    var d = a - b;
    return DotProduct(d, d);
}

public static double DotProduct(Point a, Point b)
{
    return (a.X * b.X) + (a.Y * b.Y);
}

Đã thử mã này, dường như không hoạt động khá chính xác. Có vẻ để có được khoảng cách sai một số lần.
WDUK

3

Giải pháp 2D và 3D

Xem xét một sự thay đổi của cơ sở sao cho đoạn đường trở thành (0, 0, 0)-(d, 0, 0)và điểm (u, v, 0). Khoảng cách ngắn nhất xảy ra trong mặt phẳng đó và được cho bởi

    u ≤ 0 -> d(A, C)
0 ≤ u ≤ d -> |v|
d ≤ u     -> d(B, C)

(khoảng cách đến một trong các điểm cuối hoặc tới đường hỗ trợ, tùy thuộc vào hình chiếu tới đường thẳng. Vị trí khoảng cách đẳng cự được tạo bởi hai nửa đường tròn và hai đoạn đường.)

nhập mô tả hình ảnh ở đây

Trong biểu thức trên, d là độ dài của đoạn AB và u, v tương ứng là sản phẩm vô hướng và (mô đun của) sản phẩm chéo của AB / d (vectơ đơn vị theo hướng AB) và AC. Do đó,

AB.AC ≤ 0             -> |AC|
    0 ≤ AB.AC ≤ AB²   -> |ABxAC|/|AB|
          AB² ≤ AB.AC -> |BC|

2

xem hộp công cụ Matlab GEOMETRY trong trang web sau: http://people.sc.fsu.edu/~jburkardt/m_src/geometry/geometry.html

ctrl + f và gõ "phân đoạn" để tìm các hàm liên quan đến phân đoạn dòng. các chức năng "Seg_point_dist_2d.m" và "Seg_point_dist_3d.m" là những gì bạn cần.

Các mã GEOMETRY có sẵn trong phiên bản C và phiên bản C ++ và phiên bản FORTRAN77 và phiên bản FORTRAN90 và phiên bản MATLAB.


2

Phiên bản AutoHotkeys dựa trên Javascript của Joshua:

plDist(x, y, x1, y1, x2, y2) {
    A:= x - x1
    B:= y - y1
    C:= x2 - x1
    D:= y2 - y1

    dot:= A*C + B*D
    sqLen:= C*C + D*D
    param:= dot / sqLen

    if (param < 0 || ((x1 = x2) && (y1 = y2))) {
        xx:= x1
        yy:= y1
    } else if (param > 1) {
        xx:= x2
        yy:= y2
    } else {
        xx:= x1 + param*C
        yy:= y1 + param*D
    }

    dx:= x - xx
    dy:= y - yy

    return sqrt(dx*dx + dy*dy)
}

2

Không thấy triển khai Java ở đây, vì vậy tôi đã dịch hàm Javascript từ câu trả lời được chấp nhận sang mã 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;
    }
}

2

Phiên bản WPF:

public class LineSegment
{
    private readonly Vector _offset;
    private readonly Vector _vector;

    public LineSegment(Point start, Point end)
    {
        _offset = (Vector)start;
        _vector = (Vector)(end - _offset);
    }

    public double DistanceTo(Point pt)
    {
        var v = (Vector)pt - _offset;

        // first, find a projection point on the segment in parametric form (0..1)
        var p = (v * _vector) / _vector.LengthSquared;

        // and limit it so it lays inside the segment
        p = Math.Min(Math.Max(p, 0), 1);

        // now, find the distance from that point to our point
        return (_vector * p - v).Length;
    }
}

1

Đây là mã tôi đã kết thúc bằng văn bản. Mã này giả định rằng một điểm được xác định ở dạng {x:5, y:7}. Lưu ý rằng đây không phải là cách hiệu quả nhất tuyệt đối, nhưng đó là mã đơn giản và dễ hiểu nhất mà tôi có thể nghĩ ra.

// 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
Mã này có một lỗi. Một điểm gần đường mà phân khúc nằm, nhưng cách xa một đầu của phân khúc, sẽ bị đánh giá không chính xác là gần phân khúc.
Grumdrig

Thật thú vị, tôi sẽ xem xét điều này vào lần tới khi tôi làm việc với cơ sở mã này để xác nhận khẳng định của bạn. Cảm ơn vì tiền hỗ trợ.
Eli Courtwright

1

Các chức năng trên không hoạt động trên các đường thẳng đứng. Đây là một chức năng đang hoạt động tốt! Đường thẳng với các điểm p1, p2. và Checkpoint là 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;
}

Không trả lời câu hỏi. Điều này chỉ hoạt động cho các dòng (những cái kéo dài vô tận trong không gian) không phải là các phân đoạn dòng (có độ dài hữu hạn).
Trinidad

"Hàm trên" là một tham chiếu mơ hồ. (Kích thích tôi vì đôi khi câu trả lời này được hiển thị bên dưới câu trả lời của tôi.)
RenniePet

1

Đây là điều tương tự như câu trả lời C ++ nhưng được chuyển sang pascal. Thứ tự của tham số điểm đã thay đổi cho phù hợp với mã của tôi nhưng là điều tương tự.

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;

Có một số vấn đề với Câu trả lời này: Loại PointF không được khai báo (có thể đó là loại tiêu chuẩn trong một số triển khai Pascal). Đây có thể là một bản ghi x, y: double; kết thúc; 2. các chức năng Khoảng cách và MPower không được khai báo và không có lời giải thích về những gì chúng làm (chúng ta có thể đoán, vâng). 3. Phép chiếu biến được khai báo nhưng không bao giờ được sử dụng. Nhìn chung điều đó làm cho nó một câu trả lời khá nghèo.
dummzeuch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.