점과 선분 사이의 최단 거리


358

점과 선 세그먼트 사이의 최단 거리를 찾으려면 기본 기능이 필요합니다. 원하는 언어로 솔루션을 자유롭게 작성하십시오. (Javascript)를 사용중인 것으로 번역 할 수 있습니다.

편집 : 내 선 세그먼트는 두 개의 끝점으로 정의됩니다. 따라서 내 선분 AB은 두 점 A (x1,y1)과로 정의됩니다 B (x2,y2). 이 선분과 점 사이의 거리를 찾으려고합니다 C (x3,y3). 내 지오메트리 기술이 녹슨 것이므로 내가 본 예제가 혼란스러워서 죄송합니다.


선과 점을 어떻게 표현하는지 모르겠지만 여기 에 시작하는 데 필요한 모든 수학이 있습니다. 해야 할 일을 파악하기가 너무 어렵지 않아야합니다.
mandaleeka

4
@ArthurKalliokoski : 그 링크는 죽었지 만 사본을 찾았습니다 : paulbourke.net/geometry/pointline
Gunther Struyf

11
@ GuntherStruyf : 그 링크는 죽었지 만,이 비슷한 링크는 작동합니다 : paulbourke.net/geometry/pointlineplane
Michael

1
: 누군가는 점과 선이 아닌 점과 선분 사이의 거리를 찾고 있다면,이 링크를 확인 gist.github.com/rhyolight/2846020
닉 Budden

1
위의 링크가 죽었습니다. 다음은 교과서, geomalgorithms.com/a02-_lines.html
Eric

답변:


447

엘리, 정하신 코드가 잘못되었습니다. 선분이있는 선 근방이지만 선분의 한쪽 끝에서 멀리 떨어진 지점은 선분 근처에서 잘못 판단됩니다. 업데이트 : 언급 된 잘못된 답변은 더 이상 허용되는 답변이 아닙니다.

다음은 C ++에서 올바른 코드입니다. class vec2 {float x,y;}기본적으로 2D- 벡터 클래스를 가정합니다 . 기본적으로 연산자는 더하기, 빼기, 크기 조절 등의 거리와 내적과 함수 (예 :)를 갖습니다 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);
}

편집 : Javascript 구현이 필요했기 때문에 여기에 종속성 (또는 주석이 없지만 위의 직접 포트)이 없습니다. 포인트와 객체로 표현 x하고 y속성.

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

편집 2 : Java 버전이 필요했지만 더 중요하게는 2d 대신 3d로 필요했습니다.

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
나는 이것의 다듬어 진 버전을 별도의 답변으로 추가했습니다.
M Katz

4
@Grumdrig에게 감사합니다. 자바 스크립트 솔루션이 눈에 띄었고 시간을 크게 절약했습니다. 귀하의 솔루션을 Objective-C로 포팅하고 아래에 추가했습니다.
awolf

1
우리는 실제로 0으로 나누기를 피하려고합니다.
Grumdrig

9
p선 에 점 을 투영 하면 가장 가까운 선의 점이됩니다 p. (투사에서의 라인에 수직이 통과하게된다 p.) 수가 t얼마나 멀리 선분 내지 vw투영 내리는 것이다. 따라서 t0이면 투영이 바로 떨어집니다 v. 1이면 켜져 있습니다 w. 예를 들어 0.5이면 중간에 있습니다. 경우 t0보다 작거나 1보다 큰 것입니다 그것은 한쪽 끝을지나 선 또는 세그먼트의 다른에 떨어진다. 이 경우 세그먼트까지의 거리는 가장 가까운 쪽까지의 거리가됩니다.
Grumdrig 2016 년

1
죄송합니다. 누군가 3D 버전을 제공 한 것으로 보지 못했습니다. @RogiSolorzano, 위도, 긴 좌표를 먼저 3 칸의 x, y, z 좌표로 변환해야합니다.
Grumdrig

110

다음은 자바 스크립트에서 가장 간단한 코드입니다.

x, y는 목표 점이고 x1, y1에서 x2, y2는 선분입니다.

업데이트 : 주석에서 길이가 0 인 문제가 수정되었습니다.

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

솔루션을 시각화하는 데 도움이되는 이미지


8
이 문제를 해결하기 위해 본 모든 코드 중에서이 코드가 가장 좋습니다. 매우 명확하고 읽기 쉽습니다. 그 뒤에있는 수학은 약간 신비적입니다. 예를 들어 내적을 제곱 길이로 나눈 값은 실제로 무엇을 나타 냅니까?
user1815201

