2B Noktanın bir Çokgen içinde olup olmadığını nasıl belirleyebilirim?


497

Ben hit test (örneğin ) kullanmak için, çokgen algoritması içinde hızlı bir 2D nokta oluşturmaya çalışıyorum Polygon.contains(p:Point). Etkili teknikler için öneriler takdir edilecektir.


Bize sağ veya sol ellilik hakkındaki algılarınızı anlatmayı unuttunuz - ki bu da "içeride" ve "dışarıda" olarak yorumlanabilir - RT
Richard T

13
Evet, şimdi sorunun birçok ayrıntıyı tanımsız bıraktığının farkındayım, ancak bu noktada çeşitli cevapları görmekle ilgileniyorum.
Scott Evernden

4
90 taraflı bir çokgene enneacontagon ve 10.000 taraflı bir çokgene myriagon denir.

"En zarif" hedefin dışında, çünkü "hiç iş" -algoritmasını bulmakta zorlandım. Bunu kendim
çözmeliyim

Yanıtlar:


732

Grafikler için tamsayıları tercih etmemeyi tercih ederim. Birçok sistem UI boyama için tamsayılar kullanır (pikseller sonuçta ints'tur), ancak macOS örneğin her şey için kayan nokta kullanır. macOS yalnızca noktaları bilir ve bir nokta bir piksele çevrilebilir, ancak monitör çözünürlüğüne bağlı olarak başka bir şeye dönüşebilir. Retina ekranlarında yarım nokta (0.5 / 0.5) pikseldir. Yine de, macOS UI'lerinin diğer UI'lerden önemli ölçüde daha yavaş olduğunu hiç fark etmedim. Tüm 3B API'lerden sonra (OpenGL veya Direct3D) şamandıralarla çalışır ve modern grafik kütüphaneleri genellikle GPU hızlandırmadan yararlanır.

Şimdi hızın temel kaygınız olduğunu söylediniz, tamam, hadi hız için gidelim. Herhangi bir karmaşık algoritmayı çalıştırmadan önce basit bir test yapın. Bir oluşturma ekseniyle aynı hizada sınırlayıcı kutuÇokgeninizin etrafında . Bu çok kolay, hızlı ve zaten birçok hesaplamayı güvence altına alabilir. Bu nasıl çalışıyor? Poligonun tüm noktalarında tekrarlayın ve X ve Y'nin min / maks değerlerini bulun.

Mesela puanınız var (9/1), (4/3), (2/7), (8/2), (3/6). Bu, Xmin'in 2, Xmax'ın 9, Ymin'in 1 ve Ymax'ın 7 olduğu anlamına gelir. İki kenarlı (2/1) ve (9/7) dikdörtgenin dışındaki bir nokta çokgen içinde olamaz.

// p is your point, p.x is the x coord, p.y is the y coord
if (p.x < Xmin || p.x > Xmax || p.y < Ymin || p.y > Ymax) {
    // Definitely not within the polygon!
}

Bu, herhangi bir nokta için yapılan ilk testtir. Gördüğünüz gibi, bu test ultra hızlı ama aynı zamanda çok kaba. Sınırlayıcı dikdörtgen içindeki noktaları işlemek için daha karmaşık bir algoritmaya ihtiyacımız var. Bunun nasıl hesaplanabileceğinin birkaç yolu vardır. Hangi yöntemin işe yaradığı da, çokgenin delikleri olabileceği veya her zaman katı olacağı gerçeğine bağlıdır. İşte katı olanların örnekleri (bir dışbükey, bir içbükey):

Polygon without hole

Ve işte bir deliği olan:

Polygon with hole

Yeşil olanın ortasında bir delik var!

Yukarıdaki üç vakayı da ele alabilen ve hala oldukça hızlı olan en kolay algoritmaya ray döküm denir . Algoritma fikri oldukça basittir: Çokgenin dışındaki herhangi bir yerden noktanıza sanal bir ışın çizin ve çokgenin bir tarafına ne sıklıkla çarptığını sayın. İsabet sayısı eşitse, çokgenin dışındadır, garipse, içeride.

Demonstrating how the ray cuts through a polygon

Sarım sayısı algoritması çok yakın bir poligon hattı olmak noktaları için daha doğru ama çok yavaş da vardır, alternatif olabilir. Işın dökümü, sınırlı kayan nokta hassasiyeti ve yuvarlama sorunları nedeniyle çokgen tarafına çok yakın noktalar için başarısız olabilir, ancak gerçekte bu neredeyse bir sorun, sanki bir tarafa yakın bir nokta varmış gibi, zaten içeride mi yoksa hala dışarıda mı olduğunu görmek için görüntüleyicidir.

Hala yukarıdaki sınırlayıcı kutunuz var, hatırladınız mı? Sınırlayıcı kutunun dışında bir nokta seçin ve ışınınız için başlangıç ​​noktası olarak kullanın. Örneğin nokta (Xmin - e/p.y)çokgenin dışında.

Ama nedir e? Eh, e(aslında epsilon) sınırlayıcı kutusu bazı verir dolguyu . Dediğim gibi, çokgen çizgisine çok yakın başlarsak ışın izleme başarısız olur. Sınırlayıcı kutu çokgene eşit olabileceğinden (çokgen eksene hizalanmış bir dikdörtgense, sınırlayıcı kutu çokgenin kendisine eşittir!), Bunu güvenli hale getirmek için bazı dolgulara ihtiyacımız var, hepsi bu. Ne kadar büyük seçmelisin e? Çok büyük değil. Çizim için kullandığınız koordinat sistemi ölçeğine bağlıdır. Piksel adım genişliğiniz 1,0 ise, yalnızca 1,0'ı seçin (ancak 0,1 de işe yarardı)

Işın başlangıç ​​ve bitiş koordinatlarına sahip olduğumuza göre, sorun " çokgenin içindeki nokta " dan " ışının bir çokgen tarafıyla ne sıklıkla kesiştiğine " kayıyor . Bu nedenle, daha önce olduğu gibi çokgen noktalarıyla çalışamayız, şimdi gerçek taraflara ihtiyacımız var. Bir taraf daima iki nokta ile tanımlanır.

side 1: (X1/Y1)-(X2/Y2)
side 2: (X2/Y2)-(X3/Y3)
side 3: (X3/Y3)-(X4/Y4)
:

Işını her yönden test etmeniz gerekir. Işının bir vektör ve her iki tarafın da bir vektör olduğunu düşünün. Işın her iki tarafa tam olarak bir kez vurmalı ya da hiç vurmamalıdır. Aynı tarafa iki kez vuramaz. 2B uzayda iki çizgi, paralel olmadıkça her zaman tam olarak kesişir, bu durumda asla kesişmezler. Bununla birlikte, vektörler sınırlı bir uzunluğa sahip olduklarından, iki vektör paralel olmayabilir ve yine de birbirleriyle kesişemeyecek kadar kısa oldukları için asla kesişmeyebilirler.

// Test the ray against all sides
int intersections = 0;
for (side = 0; side < numberOfSides; side++) {
    // Test if current side intersects with ray.
    // If yes, intersections++;
}
if ((intersections & 1) == 1) {
    // Inside of polygon
} else {
    // Outside of polygon
}

Şimdiye kadar iyi, ama iki vektörün kesişip kesişmediğini nasıl test edersiniz? İşte bazı C kodu (test edilmemiştir), bu hile yapmalıdır:

#define NO 0
#define YES 1
#define COLLINEAR 2

int areIntersecting(
    float v1x1, float v1y1, float v1x2, float v1y2,
    float v2x1, float v2y1, float v2x2, float v2y2
) {
    float d1, d2;
    float a1, a2, b1, b2, c1, c2;

    // Convert vector 1 to a line (line 1) of infinite length.
    // We want the line in linear equation standard form: A*x + B*y + C = 0
    // See: http://en.wikipedia.org/wiki/Linear_equation
    a1 = v1y2 - v1y1;
    b1 = v1x1 - v1x2;
    c1 = (v1x2 * v1y1) - (v1x1 * v1y2);

    // Every point (x,y), that solves the equation above, is on the line,
    // every point that does not solve it, is not. The equation will have a
    // positive result if it is on one side of the line and a negative one 
    // if is on the other side of it. We insert (x1,y1) and (x2,y2) of vector
    // 2 into the equation above.
    d1 = (a1 * v2x1) + (b1 * v2y1) + c1;
    d2 = (a1 * v2x2) + (b1 * v2y2) + c1;

    // If d1 and d2 both have the same sign, they are both on the same side
    // of our line 1 and in that case no intersection is possible. Careful, 
    // 0 is a special case, that's why we don't test ">=" and "<=", 
    // but "<" and ">".
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // The fact that vector 2 intersected the infinite line 1 above doesn't 
    // mean it also intersects the vector 1. Vector 1 is only a subset of that
    // infinite line 1, so it may have intersected that line before the vector
    // started or after it ended. To know for sure, we have to repeat the
    // the same test the other way round. We start by calculating the 
    // infinite line 2 in linear equation standard form.
    a2 = v2y2 - v2y1;
    b2 = v2x1 - v2x2;
    c2 = (v2x2 * v2y1) - (v2x1 * v2y2);

    // Calculate d1 and d2 again, this time using points of vector 1.
    d1 = (a2 * v1x1) + (b2 * v1y1) + c2;
    d2 = (a2 * v1x2) + (b2 * v1y2) + c2;

    // Again, if both have the same sign (and neither one is 0),
    // no intersection is possible.
    if (d1 > 0 && d2 > 0) return NO;
    if (d1 < 0 && d2 < 0) return NO;

    // If we get here, only two possibilities are left. Either the two
    // vectors intersect in exactly one point or they are collinear, which
    // means they intersect in any number of points from zero to infinite.
    if ((a1 * b2) - (a2 * b1) == 0.0f) return COLLINEAR;

    // If they are not collinear, they must intersect in exactly one point.
    return YES;
}

Giriş değerleri, vektör 1 ( ve ) ve vektör 2 ( ve ) ' nin iki uç noktasıdır . 2 vektörünüz, 4 puanınız, 8 koordinatınız var. ve açıktır. kavşakları arttırır , hiçbir şey yapmaz.v1x1/v1y1v1x2/v1y2v2x1/v2y1v2x2/v2y2YESNOYESNO

COLLINEAR ne olacak? Bu, her iki vektörün de pozisyona ve uzunluğa bağlı olarak aynı sonsuz çizgi üzerinde yattığı, hiç kesişmediği veya sonsuz sayıda noktada kesiştiği anlamına gelir. Bu davayı nasıl ele alacağımdan emin değilim, her iki durumda da kavşak olarak saymazdım. Bu durum pratikte kayan nokta yuvarlama hataları nedeniyle zaten oldukça nadirdir; daha iyi kod muhtemelen test etmeyecektir, == 0.0fbunun yerine < epsilonepsilon'un oldukça küçük bir sayı olduğu gibi bir şey için .

Çok sayıda noktayı test etmeniz gerekiyorsa, çokgen tarafların doğrusal denklem standart formlarını bellekte tutarak her şeyi biraz hızlandırabilirsiniz, böylece bunları her zaman yeniden hesaplamanız gerekmez. Bu, bellekte çokgen taraf başına üç kayan nokta değeri saklamak karşılığında her testte iki kayan nokta çarpımı ve üç kayan nokta çıkartması kaydedecektir. Bu tipik bir bellek vs hesaplama süresi değiş tokuş.

Son fakat en az değil: Sorunu çözmek için 3D donanım kullanabiliyorsanız, ilginç bir alternatif var. GPU'nun sizin için tüm işleri yapmasına izin verin. Ekran dışında bir boyama yüzeyi oluşturun. Tamamen siyah renkle doldurun. Şimdi, OpenGL veya Direct3D'nin çokgeninizi boyamasına izin verin (veya sadece noktaların içinde olup olmadığını test etmek istiyorsanız, çokgenlerinizi bile tümüyle boyayın, ancak hangisini umursamıyorsanız) ve çokgenleri farklı bir ile doldurun renk, örneğin beyaz. Bir noktanın çokgen içinde olup olmadığını kontrol etmek için, bu noktanın rengini çizim yüzeyinden alın. Bu sadece O (1) bellek getirmesidir.

Tabii ki bu yöntem sadece çizim yüzeyinizin büyük olması gerekmiyorsa kullanılabilir. GPU belleğine sığmıyorsa, bu yöntem CPU'da yapmaktan daha yavaştır. Büyük olması gerekiyorsa ve GPU'nuz modern gölgelendiricileri destekliyorsa, yine de GPU gölgelendirici olarak gösterilen ışın dökümünü uygulayarak GPU'yu kullanabilirsiniz, ki bu kesinlikle mümkündür. Daha fazla sayıda çokgen veya test edilecek çok sayıda nokta için, bu işe yarayacaktır, bazı GPU'ların 64 ila 256 noktayı paralel olarak test edebileceğini düşünün. Bununla birlikte, CPU'dan GPU'ya ve arkaya veri aktarmanın her zaman pahalı olduğunu unutmayın, bu nedenle noktaların veya çokgenlerin dinamik olduğu ve sık sık değişeceği birkaç basit poligona karşı birkaç noktayı test etmek için GPU yaklaşımı nadiren ödeme yapar kapatır.


26
+1 Fantastik cevap. Bunu yapmak için donanımı kullanırken, grafik kartından veri almak zorunda olduğunuz için yavaş olabileceğini başka yerlerde okudum. Ama CPU'yu çok fazla kaldırma ilkesini seviyorum. Bunun OpenGL'de nasıl yapılabileceğine dair iyi referansları olan var mı?
Gavin

3
+1 çünkü bu çok basit! Asıl sorun, çokgen ve test noktanızın bir ızgara üzerinde sıralanması (nadir değil), örneğin "çok yönlü" kavşaklarla, örneğin doğrudan bir çokgen noktasından geçmeniz gerektiğidir! (bir yerine iki parite verir). Bu garip alana girer: stackoverflow.com/questions/2255842/… . Bilgisayar Grafikleri şu özel durumlarla doludur: teoride basit, pratikte kıllı.
Jared Updike

7
@RMorrisey: Neden böyle düşünüyorsun? Bir içbükey çokgen için nasıl başarısız olacağını göremiyorum. Çokgen içbükey olduğunda ışın çokgeni terk edip birçok kez tekrar girebilir, ancak sonuçta, isabet sayacı, nokta içerideyse ve dışarıda olsa bile, içbükey çokgenler için garip olacaktır.
Mecki

6
Adresinde açıklanan 'Hızlı Sarma Numara Algoritması', softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm ... oldukça hızlı çalışır
SP

10
X ve y koordinatlarını ayırmak / için kullanmanız kafa karıştırıcıdır, x'in y'ye bölünmesiyle okunur. X, y (yani x virgül) kullanmak çok daha açıktır Genel olarak, yararlı bir cevap.
Kül

583

Aşağıdaki kod parçası ( buradan alınan ) en iyi çözüm olduğunu düşünüyorum :

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}

