Bir nokta ile bir çizgi parçası arasındaki en kısa mesafe


360

Bir nokta ve bir çizgi segmenti arasındaki en kısa mesafeyi bulmak için temel bir fonksiyona ihtiyacım var. Çözümü istediğiniz herhangi bir dilde yazmaktan çekinmeyin; Kullandığım şeye çevirebilirim (Javascript).

EDIT: Çizgi segmentim iki uç nokta ile tanımlanır. Bu yüzden çizgi segmentim ABiki nokta ile tanımlanır A (x1,y1)ve B (x2,y2). Bu çizgi parçası ve bir nokta arasındaki mesafeyi bulmaya çalışıyorum C (x3,y3). Geometri becerilerim paslı, bu yüzden gördüğüm örnekler kafa karıştırıcı, itiraf ettiğim için üzgünüm.


Çizgileri ve noktaları nasıl temsil ettiğinizi bilmiyorum, ama işte başlamak için ihtiyacınız olan tüm matematik. Ne yapmanız gerektiğini anlamak çok zor olmamalı.
mandaleeka

4
@ArthurKalliokoski: bu bağlantı öldü, ancak bir kopya buldum: paulbourke.net/geometry/pointline
Gunther Struyf

11
@GuntherStruyf: bu bağlantı da öldü, ancak bu benzer bağlantı çalışıyor: paulbourke.net/geometry/pointlineplane
Michael

1
Birisi bir nokta ve bir çizgi arasında bir nokta değil, bir nokta ve bir çizgi SEGMENT arıyorsa
Nick Budden

1
Yukarıdaki bağlantı öldü. İşte sözde kod ve c ++ örneği, bir ders kitabı olarak ayrıntılı olarak açıklanmış ve türetilmiştir, geomalgorithms.com/a02-_lines.html
Eric

Yanıtlar:


448

Eli, yerleştiğin kod yanlış. Segmentin üzerinde bulunduğu çizginin yakınında, ancak segmentin bir ucundan çok uzakta olan bir nokta, segmentin yanında yanlış değerlendirilir. Güncelleme: Belirtilen yanlış cevap artık kabul edilen cevap değil.

İşte bazı doğru kod, C ++. Bir sınıf 2D-vektörü class vec2 {float x,y;}, esas olarak, toplama, çıkarma, ölçekleme, vb. İle operatörler ve bir mesafe ve nokta ürün fonksiyonu (yani x1 x2 + y1 y2) ile birlikte varsaymaktadır .

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: Ben bir Javascript uygulaması gerekli, işte burada, hiçbir bağımlılık (ya da yorum, ama yukarıda doğrudan bir bağlantı noktası). Noktalar xve yöznitelikleri olan nesneler olarak gösterilir .

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: Bir Java sürümüne ihtiyacım vardı, ama daha da önemlisi, 2d yerine 3d olarak ihtiyacım vardı.

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
Ayrı bir cevap olarak bunun bir etli versiyonunu ekledim.
M Katz

4
Teşekkürler @Grumdrig, javascript çözümünüz spot ve büyük bir zaman tasarrufu oldu. Çözümünüzü Objective-C'ye taşıdım ve aşağıya ekledim.
awolf

1
Gerçekten sadece sıfıra bölünmekten kaçınmaya çalışıyoruz.
Grumdrig

9
Noktanın pbir çizgiye izdüşümü , çizgiye en yakın noktadır p. (Ve izdüşümünü hattına dik geçecek p.) Sayısı tne kadar hat segmenti boyunca ila viçin wprojeksiyon düşer. Yani t0 ise, yansıtma tam olarak düşer v; 1 ise açıktır w; örneğin 0,5 ise, aradaki yarıya. Eğer tdaha az 0 veya daha yüksek 1 olduğu bir uçta son hat veya segmentin diğer düşer. Bu durumda, segmente olan mesafe yakın uca olan mesafe olacaktır.
Grumdrig