2
내적을 길이 제곱으로 나눈 값은 (x1, y1)에서 투사 거리를 제공합니다. 이것은 점 (x, y)이 가장 가까운 선의 일부입니다. (xx, yy)가 계산되는 마지막 else 절에 주목하십시오. 이것은 점 (x, y)가 세그먼트 (x1, y1)-(x2, y2)에 투영 된 것입니다.
Logan Pickup

4
코드에서 길이가 0 인 선분 검사가 너무 멀리 있습니다. 'len_sq'는 0이되고 안전 점검을 받기 전에 코드는 0으로 나눕니다.
HostedMetrics.com

17
미터. 미터 단위로 반환됩니다.
Joshua

1
@nmindmind, 우리의 포인트 p0와 라인을 p1과 p2로 정의하는 포인트를 호출합시다. 그런 다음 벡터 A = p0-p1 및 B = p2-p1을 얻습니다. Param은 B에 곱하면 p0에 가장 가까운 선의 점을 제공하는 스칼라 값입니다. param <= 0이면 가장 가까운 점은 p1입니다. param> = 1 인 경우 가장 가까운 점은 p1입니다. 0과 1 사이이면 p1과 p2 사이에 있으므로 보간합니다. XX와 YY는 선분에서 가장 가까운 점이고, dx / dy는 p0에서 그 점까지의 벡터이며 마지막으로 그 벡터의 길이를 반환합니다.
Sean

70

이것은 FINITE LINE SEGMENTS를 위해 구현 된 것으로, 대부분의 다른 함수처럼 무한한 라인이 아닙니다 (그래서 내가 만든 이유).

Paul Bourke의 이론 구현 .

파이썬 :

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

자바

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
죄송하지만이 방법을 시도했지만 여전히 선이 무한대로 확장되는 것처럼 결과를 제공합니다. 그래도 작동하는 Grumdig의 답변을 찾았습니다.
Frederik

1
이 경우 잘못 사용하거나 무한대가 아닌 다른 것을 의미합니다. 이 코드의 예를 여기에서보십시오 : boomie.se/upload/Drawdebug.swf
quano

코드 또는 뭔가 실수 것 같은데, 나는 같은 결과를 얻을 수 프레데릭 등 /
Kromster

30
변수 이름의 선택은 좋은에서 (P2, 뭔가, U, ...) 멀리
miguelSantirso

2
파이썬 버전의 함수를 시도했는데 매개 변수가 정수이면 잘못된 결과가 표시됩니다. distAnother(0, 0, 4, 0, 2, 2)2.8284271247461903 (잘못된)을 제공합니다. distAnother(0., 0., 4., 0., 2., 2.)2.0 (올바름)을 제공합니다. 이 점에 유의하십시오. 어딘가에 부동 소수점 변환을하도록 코드를 개선 할 수 있다고 생각합니다.
블라디미르 오브리 잔

22

내 자신의 질문 스레드 에서 C, C # / .NET 2.0 또는 Java의 모든 경우에 점과 선 세그먼트 사이의 가장 짧은 2D 거리를 계산하는 방법은 무엇입니까? 내가 하나 찾을 때 여기에 C #을 대답을 넣어 질문을 받았다 : 그래서 여기가에서 수정 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);
} 

나는 대답하지 않고 질문을하는 @SO이므로 어떤 이유로 백만 표를 얻지 않고 비판을하기를 바랍니다. 이 스레드의 솔루션이 이국적인 언어 (Fortran, Mathematica)를 사용하거나 누군가가 잘못 표시했기 때문에 다른 사람의 아이디어를 공유하고 싶었습니다. 나를 위해 유일하게 유용한 것은 (Grumdrig) C ++로 작성되었으며 아무도 태그를 잘못 붙였습니다. 그러나 호출 된 메소드 (도트 등)가 없습니다.


1
이것을 게시 해 주셔서 감사합니다. 그러나 마지막 방법에서 가능한 최적화가있는 것처럼 보입니다. 필요하다고 판단 될 때까지 dist를 계산하지 마십시오.
RenniePet

2
DotProduct에 대한 의견은 AB.AC 컴퓨팅이지만 AB.BC 컴퓨팅이라고 말합니다.
Metal450

정의에 의한 교차 곱은 벡터를 반환하지만 스칼라를 반환합니다.
SteakOverflow

21

F #에서 점 과 c선분 사이 의 거리는 다음 ab같습니다.

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