Argümanlar

  • nvert : Çokgendeki köşe sayısı. İlk tepe noktasının sonunda tekrarlanıp tekrarlanmayacağı yukarıda belirtilen makalede tartışılmıştır.
  • vertx, verty : Çokgenin köşelerinin x ve y koordinatlarını içeren diziler.
  • testx, testy : Test noktasının X ve y koordinatları.

Hem kısa hem de verimlidir ve hem dışbükey hem de içbükey çokgenler için çalışır. Daha önce önerildiği gibi, önce sınırlayıcı dikdörtgeni kontrol etmeli ve çokgen deliklerini ayrı ayrı işlemelisiniz.

Bunun arkasındaki fikir oldukça basit. Yazar bunu şöyle anlatıyor:

Test noktasından yatay olarak yarı sonsuz bir ışın (artan x, sabit y) çalıştırıyorum ve kaç kenarı geçtiğini sayıyorum. Her geçişte, ışın iç ve dış arasında geçiş yapar. Buna Ürdün eğrisi teoremi denir.

Yatay değişken her kenardan geçtiğinde c değişkeni 0'dan 1'e ve 1'den 0'a geçer. Temel olarak, geçen kenar sayısının çift mi yoksa tek mi olduğunu takip ediyor. 0 eşittir ve 1 tek anlamına gelir.