1
Hata! Birisinin 3D sürümü sağladığını fark etmedi. @RogiSolorzano, önce 3 boşlukta lat, uzun koordinatları x, y, z koordinatlarına dönüştürmeniz gerekir.
Grumdrig

112

İşte Javascript'teki en basit tam kod.

x, y hedef noktanız ve x1, y1 - x2, y2 çizgi segmentinizdir.

GÜNCELLENDİ: yorumlardaki 0 ​​uzunluk çizgisi sorunu için düzeltme.

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

Çözümü görselleştirmeye yardımcı olan görüntü


8
Bu sorunu çözmek için gördüğüm tüm kod, ben bu bir en iyi gibi. Çok net ve okunması kolay. Gerçi arkasında yatan matematik biraz mistik. Örneğin, nokta ürünün kare uzunluğuna bölünmesi neyi temsil eder?
user1815201

2
Nokta kare uzunluğuna bölünen nokta ürünü, (x1, y1) 'den projeksiyon mesafesini verir. Bu, (x, y) noktasının en yakın olduğu çizginin bölümüdür. (Xx, yy) 'nin hesaplandığı son diğer tümceye dikkat edin - bu (x, y) noktasının (x1, y1) - (x2, y2) parçasına izdüşümü.
Logan Pickup

4
0 uzunluğundaki çizgi segmentlerinin kontrolü kodda çok aşağı doğru. 'len_sq' sıfır olacak ve güvenlik kontrolüne geçmeden önce kod 0'a bölünecektir.
HostedMetrics.com

17
Metre. Metre olarak döndürülür.
Joshua

1
@nevermind, p0 noktamıza ve çizgiyi p1 ve p2 olarak tanımlayan noktalara bakalım. Sonra A = p0 - p1 ve B = p2 - p1 vektörlerini elde edersiniz. Param, B ile çarpıldığında çizgide p0'a en yakın noktayı veren skaler değerdir. Param <= 0 ise, en yakın nokta p1'dir. Eğer param> = 1 ise, en yakın nokta p1'dir. 0 ile 1 arasındaysa, p1 ve p2 arasında bir yerdedir, bu yüzden enterpolasyon yaparız. XX ve YY daha sonra çizgi segmentindeki en yakın noktadır, dx / dy p0'dan o noktaya olan vektördür ve son olarak bu vektörün uzunluğunu döndürürüz.
Sean

70

Bu, SONLU HAT SEGMENTLERİ için yapılmış bir uygulamadır, buradaki diğer işlevlerin çoğu gibi sonsuz çizgiler değil (bu yüzden bunu yaptım).

Teorinin Paul Bourke tarafından uygulanması .

Python:

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
Üzgünüm, ama bunu denedim ve hala sonuçları sanki sonsuzluğa uzanıyormuş gibi veriyor. Yine de Grumdig'in işe yanıtını buldum.
Frederik

1
Bu durumda yanlış kullanıyorsunuz veya sonsuz olmayan başka bir şey ifade ediyorsunuz. Bu kodun bir örneğini burada görebilirsiniz: boomie.se/upload/Drawdebug.swf
quano


30
Değişken isimleri seçimi iyi olmaktan uzaktır (p2, bir şey, u, ...)
miguelSantirso

2
Ben fonksiyonun Python sürümünü denedim ve parametreler tamsayılar ise yanlış sonuçlar gösterdiğini buldum. distAnother(0, 0, 4, 0, 2, 2)2.8284271247461903 (yanlış) verir. distAnother(0., 0., 4., 0., 2., 2.)2.0 verir (doğru). Lütfen bunun farkında ol. Kod bir yerde kayan dönüşüm için geliştirilebilir düşünüyorum.
Vladimir Obrizan

22