벡터 d에서 점 a으로 b선분을 따라. d/swith 의 내적 c-a은 무한 선과 점 사이의 가장 가까운 접근 점의 매개 변수 를 제공합니다 c. minmax기능의 범위에이 매개 변수를 고정하는데 사용되는 0..s포인트와 거짓말되도록 a하고 b. 마지막으로 길이는 선분에서 가장 가까운 점 a+p-c까지의 거리입니다 c.

사용 예 :

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

1
마지막 줄이 잘못되었다고 생각하고 읽어야합니다.(a + p - c).Length
Blair Holloway

그래도 문제가 완전히 해결되지는 않습니다. 함수를 해결하는 한 가지 방법은 재정의하는 것 lambdap같은 let lambda = (c - a) * d / (s * s)let p = a + (lambda |> max 0.0 |> min 1.0) * d각각. 함수가 경우 경우에 대한 정확한 거리 등을 반환 한 후 a = (0,1), b = (1,0)하고 c = (1,1).
mikkoma

20

관심있는 사람은 Joshua의 Javascript 코드를 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);
}

이 솔루션을 사용하려면 MKMapPoint다른 사람이 필요로 할 때 공유 할 것입니다. 약간의 변화 만 있으면 거리가 미터 단위로 반환됩니다.

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

이것은 나에게 잘 작동하는 것 같습니다. 변환 해 주셔서 감사합니다.
Gregir

(xx, yy)가 가장 가까운 위치라는 점에 주목할 가치가 있습니다. 코드를 약간 편집 했으므로 점과 거리, 리팩토링 된 이름을 모두 반환하므로 무엇을 제공하고 무엇을 제공하는지 설명합니다 : stackoverflow.com/a/28028023/849616 .
Vive

20

Mathematica에서

세그먼트에 대한 파라 메트릭 설명을 사용하고 세그먼트에 의해 정의 된 선에 점을 투영합니다. 세그먼트에서 매개 변수가 0에서 1로 이동함에 따라 투영이이 범위를 벗어나면 세그먼트에 수직 인 직선 대신 해당 엔트 포인트까지의 거리를 계산합니다.

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*)
    ]
   ];  

플로팅 결과 :

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

대체 텍스트

컷오프 거리 보다 가까운 점을 그립니다 .

대체 텍스트

등고선 플롯 :

여기에 이미지 설명을 입력하십시오


11

어제 방금 썼어 Actionscript 3.0에는 기본적으로 Javascript이지만 동일한 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;
}

또한 여기에 문제에 대한 완전하고 읽기 쉬운 토론이 있습니다 : notejot.com


고마워-이것은 내가 찾던 코드의 종류입니다. 현재 시대의 브라우저-자바 스크립트에서 작동하는 것을 함께 모았 기 때문에 아래에 내 답변을 게시했지만 간단하고 잘 작성되어 이해하기 쉽기 때문에 귀하의 답변을 수락 된 것으로 표시했습니다. 그리고 많은 감사합니다.
Eli Courtwright

이것이 도트 방식이 누락되지 않았습니까? 어쨌든 vec1.x * vec2.x + vec1.y * vec2.y
quano

11

게으른 경우 위의 @Grumdrig 솔루션의 Objective-C 포트는 다음과 같습니다.

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

이 줄에서 'nan'이 반환됩니다. 왜 그런지 알아? (그런데 Obj-C에 이것을 입력 해 주셔서 감사합니다!) return dist2(p, CGPointMake(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)))
Gregir

sqrtf ()는 x를 제곱하여 제곱근을 얻지
못함

@Senseful 무슨 뜻인지 잘 모르겠습니다. sqrtf는 제곱근입니다. developer.apple.com/library/mac/documentation/Darwin/Reference/…
awolf

@awolf : 위 코드의 첫 번째 줄을보십시오. 메소드를 정의합니다 sqrtf(x) = x*x.
Senseful

@ 감사합니다, 잘못된 작업을 수행하는 대신 이름이 잘못되었습니다.
awolf

10

파이썬으로 코딩을 거부 할 수 없습니다 :)

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 :)

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
이것은 세그먼트 대신 선과 점의 거리를 계산하지 않습니까?
balint.miklos

6
이것은 실제로 세그먼트가 아닌 세그먼트가있는 선까지의 거리입니다.
Grumdrig

12
작동하지 않는 것 같습니다. (0,0)과 (5,0)의 세그먼트가 있고 (7,0) 지점에 대해 시도하면 0을 반환하지만 사실이 아닙니다. 거리는 2이어야합니다.
quano February