5
Soru. Geçirdiğim değişkenler tam olarak nedir? Neyi temsil ediyorlar?
tekknolagi

9
@Mick Bunu kontrol eder verty[i]ve verty[j]her iki tarafındadır testy, bu yüzden asla eşit olmazlar.
Peter Wood

4
Bu kod sağlam değil ve ben bunu kullanmanızı tavsiye etmem. İşte bazı ayrıntılı analizler veren bir link: www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf
Mikola

13
Bu yaklaşımın aslında sınırları vardır (kenar durumlar): Çokgen [(10,10), (10,20), (20,20), (20,10)] 'deki Noktayı Kontrol Etmek (15,20) true yerine false. (10,20) veya (20,15) ile aynı. Diğer tüm durumlarda, algoritma mükemmel çalışır ve kenar durumlarda yanlış negatifler benim uygulama için tamam.
Alexander Pacha

10
@Alexander, bu aslında tasarım gereğidir: sol ve alt sınırları zıt anlamda üst ve sağ sınırlara taşıyarak, iki farklı çokgen bir kenarı paylaşırsa, bu kenar boyunca herhangi bir nokta bir ve sadece bir çokgene yerleştirilecektir. .. yararlı bir özellik.
15'te

69

İşte bu RPI profesöründen gelen nirg tarafından verilen cevabın C # versiyonu . Bu RPI kaynağından kod kullanımının ilişkilendirme gerektirdiğini unutmayın.

Üst tarafa bir sınırlayıcı kutu denetimi eklendi. Ancak, James Brown'un işaret ettiği gibi, ana kod neredeyse sınırlayıcı kutunun kendisini kontrol ettiği kadar hızlıdır, bu nedenle kontrol ettiğiniz noktaların çoğunun sınırlayıcı kutunun içinde olması durumunda sınırlayıcı kutu kontrolü aslında genel işlemi yavaşlatabilir. . Böylece sınırlayıcı kutuyu kullanıma bırakabilirsiniz ya da bir alternatif, çok sık şekil değiştirmezlerse çokgenlerin sınırlayıcı kutularını önceden hesaplamak olabilir.

public bool IsPointInPolygon( Point p, Point[] polygon )
{
    double minX = polygon[ 0 ].X;
    double maxX = polygon[ 0 ].X;
    double minY = polygon[ 0 ].Y;
    double maxY = polygon[ 0 ].Y;
    for ( int i = 1 ; i < polygon.Length ; i++ )
    {
        Point q = polygon[ i ];
        minX = Math.Min( q.X, minX );
        maxX = Math.Max( q.X, maxX );
        minY = Math.Min( q.Y, minY );
        maxY = Math.Max( q.Y, maxY );
    }

    if ( p.X < minX || p.X > maxX || p.Y < minY || p.Y > maxY )
    {
        return false;
    }

    // https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
    bool inside = false;
    for ( int i = 0, j = polygon.Length - 1 ; i < polygon.Length ; j = i++ )
    {
        if ( ( polygon[ i ].Y > p.Y ) != ( polygon[ j ].Y > p.Y ) &&
             p.X < ( polygon[ j ].X - polygon[ i ].X ) * ( p.Y - polygon[ i ].Y ) / ( polygon[ j ].Y - polygon[ i ].Y ) + polygon[ i ].X )
        {
            inside = !inside;
        }
    }

    return inside;
}

5
Harika çalışıyor, teşekkürler, JavaScript'e dönüştürdüm. stackoverflow.com/questions/217578/…
Philipp Lenssen

2
Bu, GraphicsPath kullanmaktan 1000 kat daha hızlıdır. Sınırlayıcı kutu kontrolü işlevi yaklaşık% 70 daha yavaş hale getirir.
James Brown

Sadece GraphicsPath.IsVisible () çok daha yavaş değil, aynı zamanda 0.01f aralığında tarafı olan çok küçük çokgenlerle de iyi çalışmıyor
NDepend ekibinden Patrick

50

Nirg'ın yaklaşımına dayanan M. Katz'ın cevabının bir JavaScript varyantı:

function pointIsInPoly(p, polygon) {
    var isInside = false;
    var minX = polygon[0].x, maxX = polygon[0].x;
    var minY = polygon[0].y, maxY = polygon[0].y;
    for (var n = 1; n < polygon.length; n++) {
        var q = polygon[n];
        minX = Math.min(q.x, minX);
        maxX = Math.max(q.x, maxX);
        minY = Math.min(q.y, minY);
        maxY = Math.max(q.y, maxY);
    }

    if (p.x < minX || p.x > maxX || p.y < minY || p.y > maxY) {
        return false;
    }

    var i = 0, j = polygon.length - 1;
    for (i, j; i < polygon.length; j = i++) {
        if ( (polygon[i].y > p.y) != (polygon[j].y > p.y) &&
                p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x ) {
            isInside = !isInside;
        }
    }

    return isInside;
}

32

P noktası ile çokgen apekslerinin her biri arasındaki açıların yönlendirilmiş toplamını hesaplayın. Toplam yönlendirilmiş açı 360 dereceyse, nokta içeridedir. Toplam 0 ise, nokta dışarıdadır.

Bu yöntemi daha çok seviyorum çünkü daha sağlam ve sayısal hassasiyete daha az bağımlı.

Kavşak sayısının eşitliğini hesaplayan yöntemler sınırlıdır, çünkü kavşak sayısının hesaplanması sırasında bir tepeye 'vurabilirsiniz'.

EDIT: Bu arada, bu yöntem içbükey ve dışbükey çokgenler ile çalışır.

EDIT: Geçenlerde konuyla ilgili bir Wikipedia makalesi buldum .


1
Hayır, bu doğru değil. Bu, çokgenin dışbükeyliğinden bağımsız olarak çalışır.
David Segonds