Kendi soru iş parçacığı C, C # / .NET 2.0 veya Java tüm durumlarda bir nokta ve bir çizgi segmenti arasındaki en kısa 2D mesafe nasıl hesaplanır? Bir tane bulduğumda buraya bir C # yanıtı koymam istendi: işte burada, 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'YI cevaplamıyorum ama soru soruyorum, umarım bazı nedenlerden ötürü milyon aşağı oy almıyorum ama eleştirmen yapıyorum. Bu konudaki çözümler ya egzotik bir dille (Fortran, Mathematica) ya da birileri tarafından hatalı olarak etiketlendiğinden, başkalarının fikirlerini paylaşmak istedim (ve teşvik edildim). Benim için tek yararlı olan (Grumdrig tarafından) C ++ ile yazılmıştır ve kimse hatalı etiketlememiştir. Ancak, adı verilen yöntemleri (nokta vb.) Eksik.


1
Bunu gönderdiğiniz için teşekkürler. Ancak son yöntemde bariz bir optimizasyon mümkün gibi görünüyor: Dağıtımın gerekli olduğunu belirleyene kadar hesaplama yapmayın.
RenniePet

2
DotProduct yorum AB.AC hesaplama, ama AB.BC hesaplama diyor.
Metal450

Çapraz ürün tanımı gereği bir vektör döndürür ancak burada bir skaler döndürür.
SteakOverflow

21

F #, noktadan itibaren mesafe cile segmente ave bile verilir:

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

Vektör dgelen noktaları aiçin bhat segmenti boyunca. Nokta ürünü d/sile c-asonsuz hat nokta arasındaki en yakın yaklaşım noktasının parametre verir c. minVe maxfonksiyon aralığı için bu parametreyi kenetlemek için kullanılan 0..snoktası yatıyor arasında olacak şekilde ave b. Son olarak, uzunluğu çizgi segmentindeki en yakın noktaya a+p-colan mesafedir c.

Örnek kullanım:

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

1
Bence son satır yanlış ve şunu okumalıyım:(a + p - c).Length
Blair Holloway

Bu hala sorunu tam olarak çözmüyor. Fonksiyonunu düzeltmek için bir yolu yeniden tanımlama olacağını lambdave polarak let lambda = (c - a) * d / (s * s)ve let p = a + (lambda |> max 0.0 |> min 1.0) * dsırasıyla. Fonksiyon durumda nereye doğru mesafe örneğin döndüren Bundan sonra a = (0,1), b = (1,0)ve c = (1,1).
mikkoma

20

İlgilenenler için, Joshua'nın Javascript kodunun Objective-C'ye önemsiz bir dönüşümü:

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

Çalışmak için bu çözüme ihtiyacım vardı, MKMapPointböylece bir başkasının ihtiyacı olması durumunda paylaşacağım. Sadece küçük bir değişiklik ve bu mesafe metre cinsinden dönecektir:

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

Bu benim için iyi çalışıyor gibi görünüyor. Dönüştürdüğünüz için teşekkürler.
Gregir

Fark etmekte fayda var, (xx, yy) en yakın noktanın yeri. Kodunuzu biraz düzenledim, bu yüzden hem noktayı hem de mesafeyi, yeniden adlandırılmış isimleri döndürdüler, böylece neyin ne olduğunu ve örnek verilenleri açıklarlar: stackoverflow.com/a/28028023/849616 .
Vive

20

Mathematica'da

Segmentin parametrik bir tanımını kullanır ve noktayı segment tarafından tanımlanan çizgiye yansıtır. Parametre segmentte 0'dan 1'e giderken, projeksiyon bu sınırların dışındaysa, segmente normal düz çizgi yerine karşılık gelen noktaya olan mesafeyi hesaplarız.

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

Sonuç çizme:

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

alternatif metin

Bu noktaları bir kesme mesafesinden daha yakın çizin :

alternatif metin

Kontur Grafiği:

resim açıklamasını buraya girin


11

Hey, bunu dün yazdım. Aynı Point sınıfına sahip olmasanız da temelde Javascript olan Actionscript 3.0'dadır.

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

Ayrıca, burada sorunun oldukça eksiksiz ve okunabilir bir tartışması var: notejot.com