8
그는 세그먼트에 대한 점의 투영이 A에서 B까지의 간격을 벗어난 경우를 고려하지 못했습니다. 질문자가 원했던 것이지 그가 요구 한 것이 아닐 수도 있습니다.
phkahler

5
이것은 원래 요청 된 것이 아닙니다.
Sambatyon

10

다음은 Grumdrig 솔루션의 더 완벽한 철자입니다. 이 버전은 또한 가장 가까운 점 자체를 반환합니다.

#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 ++을 얼마나 싫어하는지 잊게되었습니다. 이것을 사용하여 해당 C # 버전을 만들었습니다.이 버전은 여기에 게시했습니다.
RenniePet

10

아크 탄젠트를 사용한 한 라인 솔루션 :

아이디어는 이동하는 것입니다 만들기 위해 (0, 0)과 회전 삼각형 시계 방향으로 C는 이런 일이 때, X 축에 누워 으로는 거리가 될 것입니다.

  1. 각도 = Atan (Cy-Ay, Cx-Ax);
  2. b 각도 = 아탄 (By-Ay, Bx-Ax);
  3. AB 길이 = Sqrt ((Bx-Ax) ^ 2 + (By-Ay) ^ 2)
  4. By = Sin (bAngle-aAngle) * ABLength

씨#

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

한 줄 C # (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

위의 Grumdrig의 답변에 대한이 수정을 고려하십시오. 부동 소수점 부정확성이 문제를 일으킬 수 있다는 것을 여러 번 알게 될 것입니다. 아래 버전에서는 복식을 사용하고 있지만 수레로 쉽게 변경할 수 있습니다. 중요한 부분은 엡실론을 사용하여 "경사"를 처리한다는 것입니다. 또한 교차로가 어디에서 발생했는지 또는 전혀 발생했는지 알고 싶어하는 경우가 많습니다. 반환 된 t가 <0.0 또는> 1.0이면 충돌이 발생하지 않은 것입니다. 그러나 충돌이 발생하지 않더라도 세그먼트에서 P에 가장 가까운 지점이 어디인지 알고 싶을 것이므로 qx와 qy를 사용 하여이 위치를 반환합니다.

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

나는 당신이 가장 짧은 것을 찾고 싶다고 가정합니다.점과 선분 사이의 거리; 이렇게하려면 점을 통과하는 선분 (lineB)에 수직 인 선 (lineA)을 찾아 해당 선 (lineA)과 선분 (lineB)을 통과하는 선의 교차점을 결정해야합니다. ; 해당 점이 선분의 두 점 사이에 있으면 거리는 점과 방금 찾은 점 사이의 거리이며 선 A와 선 B의 교차점입니다. 점이 선분의 두 점 사이에 있지 않으면 점과 선분의 두 끝점 사이의 거리를 가져와야합니다. 이것은 선분의 점과 두 점 사이의 제곱 거리 (제곱근을 피하기 위해)를 취함으로써 쉽게 수행 될 수 있습니다. 더 가까운 쪽이든 그 쪽의 제곱근을 취하십시오.


6

Grumdrig의 C ++ / JavaScript 구현은 나에게 매우 유용했기 때문에 사용중인 Python 직접 포트를 제공했습니다. 완전한 코드는 여기에 있습니다 .

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

인수없이 함수를 호출하면 내장 "자체 테스트"기능이있는 Matlab 코드 :

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

감사합니다.이 Matlab 코드는 실제로 세그먼트 가있는 무한 선까지의 거리가 아니라 선 세그먼트 까지의 최단 거리를 계산합니다 .
Rudolf Meijering

4

그리고 이제 내 솔루션도 ... (자바 스크립트)

Math.pow 함수를 피하려고 시도하기 때문에 매우 빠릅니다.

보시다시피, 함수의 끝에 선의 거리가 있습니다.

코드는 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

t-sql로 코딩

점은 (@px, @py)이고 선분은 (@ax, @ay)에서 (@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

StackOverflow의 다른 모든 사람들이 답변 (지금까지 23 답변)에 기여한 것처럼 보이므로 여기 C #에 대한 나의 기여가 있습니다. 이것은 대부분 M. Katz의 답변을 기반으로하며 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);
      }
   }

여기 작은 테스트 프로그램이 있습니다.

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