2
@DarenW: Her köşe için sadece bir acos; Öte yandan, bu algoritma SIMD'de uygulanması en kolay olanı olmalıdır, çünkü kesinlikle dallanma yoktur.
Jasper Bekkers

1
@emilio, nokta üçgenden uzaksa, noktanın oluşturduğu açının ve üçgenin iki apicesinin nasıl 90 derece olacağını göremiyorum.
David Segonds

2
İlk kullanım sınırlama kutusu kontrol "nokta uzak" vakaları çözmek için. Trig için önceden oluşturulmuş tabloları kullanabilirsiniz.
JOM

3
Bu optimal çözümdür, çünkü O (n) ve minimum hesaplama gerektirir. Tüm çokgenler için çalışır. Bu çözümü 30 yıl önce ilk IBM işimde araştırdım. Bunu imzaladılar ve bugün hala CBS teknolojilerinde kullanıyorlar.
Dominic Cerisano

24

Bu soru çok ilginç. Bu yazının diğer cevaplarından farklı, uygulanabilir başka bir fikrim var. Fikir, hedefin içinde mi yoksa dışında mı olduğuna karar vermek için açıların toplamını kullanmaktır. Sargı numarası olarak bilinir .

X hedef nokta olsun. [0, 1, .... n] dizisi alanın tüm noktaları olsun. Hedef noktayı bir çizgiyle her kenarlık noktasına bağlayın. Hedef nokta bu alanın içindeyse. Tüm açıların toplamı 360 derece olacaktır. Aksi takdirde, açılar 360'tan az olacaktır.

Fikir hakkında temel bir fikir edinmek için bu resme bakın: enter image description here

Algoritmam saat yönünde pozitif yön olduğunu varsayar. İşte potansiyel bir girdi:

[[-122.402015, 48.225216], [-117.032049, 48.999931], [-116.919132, 45.995175], [-124.079107, 46.267259], [-124.717175, 48.377557], [-122.92315, 47.047963], [-122.402015, 48.225216]]

Fikri uygulayan python kodu aşağıdadır:

def isInside(self, border, target):
degree = 0
for i in range(len(border) - 1):
    a = border[i]
    b = border[i + 1]

    # calculate distance of vector
    A = getDistance(a[0], a[1], b[0], b[1]);
    B = getDistance(target[0], target[1], a[0], a[1])
    C = getDistance(target[0], target[1], b[0], b[1])

    # calculate direction of vector
    ta_x = a[0] - target[0]
    ta_y = a[1] - target[1]
    tb_x = b[0] - target[0]
    tb_y = b[1] - target[1]

    cross = tb_y * ta_x - tb_x * ta_y
    clockwise = cross < 0

    # calculate sum of angles
    if(clockwise):
        degree = degree + math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))
    else:
        degree = degree - math.degrees(math.acos((B * B + C * C - A * A) / (2.0 * B * C)))

if(abs(round(degree) - 360) <= 3):
    return True
return False

21

Eric Haines makale bobobobo bahsettiği gerçekten mükemmel. Algoritmaların performansını karşılaştıran tablolar özellikle ilginçtir; açı toplamı yöntemi diğerlerine göre gerçekten kötüdür. Ayrıca ilginç olan, çokgeni "giriş" ve "çıkış" sektörlerine daha da alt bölümlere ayırmak için bir arama ızgarası kullanmak gibi optimizasyonların, testi> 1000 tarafı olan çokgenlerde bile inanılmaz derecede hızlı hale getirebilmesidir.

Her neyse, ilk günler ama oyum "geçişler" yöntemine gidiyor, bu Mecki'nin sanırım açıkladığı gibi. Ancak en çok David Bourke tarafından tanımlanmış ve kodlanmış olarak buldum . Gerçek bir trigonometri gerekmediğini ve dışbükey ve içbükey için çalıştığını ve kenar sayısı arttıkça makul bir performans sergilediğini seviyorum.

Bu arada, Eric Haines'in makalesi için, çokgenleri rastgele test eden performans tablolarından biri.

                       number of edges per polygon
                         3       4      10      100    1000
MacMartin               2.9     3.2     5.9     50.6    485
Crossings               3.1     3.4     6.8     60.0    624
Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
Triangle Fan            1.2     2.1     7.3     85.4    865
Barycentric             2.1     3.8    13.8    160.7   1665
Angle Summation        56.2    70.4   153.6   1403.8  14693

Grid (100x100)          1.5     1.5     1.6      2.1      9.8
Grid (20x20)            1.7     1.7     1.9      5.7     42.2
Bins (100)              1.8     1.9     2.7     15.1    117
Bins (20)               2.1     2.2     3.7     26.3    278

11

Nirg tarafından cevap hızlı sürümü :

extension CGPoint {
    func isInsidePolygon(vertices: [CGPoint]) -> Bool {
        guard !vertices.isEmpty else { return false }
        var j = vertices.last!, c = false
        for i in vertices {
            let a = (i.y > y) != (j.y > y)
            let b = (x < (j.x - i.x) * (y - i.y) / (j.y - i.y) + i.x)
            if a && b { c = !c }
            j = i
        }
        return c
    }
}

Bu, b hesaplamasında sıfır problemle potansiyel bir bölünmeye sahiptir. Eğer "a" için bir hesaplama kesişme olasılığı olduğunu gösteriyorsa, sadece "b" yi ve "c" ile sonraki satırı hesaplamanız gerekir. Her iki noktanın da üstte veya her iki noktanın üstünde olması mümkün değildir - bu "a" için hesaplama ile tanımlanır.
anorskdev

11

Nirg tarafından yayınlanan ve bobobobo tarafından düzenlenen çözüm gibi. Ben sadece javascript dostu ve kullanımı için biraz daha okunaklı yaptı:

function insidePoly(poly, pointx, pointy) {
    var i, j;
    var inside = false;
    for (i = 0, j = poly.length - 1; i < poly.length; j = i++) {
        if(((poly[i].y > pointy) != (poly[j].y > pointy)) && (pointx < (poly[j].x-poly[i].x) * (pointy-poly[i].y) / (poly[j].y-poly[i].y) + poly[i].x) ) inside = !inside;
    }
    return inside;
}

8

Michael Stonebraker altında bir araştırmacı olduğumda bu konuda biraz çalıştım - bilirsiniz, Ingres , PostgreSQL vb.

En hızlı yolun ilk önce sınırlayıcı bir kutu yapmak olduğunu fark ettik çünkü SUPER hızlı. Sınırlayıcı kutunun dışındaysa, dışarıdadır. Aksi takdirde, daha zor bir iş yaparsınız ...

Harika bir algoritma istiyorsanız, coğrafi çalışma için açık kaynak kodlu PostgreSQL kaynak koduna bakın ...