Teşekkürler - bu tam olarak aradığım kod türüdür. Aşağıda kendi cevabımı gönderdim, çünkü şu anki çağda tarayıcı-Javascript'te çalışan bir şeyi bir araya getirmeyi başardım, ancak cevabınızı kabul edilmiş olarak işaretledim çünkü basit, iyi yazılmış, anlaşılması kolay, ve çok takdir.
Eli Courtwright

Bu nokta yöntemini kaçırmıyor mu? Her durumda, hesaplamak kolaydır: vec1.x * vec2.x + vec1.y * vec2.y
quano

11

Tembel için, yukarıdaki yukarıdaki Grumdrig'in çözümünün Objective-C bağlantı noktası:

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

Bu çizgiden 'nan' döndürülüyor. Neden olduğu hakkında bir fikrin var mı? (Bu arada, Obj-C'ye return dist2(p, CGPointMake(v.x + t * (w.x - v.x), v.y + t * (w.y - v.y)))
yazdığınız için teşekkürler

sqrtf () kare karesi alamadım x kare
Senseful

@Senseful Ne demek istediğinizden emin değilim. sqrtf kare köküdür. developer.apple.com/library/mac/documentation/Darwin/Reference/…
awolf

@awolf: Yukarıdaki ilk kod satırına bir göz atın. Yöntemi tanımlar sqrtf(x) = x*x.
14:35

@Senseful teşekkürler, yanlış işlem yerine yanlış adlandırıldı.
awolf

10

Python'da kodlamaya karşı koyamadım :)

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)


Fortran için Aynen :)

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
bu, bir noktanın segment yerine bir çizgiye olan uzaklığını hesaplamıyor mu?
balint.miklos

6
Bu gerçekten, segmente değil, segmentin üzerinde bulunduğu çizgiye olan mesafedir.
Grumdrig

12
Bu işe yaramıyor gibi görünüyor. (0,0) ve (5,0) 'lik bir segmentiniz varsa ve (7,0) noktasını denerseniz, 0 döndürür, bu doğru değildir. Mesafe 2 olmalıdır.
quano

8
Noktanın segmente izdüşümünün A'dan B'ye aralığın dışında olduğu durumu düşünemedi.
phkahler

5
Başlangıçta sorulan bu değil.
Sambatyon

10

İşte Grumdrig'in çözümünden daha eksiksiz bir yazım. Bu sürüm aynı zamanda en yakın noktanın kendisini döndürür.

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

Bunu gönderdiğiniz için teşekkürler. Çok iyi yapılandırılmış ve yorumlanmış ve biçimlendirilmiş - neredeyse bana ne kadar C ++ sevmediğim unutmak yaptı. Bunu, burada yayınladığım ilgili bir C # sürümünü yapmak için kullandım.
RenniePet

10

Arctangents kullanan tek hat çözümü:

Fikri taşımaktır A yapmak (0, 0) ve döndürme üçgen saat yönünde Bu gerçekleştiğinde, X ekseninde lay tarafından uzaklık olacaktır.

  1. bir açı = Atan (Cy - Ay, Cx-Ax);
  2. b açısı = Atan (By - Ay, Bx - Ax);
  3. AB uzunluğu = Metrekare ((Bx - Ax) ^ 2 + (By - Ay) ^ 2)
  4. Tarafından = Sin (bAngle - aAngle) * ABLength

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

Bir satır C # (SQL'e dönüştürülecek)

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

Yukarıdaki Grumdrig'in cevabındaki bu değişikliği düşünün. Çoğu zaman kayan nokta belirsizliğinin sorunlara neden olabileceğini göreceksiniz. Aşağıdaki sürümde çiftler kullanıyorum, ancak şamandıralara kolayca geçebilirsiniz. Önemli olan, "eğimi" işlemek için bir epsilon kullanmasıdır. Buna ek olarak, birçok kez kavşağın NEREDE olduğunu ya da hiç olup olmadığını bilmek isteyeceksiniz. Döndürülen t <0,0 veya> 1,0 ise, çarpışma meydana gelmez. Ancak, herhangi bir çarpışma meydana gelmese bile, birçok kez P segmentindeki en yakın noktanın nerede olduğunu bilmek isteyeceksiniz ve bu nedenle bu konuma dönmek için qx ve qy kullanıyorum.

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