보시다시피 Sqrt () 메서드를 피하는 버전과 일반 버전의 차이점을 측정하려고했습니다. 내 테스트에 따르면 약 2.5 %를 절약 할 수 있지만 확실하지는 않습니다. 다양한 테스트 실행의 변형은 동일한 크기입니다. 또한 Matti가 게시 한 버전 (명확한 최적화)을 측정하려고 시도했으며 그 버전은 Katz / Grumdrig 코드 기반 버전보다 약 4 % 느립니다.

편집 : 우연히도 교차 곱 (및 Sqrt ())을 사용하여 무한 선 (선 세그먼트가 아닌)까지의 거리를 찾는 방법을 측정하려고 시도했으며 약 32 % 빠릅니다.


3

다음은 devnullicus의 C ++ 버전을 C #으로 변환 한 것입니다. 구현을 위해 교차 지점을 알아야하고 그의 솔루션이 잘 작동한다는 것을 알았습니다.

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

매력처럼 작동합니다! 수많은 시간을 절약했습니다. 정말 고마워!!
Steve Johnson

3

여기에 스위프트를 사용하고 있습니다.

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

씨#

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

이 코드를 사용해 보았지만 제대로 작동하지 않는 것 같습니다. 몇 번 잘못된 거리를 얻는 것 같습니다.
WDUK

3

2D 및 3D 솔루션

선분이되는 기초의 변화를 고려하십시오 (0, 0, 0)-(d, 0, 0) 되고 포인트(u, v, 0) . 최단 거리는 해당 평면에서 발생하며

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

(선으로의 투영에 따라 끝점 중 하나 또는 지지선까지의 거리. 등거리 궤적은 2 개의 반원과 2 개의 선분으로 구성됩니다.)

여기에 이미지 설명을 입력하십시오

상기 식에서, d는 세그먼트 AB의 길이이고, u, v는 각각 AB / d (AB 방향의 단위 벡터) 및 AC의 스칼라 곱 및 (모듈러스의) 곱이다. 따라서 벡터 적으로

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

2

다음 웹 사이트에서 Matlab GEOMETRY 도구 상자를 참조하십시오. http://people.sc.fsu.edu/~jburkardt/m_src/geometry/geometry.html

ctrl + f를 입력하고 "세그먼트"를 입력하여 선분 관련 기능을 찾으십시오. "segment_point_dist_2d.m"및 "segment_point_dist_3d.m"기능이 필요합니다.

GEOMETRY 코드는 C 버전 및 C ++ 버전과 FORTRAN77 버전, FORTRAN90 버전 및 MATLAB 버전으로 제공됩니다.


2

Joshua의 Javascript를 기반으로 한 AutoHotkeys 버전 :

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

여기에 Java 구현이 표시되지 않았으므로 Javascript 함수를 허용 된 답변에서 Java 코드로 변환했습니다.

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

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

2

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

내가 작성한 코드는 다음과 같습니다. 이 코드는 점이 형식으로 정의되어 있다고 가정합니다 {x:5, y:7}. 이것은 절대적으로 가장 효율적인 방법은 아니지만 내가 알아낼 수있는 가장 간단하고 이해하기 쉬운 코드입니다.

// a, b, and c in the code below are all points

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

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

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

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

1
이 코드에는 버그가 있습니다. 선분이있는 선 근방이지만 선분의 한쪽 끝에서 멀리 떨어진 지점은 선분 근처에있는 것으로 잘못 판단됩니다.
Grumdrig

흥미롭게도, 다음에이 코드베이스에서 작업 할 때이 주장을 확인하려고합니다. 팁 고마워.
Eli Courtwright

1

위의 기능은 세로선에서 작동하지 않습니다. 다음은 잘 작동하는 기능입니다! 점 p1, p2가있는 선 CheckPoint는 p입니다.

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

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

질문에 대답하지 않습니다. 이것은 선분 (유한 한 길이를 가짐)이 아닌 선 (공간에서 무한히 확장되는 선)에 대해서만 작동합니다.
트리니다드

"위의 기능"은 모호한 참조입니다. (때로는이 답변이 내 답변 아래에 표시되기 때문에
짜증

1

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 유형이 선언되지 않았습니다 (일부 파스칼 구현에서는 표준 유형일 수 있음). 아마 레코드 x, y 일 것이다 : double; 종료; 2. Distance와 MPower 함수는 선언되어 있지 않으며 어떤 기능을 수행하는지에 대한 설명이 없습니다 (예, 추측 가능). 3. 변수 투영이 선언되었지만 사용되지는 않습니다. 전반적으로 그것은 다소 좋지 않은 대답입니다.
dummzeuch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.