Ben işaret etmek istiyorum, biz asla sağ vs sol elini (herhangi bir "iç" vs "dış" sorun olarak ifade edilebilir herhangi bir fikir var ...


GÜNCELLEME

BKB'nin bağlantısı çok sayıda makul algoritma sağlamıştır. Dünya Bilimi sorunları üzerinde çalışıyordum ve bu nedenle enlem / boylamda çalışan bir çözüme ihtiyacım vardı ve kendine özgü bir el sorunu var - daha küçük alanın içindeki alan mı yoksa daha büyük alan mı? Cevap şudur: Verislerin "yönü" önemlidir - ya solak ya da sağlaktır ve bu şekilde her iki alanı da herhangi bir çokgenin "içinde" olarak belirtebilirsiniz. Bu nedenle, işim o sayfada numaralandırılmış üç numaralı çözümü kullandı.

Buna ek olarak, çalışmam "on line" testleri için ayrı fonksiyonlar kullandı.

Birisi sorduğundan beri: sınır sayısı kutu testlerinin en iyi olduğunu anladık, vertik sayısı bir sayının üzerine çıktığında - gerekirse daha uzun bir test yapmadan önce çok hızlı bir test yapın ... en büyük x, en küçük x, en büyük y ve en küçük y ve bir kutunun dört noktasını yapmak için bir araya getirme ...

Takip edenler için bir başka ipucu: daha karmaşık ve "ışık kararan" tüm hesaplamalarımızı bir düzlemde bir düzlemde pozitif noktalarda yaptık ve daha sonra tekrar "gerçek" boylam / enlem haline yansıtıp olası hatalardan kaçındık 180 boylam çizgisini aştığında ve kutup bölgelerini işlerken etrafını sarar. Harika çalıştı!


Sınırlayıcı kutum olmazsa ne olur? :)
Scott Evernden

8
Kolayca sınırlayıcı bir kutu oluşturabilirsiniz - sadece en büyük ve en az x ve en büyük ve en az y'yi kullanan dört nokta. Daha fazlası yakında.
Richard T

"... bir boylam 180 çizgisini geçtiğinde ve kutup bölgelerini işlerken olası sargı hatalarından kaçınmak." bunu daha ayrıntılı olarak açıklayabilir misiniz? Ben çokgen benim 0 boylam geçiş önlemek için her şeyi 'yukarı' taşımak için nasıl anlamaya düşünüyorum, ama kutuplardan birini içeren çokgen nasıl ele açık değil ...
tiritea

6

David Segond'un cevabı hemen hemen standart genel cevaptır ve Richard T'ler en yaygın optimizasyondur, ancak diğerleri de vardır. Diğer güçlü optimizasyonlar daha az genel çözümlere dayanmaktadır. Örneğin, aynı çokgeni çok sayıda nokta ile kontrol edecekseniz, çokgeni üçgenlemek çok sayıda hızlı TIN arama algoritması olduğu için işleri büyük ölçüde hızlandırabilir. Bir diğeri, çokgen ve noktaların düşük çözünürlükte sınırlı bir düzlemde olması, örneğin bir ekran görüntüsü olması durumunda, çokgeni belirli bir renkte bellek eşlemeli bir ekran arabelleğine boyayabilir ve belirli bir pikselin rengini kontrol edip çokgenlerde.

Birçok optimizasyon gibi, bunlar genel durumlardan ziyade spesifik vakalara dayanır ve tek kullanım yerine amortisman süresine göre fayda sağlar.

Bu alanda çalışarak, C 'ISBN 0-521-44034-3'te Joeseph O'Rourkes'in Hesaplama Geometrisinin çok yardımcı olduğunu gördüm.


4

Önemsiz çözüm, çokgeni üçgenlere bölmek ve burada açıklandığı gibi üçgenleri test etmek olacaktır.

Çokgeniniz CONVEX ise daha iyi bir yaklaşım olabilir. Çokgene sonsuz çizgi koleksiyonu olarak bakın. Her satır alanı ikiye böler. Her nokta için, çizginin bir tarafında mı yoksa diğer tarafında mı olduğunu söylemek kolaydır. Bir nokta tüm çizgilerin aynı tarafındaysa, poligonun içindedir.


çok hızlıdır ve daha genel şekillere uygulanabilir. 1990'larda, bazı tarafların dairesel yaylar olduğu "kıvrımlar" vardı. Bu tarafları dairesel kamalar ve kamaya orijine bağlayan bir çift üçgen (poligon centroid) olarak analiz ederek, isabet testi hızlı ve kolaydı.
DarenW

1
bu kıvrımlar hakkında herhangi bir referans var mı?
16'da shoosh

Ben de curvigons için bir ref isterim.
Joel Gö

Dışbükey çokgenler için önemli bir çözümü kaçırdınız: noktayı bir köşegenle karşılaştırarak köşe sayısını yarıya indirebilirsiniz. Ve bu işlemi tekrarlayarak, Log (N) işlemlerinde N yerine tek bir üçgene
indirgiyorsunuz

4

Bunun eski olduğunu anlıyorum, ama burada herkes ilgilenirse Kakao'da uygulanan bir ışın döküm algoritması var. Bir şeyler yapmanın en etkili yolu olduğundan emin değilim, ama birisine yardımcı olabilir.

- (BOOL)shape:(NSBezierPath *)path containsPoint:(NSPoint)point
{
    NSBezierPath *currentPath = [path bezierPathByFlatteningPath];
    BOOL result;
    float aggregateX = 0; //I use these to calculate the centroid of the shape
    float aggregateY = 0;
    NSPoint firstPoint[1];
    [currentPath elementAtIndex:0 associatedPoints:firstPoint];
    float olderX = firstPoint[0].x;
    float olderY = firstPoint[0].y;
    NSPoint interPoint;
    int noOfIntersections = 0;

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];
        [currentPath elementAtIndex:n associatedPoints:points];
        aggregateX += points[0].x;
        aggregateY += points[0].y;
    }

    for (int n = 0; n < [currentPath elementCount]; n++) {
        NSPoint points[1];

        [currentPath elementAtIndex:n associatedPoints:points];
        //line equations in Ax + By = C form
        float _A_FOO = (aggregateY/[currentPath elementCount]) - point.y;  
        float _B_FOO = point.x - (aggregateX/[currentPath elementCount]);
        float _C_FOO = (_A_FOO * point.x) + (_B_FOO * point.y);

        float _A_BAR = olderY - points[0].y;
        float _B_BAR = points[0].x - olderX;
        float _C_BAR = (_A_BAR * olderX) + (_B_BAR * olderY);

        float det = (_A_FOO * _B_BAR) - (_A_BAR * _B_FOO);
        if (det != 0) {
            //intersection points with the edges
            float xIntersectionPoint = ((_B_BAR * _C_FOO) - (_B_FOO * _C_BAR)) / det;
            float yIntersectionPoint = ((_A_FOO * _C_BAR) - (_A_BAR * _C_FOO)) / det;
            interPoint = NSMakePoint(xIntersectionPoint, yIntersectionPoint);
            if (olderX <= points[0].x) {
                //doesn't matter in which direction the ray goes, so I send it right-ward.
                if ((interPoint.x >= olderX && interPoint.x <= points[0].x) && (interPoint.x > point.x)) {  
                    noOfIntersections++;
                }
            } else {
                if ((interPoint.x >= points[0].x && interPoint.x <= olderX) && (interPoint.x > point.x)) {
                     noOfIntersections++;
                } 
            }
        }
        olderX = points[0].x;
        olderY = points[0].y;
    }
    if (noOfIntersections % 2 == 0) {
        result = FALSE;
    } else {
        result = TRUE;
    }
    return result;
}

5
Gerçekten Cocoa'da yapıyorsanız, [NSBezierPath includePoint:] yöntemini kullanabileceğinizi unutmayın.
ThomasW

4

Test noktaları için örnek yöntemle nirg cevabının Obj-C versiyonu. Nirg'un yanıtı benim için iyi çalıştı.