En kısa noktayı bulmak istediğini varsayıyorumnokta ile çizgi parçası arasındaki mesafe; Bunu yapmak için, noktanıza gelen çizgi parçanıza (lineB) dikey olan çizgiyi (lineA) bulmanız, o çizgi (lineA) ile çizgi parçanızdan (lineB) geçen çizginiz arasındaki kesişmeyi belirlemeniz gerekir. ; bu nokta çizgi parçanızın iki noktası arasındaysa, mesafe noktanız ile az önce bulduğunuz nokta ile A çizgisinin ve B çizgisinin kesişimi olan mesafedir; nokta çizgi parçanızın iki noktası arasında değilse, noktanız ile çizgi parçasının iki ucunun daha yakın arasındaki mesafeyi almanız gerekir; bu, çizgi segmentinin noktası ile iki noktası arasındaki kare mesafesini (kare kökü önlemek için) alarak kolayca yapılabilir; hangisi daha yakınsa, o karenin karekökünü alın.


6

Grumdrig'in C ++ / JavaScript uygulaması benim için çok yararlı oldu, bu yüzden kullandığım bir Python doğrudan bağlantı noktası sağladım. Kodun tamamı burada .

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 kodu, işlevi bağımsız değişken olmadan çağırırlarsa yerleşik "kendi kendine test" ile:

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

Teşekkürler, bu Matlab kodu gerçekten SEGMENT hattına en kısa mesafeyi hesaplar , segmentin bulunduğu sonsuz çizgiye olan mesafeyi hesaplamaz .
Rudolf Meijering

4

Ve şimdi benim çözümüm de ...... (Javascript)

Herhangi bir Math.pow işlevinden kaçınmaya çalıştığım için çok hızlı.

Gördüğünüz gibi, fonksiyonun sonunda çizgi mesafesi var.

kodu http://www.draw2d.org/graphiti/jsdoc/#!/example adresinden alabilirsiniz