- (BOOL)isPointInPolygon:(NSArray *)vertices point:(CGPoint)test {
    NSUInteger nvert = [vertices count];
    NSInteger i, j, c = 0;
    CGPoint verti, vertj;

    for (i = 0, j = nvert-1; i < nvert; j = i++) {
        verti = [(NSValue *)[vertices objectAtIndex:i] CGPointValue];
        vertj = [(NSValue *)[vertices objectAtIndex:j] CGPointValue];
        if (( (verti.y > test.y) != (vertj.y > test.y) ) &&
        ( test.x < ( vertj.x - verti.x ) * ( test.y - verti.y ) / ( vertj.y - verti.y ) + verti.x) )
            c = !c;
    }

    return (c ? YES : NO);
}

- (void)testPoint {

    NSArray *polygonVertices = [NSArray arrayWithObjects:
        [NSValue valueWithCGPoint:CGPointMake(13.5, 41.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 56.5)],
        [NSValue valueWithCGPoint:CGPointMake(39.5, 69.5)],
        [NSValue valueWithCGPoint:CGPointMake(42.5, 84.5)],
        [NSValue valueWithCGPoint:CGPointMake(13.5, 100.0)],
        [NSValue valueWithCGPoint:CGPointMake(6.0, 70.5)],
        nil
    ];

    CGPoint tappedPoint = CGPointMake(23.0, 70.0);

    if ([self isPointInPolygon:polygonVertices point:tappedPoint]) {
        NSLog(@"YES");
    } else {
        NSLog(@"NO");
    }
}

örnek çokgen


2
Tabii ki, Objective-C, CGPathContainsPoint()arkadaşın.
Zev Eisenberg

@ZevEisenberg ama bu çok kolay olurdu! Not için teşekkürler. Neden özel bir çözüm kullandığımı görmek için bu projeyi bir noktada kazacağım. Muhtemelen bilmiyordumCGPathContainsPoint()
Jon

4

Bir sorunun indüktif tanımından daha güzel bir şey yoktur. Burada eksiksizlik uğruna, ışın dökümü arkasındaki düşünceleri de netleştirebilecek bir prolog sürümü var. :

Http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html adresindeki basitlik algoritmasının simülasyonuna dayanmaktadır

Bazı yardımcılar şunları öngörüyor:

exor(A,B):- \+A,B;A,\+B.
in_range(Coordinate,CA,CB) :- exor((CA>Coordinate),(CB>Coordinate)).

inside(false).
inside(_,[_|[]]).
inside(X:Y, [X1:Y1,X2:Y2|R]) :- in_range(Y,Y1,Y2), X > ( ((X2-X1)*(Y-Y1))/(Y2-Y1) +      X1),toggle_ray, inside(X:Y, [X2:Y2|R]); inside(X:Y, [X2:Y2|R]).

get_line(_,_,[]).
get_line([XA:YA,XB:YB],[X1:Y1,X2:Y2|R]):- [XA:YA,XB:YB]=[X1:Y1,X2:Y2]; get_line([XA:YA,XB:YB],[X2:Y2|R]).

2 nokta A ve B (Çizgi (A, B)) verilen bir çizginin denklemi:

                    (YB-YA)
           Y - YA = ------- * (X - XA) 
                    (XB-YB) 

Hattın dönüş yönünün sınırlar için saat yönünde ve delikler için saat yönünün tersine ayarlanması önemlidir. Noktanın (X, Y), yani test edilen noktanın hattımızın sol yarım düzleminde olup olmadığını kontrol edeceğiz (bu bir zevk meselesi, aynı zamanda sağ taraf, aynı zamanda sınırların yönü de olabilir) bu durumda çizgiler değiştirilmelidir), bu, ışını noktadan sağa (veya sola) yansıtmak ve çizgiyle kesişmeyi kabul etmektir. Işını yatay yönde yansıtmayı seçtik (yine bir zevk meselesi, benzer kısıtlamalarla dikey olarak da yapılabilir), bu yüzden:

               (XB-XA)
           X < ------- * (Y - YA) + XA
               (YB-YA) 

Şimdi noktanın tüm düzlemde değil, yalnızca çizgi segmentinin sol (veya sağ) tarafında olup olmadığını bilmemiz gerekiyor, bu nedenle aramayı sadece bu segmentle sınırlamamız gerekiyor, ancak segmentin içinde olması kolay. çizgideki sadece bir nokta dikey eksende Y'den daha yüksek olabilir. Bu daha güçlü bir kısıtlama olduğundan, ilk kontrol edilmesi gerekir, bu yüzden önce sadece bu gereksinimi karşılayan satırları alırız ve ardından varlığını kontrol ederiz. Jordan Curve teoremi ile, bir çokgene yansıtılan herhangi bir ışın, eşit sayıda çizgide kesişmelidir. Böylece işimiz bitti, ışını sağa fırlatacağız ve sonra bir çizgiyle her kesiştiğinde, durumunu değiştireceğiz. Bununla birlikte, uygulamamızda verilen kısıtlamaları karşılayan çözüm torbasının uzunluğunu kontrol edip içselliğe karar vermekteyiz. çokgendeki her satır için bu yapılmalıdır.

is_left_half_plane(_,[],[],_).
is_left_half_plane(X:Y,[XA:YA,XB:YB], [[X1:Y1,X2:Y2]|R], Test) :- [XA:YA, XB:YB] =  [X1:Y1, X2:Y2], call(Test, X , (((XB - XA) * (Y - YA)) / (YB - YA) + XA)); 
                                                        is_left_half_plane(X:Y, [XA:YA, XB:YB], R, Test).

in_y_range_at_poly(Y,[XA:YA,XB:YB],Polygon) :- get_line([XA:YA,XB:YB],Polygon),  in_range(Y,YA,YB).
all_in_range(Coordinate,Polygon,Lines) :- aggregate(bag(Line),    in_y_range_at_poly(Coordinate,Line,Polygon), Lines).

traverses_ray(X:Y, Lines, Count) :- aggregate(bag(Line), is_left_half_plane(X:Y, Line, Lines, <), IntersectingLines), length(IntersectingLines, Count).

% This is the entry point predicate
inside_poly(X:Y,Polygon,Answer) :- all_in_range(Y,Polygon,Lines), traverses_ray(X:Y, Lines, Count), (1 is mod(Count,2)->Answer=inside;Answer=outside).

3

Nirg'in cevabının C # sürümü burada: Sadece kodu paylaşacağım. Birini biraz zaman kurtarabilir.

public static bool IsPointInPolygon(IList<Point> polygon, Point testPoint) {
            bool result = false;
            int j = polygon.Count() - 1;
            for (int i = 0; i < polygon.Count(); i++) {
                if (polygon[i].Y < testPoint.Y && polygon[j].Y >= testPoint.Y || polygon[j].Y < testPoint.Y && polygon[i].Y >= testPoint.Y) {
                    if (polygon[i].X + (testPoint.Y - polygon[i].Y) / (polygon[j].Y - polygon[i].Y) * (polygon[j].X - polygon[i].X) < testPoint.X) {
                        result = !result;
                    }
                }
                j = i;
            }
            return result;
        }

Bu çoğu durumda çalışır, ancak yanlış ve her zaman düzgün çalışmıyor! doğru olan M Katz'ın çözümünü kullanın
Lukas Hanacek

3

Java Sürümü:

public class Geocode {
    private float latitude;
    private float longitude;

    public Geocode() {
    }

    public Geocode(float latitude, float longitude) {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public float getLatitude() {
        return latitude;
    }

    public void setLatitude(float latitude) {
        this.latitude = latitude;
    }

    public float getLongitude() {
        return longitude;
    }

    public void setLongitude(float longitude) {
        this.longitude = longitude;
    }
}

public class GeoPolygon {
    private ArrayList<Geocode> points;

    public GeoPolygon() {
        this.points = new ArrayList<Geocode>();
    }

    public GeoPolygon(ArrayList<Geocode> points) {
        this.points = points;
    }

    public GeoPolygon add(Geocode geo) {
        points.add(geo);
        return this;
    }

    public boolean inside(Geocode geo) {
        int i, j;
        boolean c = false;
        for (i = 0, j = points.size() - 1; i < points.size(); j = i++) {
            if (((points.get(i).getLongitude() > geo.getLongitude()) != (points.get(j).getLongitude() > geo.getLongitude())) &&
                    (geo.getLatitude() < (points.get(j).getLatitude() - points.get(i).getLatitude()) * (geo.getLongitude() - points.get(i).getLongitude()) / (points.get(j).getLongitude() - points.get(i).getLongitude()) + points.get(i).getLatitude()))
                c = !c;
        }
        return c;
    }

}

2

Net bağlantı noktası:.