/**
 * 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 ile kodlanmış

nokta (@px, @py) ve çizgi segmenti (@ax, @ay) ile (@bx, @by) arasında değişir

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'daki hemen hemen herkes gibi bir yanıt verdi (şimdiye kadar 23 cevap), bu yüzden C # için katkım. Bu çoğunlukla M. Katz'ın cevabına dayanıyor ve bu da Grumdrig'in cevabına dayanıyor.

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

Ve işte küçük bir test programı.

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

Gördüğünüz gibi, Sqrt () yönteminden kaçınan sürümü kullanma ile normal sürüm arasındaki farkı ölçmeye çalıştım. Testlerim, yaklaşık% 2.5 tasarruf edebileceğinizi gösteriyor, ancak bundan bile emin değilim - çeşitli test çalışmalarındaki varyasyonlar aynı büyüklükteydi. Ayrıca Matti tarafından yayınlanan sürümü (artı belirgin bir optimizasyon) ölçmeyi denedim ve bu sürüm Katz / Grumdrig koduna dayanan versiyondan yaklaşık% 4 daha yavaş görünüyor.

Düzenleme: Bu arada, aynı zamanda bir çapraz ürün (ve bir Sqrt ()) kullanarak sonsuz bir çizgiye (bir çizgi segmenti değil) mesafe bulur bir yöntem ölçüm denedim ve yaklaşık% 32 daha hızlı.


3

İşte devnullicus'un C ++ sürümüne C # dönüştürülmüştür. Uygulamam için kesişim noktasını bilmem gerekiyordu ve çözümünün iyi çalıştığını buldum.

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

Tıkır tıkır çalışıyor!! Beni sayısız saat kurtardı. Çok teşekkürler!!
Steve Johnson

3

İşte Swift kullanıyor

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

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

Bu kodu denedim, oldukça doğru çalışmıyor gibi görünüyor. Bazı zamanlar yanlış mesafe var gibi görünüyor.
WDUK

3

2D ve 3D çözümü

Çizgi segmenti (0, 0, 0)-(d, 0, 0)ve nokta olacak şekilde bir temel değişikliği düşünün (u, v, 0). En kısa mesafe o düzlemde meydana gelir ve

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

(hatta izdüşümüne bağlı olarak, uç noktalardan birine veya destekleme hattına olan mesafe. İzo-mesafe lokusu iki yarım daire ve iki çizgi parçasından oluşur.)

resim açıklamasını buraya girin

Yukarıdaki ekspresyonda, d, AB segmentinin uzunluğudur ve u, v, AB / d'nin (AB yönünde birim vektör) ve AC'nin skaler ürünü ve (modül) çapraz ürününe karşılık gelir. Dolayısıyla vektörel olarak,

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

2

aşağıdaki web sitesindeki Matlab GEOMETRY araç kutusuna bakın: http://people.sc.fsu.edu/~jburkardt/m_src/geometry/geometry.html

ctrl + f tuşlarına basın ve çizgi segmentiyle ilgili işlevleri bulmak için "segment" yazın. "segment_point_dist_2d.m" ve "segment_point_dist_3d.m" işlevleri ihtiyacınız olan özelliklerdir.

GEOMETRY kodları bir C sürümü ve bir C ++ sürümü ve bir FORTRAN77 sürümü ve bir FORTRAN90 sürümü ve bir MATLAB sürümünde mevcuttur.


2

Joshua'nın Javascript'ine dayanan AutoHotkeys sürümü:

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

Burada bir Java uygulaması görmedim, bu yüzden Javascript işlevini kabul edilen cevaptan Java koduna çevirdim:

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 sürümü:

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

İşte yazdığım kod. Bu kod, bir noktanın şeklinde tanımlandığını varsayar {x:5, y:7}. Bunun mutlak en verimli yol olmadığını, ancak ortaya koyabileceğim en basit ve anlaşılması en kolay kod olduğunu unutmayın.

// 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
Bu kodda bir hata var. Segmentin üzerinde bulunduğu çizginin yakınında, ancak segmentin bir ucundan uzakta olan bir noktanın, segmentin yakınında olduğu yanlış değerlendirilir.
Grumdrig

İlginç, bir dahaki sefere bu kod temeli üzerinde iddianızı onaylamak için çalışacağım. Bahşiş için teşekkürler.
Eli Courtwright

1

Yukarıdaki işlev dikey çizgiler üzerinde çalışmıyor. İşte iyi çalışan bir işlev! P1, p2 noktaları olan çizgi. ve CheckPoint p'dir;

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

Soruya cevap vermiyor. Bu yalnızca (sınırlı bir uzunluğa sahip olan) çizgi segmentleri için değil, çizgiler (boşlukta sonsuz uzayanlar) için geçerlidir.
Trinidad

"yukarıdaki işlev" belirsiz bir referanstır. (Beni rahatsız ediyor çünkü bazen bu cevap cevabımın altında gösteriliyor.)
RenniePet

1

İşte C ++ cevabı ile aynı şey ama pascal'a taşındı. Point parametresinin sırası koduma uyacak şekilde değişti ancak aynı şey.

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;

Bu Yanıtla ilgili birkaç sorun vardır: PointF türü bildirilmemiştir (belki de bazı Pascal uygulamalarında standart bir türdür). Muhtemelen bir kayıt x, y: çift; son; 2. Mesafe ve MPower işlevleri bildirilmez ve ne yaptıklarına dair bir açıklama yoktur (tahmin edebiliriz, evet). 3. Değişken izdüşüm bildirilmiş ancak hiç kullanılmamış. Genel olarak bu oldukça zayıf bir cevap yapar.
dummzeuch
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.