    static void Main(string[] args)
    {

        Console.Write("Hola");
        List<double> vertx = new List<double>();
        List<double> verty = new List<double>();

        int i, j, c = 0;

        vertx.Add(1);
        vertx.Add(2);
        vertx.Add(1);
        vertx.Add(4);
        vertx.Add(4);
        vertx.Add(1);

        verty.Add(1);
        verty.Add(2);
        verty.Add(4);
        verty.Add(4);
        verty.Add(1);
        verty.Add(1);

        int nvert = 6;  //Vértices del poligono

        double testx = 2;
        double testy = 5;


        for (i = 0, j = nvert - 1; i < nvert; j = i++)
        {
            if (((verty[i] > testy) != (verty[j] > testy)) &&
             (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
                c = 1;
        }
    }

2

VBA SÜRÜMÜ:

Not: Poligonunuz bir harita içindeki bir alansa, Enlem / Boylam'ın X / Y (Latitude = Y, Boylam = X) yerine Y / X değerleri olduğunu unutmayın. Boylam bir ölçüm değildi.

SINIF MODÜLÜ: CPoint

Private pXValue As Double
Private pYValue As Double

'''''X Value Property'''''

Public Property Get X() As Double
    X = pXValue
End Property

Public Property Let X(Value As Double)
    pXValue = Value
End Property

'''''Y Value Property'''''

Public Property Get Y() As Double
    Y = pYValue
End Property

Public Property Let Y(Value As Double)
    pYValue = Value
End Property

MODÜL:

Public Function isPointInPolygon(p As CPoint, polygon() As CPoint) As Boolean

    Dim i As Integer
    Dim j As Integer
    Dim q As Object
    Dim minX As Double
    Dim maxX As Double
    Dim minY As Double
    Dim maxY As Double
    minX = polygon(0).X
    maxX = polygon(0).X
    minY = polygon(0).Y
    maxY = polygon(0).Y

    For i = 1 To UBound(polygon)
        Set q = polygon(i)
        minX = vbMin(q.X, minX)
        maxX = vbMax(q.X, maxX)
        minY = vbMin(q.Y, minY)
        maxY = vbMax(q.Y, maxY)
    Next i

    If p.X < minX Or p.X > maxX Or p.Y < minY Or p.Y > maxY Then
        isPointInPolygon = False
        Exit Function
    End If


    ' SOURCE: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    isPointInPolygon = False
    i = 0
    j = UBound(polygon)

    Do While i < UBound(polygon) + 1
        If (polygon(i).Y > p.Y) Then
            If (polygon(j).Y < p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        ElseIf (polygon(i).Y < p.Y) Then
            If (polygon(j).Y > p.Y) Then
                If p.X < (polygon(j).X - polygon(i).X) * (p.Y - polygon(i).Y) / (polygon(j).Y - polygon(i).Y) + polygon(i).X Then
                    isPointInPolygon = True
                    Exit Function
                End If
            End If
        End If
        j = i
        i = i + 1
    Loop   
End Function

Function vbMax(n1, n2) As Double
    vbMax = IIf(n1 > n2, n1, n2)
End Function

Function vbMin(n1, n2) As Double
    vbMin = IIf(n1 > n2, n2, n1)
End Function


Sub TestPointInPolygon()

    Dim i As Integer
    Dim InPolygon As Boolean

'   MARKER Object
    Dim p As CPoint
    Set p = New CPoint
    p.X = <ENTER X VALUE HERE>
    p.Y = <ENTER Y VALUE HERE>

'   POLYGON OBJECT
    Dim polygon() As CPoint
    ReDim polygon(<ENTER VALUE HERE>) 'Amount of vertices in polygon - 1
    For i = 0 To <ENTER VALUE HERE> 'Same value as above
       Set polygon(i) = New CPoint
       polygon(i).X = <ASSIGN X VALUE HERE> 'Source a list of values that can be looped through
       polgyon(i).Y = <ASSIGN Y VALUE HERE> 'Source a list of values that can be looped through
    Next i

    InPolygon = isPointInPolygon(p, polygon)
    MsgBox InPolygon

End Sub

2

Nirg'in c ++ kodunun bir Python uygulamasını yaptım :

Girdiler

  • bounding_points: çokgeni oluşturan düğümler.
  • bounding_box_positions: filtrelenecek aday noktaları. (Uygulamamda sınırlayıcı kutudan oluşturuldu.

    (Girdiler biçiminde dizilerini listeleri: [(xcord, ycord), ...])

İadeler

  • Çokgenin içindeki tüm noktalar.
def polygon_ray_casting(self, bounding_points, bounding_box_positions):
    # Arrays containing the x- and y-coordinates of the polygon's vertices.
    vertx = [point[0] for point in bounding_points]
    verty = [point[1] for point in bounding_points]
    # Number of vertices in the polygon
    nvert = len(bounding_points)
    # Points that are inside
    points_inside = []

    # For every candidate position within the bounding box
    for idx, pos in enumerate(bounding_box_positions):
        testx, testy = (pos[0], pos[1])
        c = 0
        for i in range(0, nvert):
            j = i - 1 if i != 0 else nvert - 1
            if( ((verty[i] > testy ) != (verty[j] > testy))   and
                    (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ):
                c += 1
        # If odd, that means that we are inside the polygon
        if c % 2 == 1: 
            points_inside.append(pos)


    return points_inside

Yine, fikir buradan alınır


2

Kimse bunu daha önce ortaya koymadı, ama bir veritabanı gerektiren pragmatistler için: MongoDB, bunu içeren Coğrafi sorgular için mükemmel bir desteğe sahip.

Aradığın şey:

db.neighborhoods.findOne ({geometry: {$ geoIntersects: {$ geometry: {type: "Point", koordinatlar: ["boylam", "enlem"]}}}}))

Neighborhoodsbir veya daha fazla poligonu standart GeoJson formatında saklayan koleksiyondur. Sorgu null değerini döndürürse kesişmez, aksi halde kesişir.

Burada çok iyi belgelenmiş: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

330 düzensiz çokgen ızgarada sınıflandırılmış 6.000'den fazla noktanın performansı hiç optimizasyon olmadan bir dakikadan azdı ve belgeleri ilgili çokgenleri ile güncelleme süresi dahil.


1

C'de poligon testinde ışın dökümü kullanmayan bir nokta var. Üst üste binen alanlar (kendi kendine kesişimler) için çalışabilir, use_holestartışmaya bakın .

/* math lib (defined below) */
static float dot_v2v2(const float a[2], const float b[2]);
static float angle_signed_v2v2(const float v1[2], const float v2[2]);
static void copy_v2_v2(float r[2], const float a[2]);

/* intersection function */
bool isect_point_poly_v2(const float pt[2], const float verts[][2], const unsigned int nr,
                         const bool use_holes)
{
    /* we do the angle rule, define that all added angles should be about zero or (2 * PI) */
    float angletot = 0.0;
    float fp1[2], fp2[2];
    unsigned int i;
    const float *p1, *p2;

    p1 = verts[nr - 1];

    /* first vector */
    fp1[0] = p1[0] - pt[0];
    fp1[1] = p1[1] - pt[1];

    for (i = 0; i < nr; i++) {
        p2 = verts[i];

        /* second vector */
        fp2[0] = p2[0] - pt[0];
        fp2[1] = p2[1] - pt[1];

        /* dot and angle and cross */
        angletot += angle_signed_v2v2(fp1, fp2);

        /* circulate */
        copy_v2_v2(fp1, fp2);
        p1 = p2;
    }

    angletot = fabsf(angletot);
    if (use_holes) {
        const float nested = floorf((angletot / (float)(M_PI * 2.0)) + 0.00001f);
        angletot -= nested * (float)(M_PI * 2.0);
        return (angletot > 4.0f) != ((int)nested % 2);
    }
    else {
        return (angletot > 4.0f);
    }
}

/* math lib */

static float dot_v2v2(const float a[2], const float b[2])
{
    return a[0] * b[0] + a[1] * b[1];
}

static float angle_signed_v2v2(const float v1[2], const float v2[2])
{
    const float perp_dot = (v1[1] * v2[0]) - (v1[0] * v2[1]);
    return atan2f(perp_dot, dot_v2v2(v1, v2));
}

static void copy_v2_v2(float r[2], const float a[2])
{
    r[0] = a[0];
    r[1] = a[1];
}

Not: Bu, çok sayıda çağrı içerdiğinden, daha az optimal yöntemlerden biridir atan2f, ancak bu iş parçacığını okuyan geliştiricilerin ilgisini çekebilir (testlerimde ~ 23x daha yavaş, çizgi kesişim yöntemini kullanarak).


0

Polygon'a isabet tespiti için iki şeyi test etmemiz gerekir:

  1. Nokta çokgen alanının içindeyse. (Ray-Casting Algoritması ile gerçekleştirilebilir)
  2. Nokta çokgen sınırındaysa (çok satırda (çizgi) nokta tespiti için kullanılan aynı algoritma ile gerçekleştirilebilir).

0

Ray döküm algoritmasında aşağıdaki özel durumlarla başa çıkmak için :

  1. Işın çokgenin yanlarından biriyle örtüşüyor.
  2. Nokta çokgenin içindedir ve ışın çokgenin bir tepe noktasından geçer.
  3. Nokta çokgenin dışında ve ışın çokgenin açısından birine dokunuyor.

Kontrol İster bir noktadır İçerisinde Karmaşık Bir Çokgeninizi Belirlenmesi . Makale, bunları çözmek için kolay bir yol sağlar, bu nedenle yukarıdaki durumlar için özel bir tedavi gerekmez.


0

Bunu, istenen noktayı çokgeninizin köşelerine bağlayarak oluşan alanın çokgenin kendisiyle aynı olup olmadığını kontrol ederek yapabilirsiniz.

Ya da noktanızdan kontrol noktanıza iki ardışık çokgen köşe çiftinin her birine kadar olan iç açıların toplamının 360'a kadar olup olmadığını kontrol edebilirsiniz, ancak ilk seçeneğin daha hızlı olduğunu hissediyorum, çünkü bölümleri veya hesaplamaları içermiyor trigonometrik fonksiyonların tersi.

Çokgeninizin içinde bir delik varsa ne olacağını bilmiyorum ama bana göre ana fikir bu duruma uyarlanabilir

Soruyu bir matematik topluluğuna da gönderebilirsiniz. Eminim bunu yapmanın bir milyon yolu vardır


0

Bir java-script kütüphanesi arıyorsanız Polygon sınıfı için bir noktanın içinde bulunup bulunmadığını tespit etmek için bir javascript google maps v3 uzantısı vardır.

var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);

Google Uzantı Github



0

Cevap, basit veya karmaşık çokgenlere sahip olup olmadığınıza bağlıdır. Basit çokgenlerin herhangi bir çizgi segmenti kesişimi olmamalıdır. Böylece delikleri olabilir ama çizgiler birbirini kesemez. Karmaşık bölgeler çizgi kesişimlerine sahip olabilir - böylece üst üste binen bölgelere veya tek bir noktadan birbirine temas eden bölgelere sahip olabilirler.

Basit çokgenler için en iyi algoritma Ray döküm (Geçiş sayısı) algoritmasıdır. Karmaşık çokgenler için, bu algoritma çakışan bölgelerin içindeki noktaları algılamaz. Bu yüzden karmaşık çokgenler için Sargı numarası algoritması kullanmanız gerekir.

İşte her iki algoritmanın C uygulaması ile mükemmel bir makale. Onları denedim ve iyi çalışıyorlar.

http://geomalgorithms.com/a03-_inclusion.html


0

Nirg tarafından çözümün Scala versiyonu (sınırlayıcı dikdörtgen ön kontrolünün ayrı ayrı yapıldığını varsayar):

def inside(p: Point, polygon: Array[Point], bounds: Bounds): Boolean = {

  val length = polygon.length

  @tailrec
  def oddIntersections(i: Int, j: Int, tracker: Boolean): Boolean = {
    if (i == length)
      tracker
    else {
      val intersects = (polygon(i).y > p.y) != (polygon(j).y > p.y) && p.x < (polygon(j).x - polygon(i).x) * (p.y - polygon(i).y) / (polygon(j).y - polygon(i).y) + polygon(i).x
      oddIntersections(i + 1, i, if (intersects) !tracker else tracker)
    }
  }

  oddIntersections(0, length - 1, tracker = false)
}

0

@Nirg answer'un golang sürümü (@@ m-katz tarafından C # kodundan esinlenilmiştir)

func isPointInPolygon(polygon []point, testp point) bool {
    minX := polygon[0].X
    maxX := polygon[0].X
    minY := polygon[0].Y
    maxY := polygon[0].Y

    for _, p := range polygon {
        minX = min(p.X, minX)
        maxX = max(p.X, maxX)
        minY = min(p.Y, minY)
        maxY = max(p.Y, maxY)
    }

    if testp.X < minX || testp.X > maxX || testp.Y < minY || testp.Y > maxY {
        return false
    }

    inside := false
    j := len(polygon) - 1
    for i := 0; i < len(polygon); i++ {
        if (polygon[i].Y > testp.Y) != (polygon[j].Y > testp.Y) && testp.X < (polygon[j].X-polygon[i].X)*(testp.Y-polygon[i].Y)/(polygon[j].Y-polygon[i].Y)+polygon[i].X {
            inside = !inside
        }
        j = i
    }

    return inside
}
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.