Wie kann ich feststellen, ob sich ein 2D-Punkt innerhalb eines Polygons befindet?


497

Ich versuche, einen schnellen 2D-Punkt innerhalb des Polygon-Algorithmus zu erstellen , um ihn beim Testen von Treffern zu verwenden (z Polygon.contains(p:Point). B. ). Vorschläge für effektive Techniken wären willkommen.


Sie haben vergessen, uns Ihre Wahrnehmungen zur Frage der Rechts- oder Linkshändigkeit mitzuteilen - was auch als "innen" gegen "außen" - RT interpretiert werden kann
Richard T

13
Ja, mir ist jetzt klar, dass die Frage viele Details nicht spezifiziert, aber an diesem Punkt bin ich irgendwie daran interessiert, die Vielfalt der Antworten zu sehen.
Scott Evernden

4
Ein 90-seitiges Polygon wird als Enneacontagon und ein 10.000-seitiges Polygon als Myriagon bezeichnet.

"Most elegant" ist nicht im Ziel, da ich Probleme hatte, einen "überhaupt Arbeit" -Algorithmus zu finden. Ich muss es selbst herausfinden
davidkonrad

Antworten:


732

Für Grafiken würde ich lieber keine ganzen Zahlen bevorzugen. Viele Systeme verwenden Ganzzahlen für das UI-Malen (Pixel sind schließlich Ints), aber macOS verwendet beispielsweise float für alles. macOS kennt nur Punkte und ein Punkt kann in ein Pixel übersetzt werden. Abhängig von der Monitorauflösung kann er jedoch in etwas anderes übersetzt werden. Auf Retina-Bildschirmen ist ein halber Punkt (0,5 / 0,5) Pixel. Trotzdem habe ich nie bemerkt, dass MacOS-Benutzeroberflächen erheblich langsamer sind als andere Benutzeroberflächen. Schließlich funktionieren 3D-APIs (OpenGL oder Direct3D) auch mit Floats, und moderne Grafikbibliotheken nutzen sehr oft die GPU-Beschleunigung.

Jetzt hast du gesagt, Geschwindigkeit ist dein Hauptanliegen, okay, lass uns Geschwindigkeit anstreben. Bevor Sie einen ausgeklügelten Algorithmus ausführen, führen Sie zunächst einen einfachen Test durch. Erstellen Sie einen achsenausgerichteten Begrenzungsrahmen um Ihr Polygon. Dies ist sehr einfach, schnell und kann Ihnen bereits viele Berechnungen ersparen. Wie funktioniert das? Iterieren Sie über alle Punkte des Polygons und ermitteln Sie die Min / Max-Werte von X und Y.

ZB hast du die Punkte (9/1), (4/3), (2/7), (8/2), (3/6). Dies bedeutet, dass Xmin 2, Xmax 9, Ymin 1 und Ymax 7 ist. Ein Punkt außerhalb des Rechtecks ​​mit den beiden Kanten (2/1) und (9/7) kann nicht innerhalb des Polygons liegen.

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

Dies ist der erste Test, der für einen beliebigen Punkt ausgeführt wird. Wie Sie sehen können, ist dieser Test ultraschnell, aber auch sehr grob. Um Punkte innerhalb des Begrenzungsrechtecks ​​zu behandeln, benötigen wir einen komplexeren Algorithmus. Es gibt verschiedene Möglichkeiten, wie dies berechnet werden kann. Welche Methode funktioniert, hängt auch davon ab, ob das Polygon Löcher haben kann oder immer fest sein wird. Hier sind Beispiele für feste (eine konvexe, eine konkave):

Polygon ohne Loch

Und hier ist einer mit einem Loch:

Polygon mit Loch

Der Grüne hat ein Loch in der Mitte!

Der einfachste Algorithmus, der alle drei oben genannten Fälle behandeln kann und immer noch ziemlich schnell ist, heißt Ray Casting . Die Idee des Algorithmus ist ziemlich einfach: Zeichnen Sie einen virtuellen Strahl von einer beliebigen Stelle außerhalb des Polygons zu Ihrem Punkt und zählen Sie, wie oft er auf eine Seite des Polygons trifft. Wenn die Anzahl der Treffer gerade ist, liegt sie außerhalb des Polygons. Wenn sie ungerade ist, befindet sie sich innerhalb.

Demonstrieren, wie der Strahl ein Polygon durchschneidet

Der Wicklungszahlalgorithmus wäre eine Alternative. Er ist genauer für Punkte, die sehr nahe an einer Polygonlinie liegen, aber auch viel langsamer. Das Ray Casting kann für Punkte fehlschlagen, die zu nahe an einer Polygonseite liegen, da die Gleitkommapräzision und Rundungsprobleme begrenzt sind. In der Realität ist dies jedoch kaum ein Problem, da ein Punkt so nahe an einer Seite liegt, dass es für a oft visuell nicht möglich ist Betrachter zu erkennen, ob es bereits drinnen oder noch draußen ist.

Sie haben immer noch den Begrenzungsrahmen von oben, erinnern Sie sich? Wählen Sie einfach einen Punkt außerhalb des Begrenzungsrahmens und verwenden Sie ihn als Ausgangspunkt für Ihren Strahl. ZB liegt der Punkt (Xmin - e/p.y)sicher außerhalb des Polygons.

Aber was ist das e? Nun, e(eigentlich Epsilon) gibt dem Begrenzungsrahmen etwas Polsterung . Wie gesagt, die Strahlverfolgung schlägt fehl, wenn wir zu nahe an einer Polygonlinie beginnen. Da der Begrenzungsrahmen möglicherweise dem Polygon entspricht (wenn das Polygon ein achsenausgerichtetes Rechteck ist, entspricht der Begrenzungsrahmen dem Polygon selbst!), Benötigen wir eine Auffüllung, um dies sicher zu machen, das ist alles. Wie groß solltest du wählen e? Nicht zu groß. Dies hängt von der Skalierung des Koordinatensystems ab, die Sie zum Zeichnen verwenden. Wenn Ihre Pixelschrittbreite 1,0 beträgt, wählen Sie einfach 1,0 (0,1 hätte jedoch auch funktioniert).

Nachdem wir nun den Strahl mit seinen Start- und Endkoordinaten haben, verschiebt sich das Problem von " ist der Punkt innerhalb des Polygons " zu " wie oft schneidet der Strahl eine Polygonseite ". Daher können wir nicht wie bisher mit den Polygonpunkten arbeiten, sondern benötigen jetzt die eigentlichen Seiten. Eine Seite wird immer durch zwei Punkte definiert.

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

Sie müssen den Strahl gegen alle Seiten testen. Betrachten Sie den Strahl als Vektor und jede Seite als Vektor. Der Strahl muss jede Seite genau einmal oder nie treffen. Es kann nicht zweimal dieselbe Seite treffen. Zwei Linien im 2D-Raum schneiden sich immer genau einmal, es sei denn, sie sind parallel. In diesem Fall schneiden sie sich nie. Da Vektoren jedoch eine begrenzte Länge haben, sind zwei Vektoren möglicherweise nicht parallel und schneiden sich immer noch nie, da sie zu kurz sind, um sich jemals zu treffen.

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

So weit so gut, aber wie testen Sie, ob sich zwei Vektoren schneiden? Hier ist ein C-Code (nicht getestet), der den Trick machen sollte:

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

Die Eingabewerte sind die beiden Endpunkte von Vektor 1 ( v1x1/v1y1und v1x2/v1y2) und Vektor 2 ( v2x1/v2y1und v2x2/v2y2). Sie haben also 2 Vektoren, 4 Punkte, 8 Koordinaten. YESund NOsind klar. YESerhöht Kreuzungen, NOtut nichts.

Was ist mit COLLINEAR? Dies bedeutet, dass beide Vektoren auf derselben unendlichen Linie liegen, je nach Position und Länge sich überhaupt nicht oder in einer endlosen Anzahl von Punkten schneiden. Ich bin mir nicht ganz sicher, wie ich mit diesem Fall umgehen soll. Ich würde ihn in keiner Weise als Kreuzung betrachten. Nun, dieser Fall ist in der Praxis ohnehin aufgrund von Gleitkomma-Rundungsfehlern eher selten. Besserer Code würde wahrscheinlich nicht testen, == 0.0fsondern auf etwas wie < epsilon, bei dem epsilon eine eher kleine Zahl ist.

Wenn Sie eine größere Anzahl von Punkten testen müssen, können Sie das Ganze sicherlich ein wenig beschleunigen, indem Sie die linearen Gleichungsstandardformen der Polygonseiten im Speicher behalten, sodass Sie diese nicht jedes Mal neu berechnen müssen. Auf diese Weise sparen Sie bei jedem Test zwei Gleitkomma-Multiplikationen und drei Gleitkomma-Subtraktionen, um drei Gleitkommawerte pro Polygonseite im Speicher zu speichern. Es ist ein typischer Kompromiss zwischen Speicher und Rechenzeit.

Last but not least: Wenn Sie zur Lösung des Problems 3D-Hardware verwenden, gibt es eine interessante Alternative. Lassen Sie einfach die GPU die ganze Arbeit für Sie erledigen. Erstellen Sie eine Malfläche außerhalb des Bildschirms. Füllen Sie es vollständig mit der Farbe Schwarz. Lassen Sie nun OpenGL oder Direct3D Ihr Polygon malen (oder sogar alle Ihre Polygone, wenn Sie nur testen möchten, ob der Punkt in einem von ihnen liegt, aber Sie sich nicht darum kümmern, welches) und füllen Sie die Polygone mit einem anderen Farbe, zB weiß. Um zu überprüfen, ob sich ein Punkt innerhalb des Polygons befindet, rufen Sie die Farbe dieses Punkts von der Zeichenfläche ab. Dies ist nur ein O (1) -Speicherabruf.

Natürlich ist diese Methode nur verwendbar, wenn Ihre Zeichenfläche nicht riesig sein muss. Wenn es nicht in den GPU-Speicher passt, ist diese Methode langsamer als auf der CPU. Wenn es riesig sein müsste und Ihre GPU moderne Shader unterstützt, können Sie die GPU weiterhin verwenden, indem Sie das oben gezeigte Ray Casting als GPU-Shader implementieren, was absolut möglich ist. Für eine größere Anzahl von Polygonen oder eine große Anzahl von zu testenden Punkten zahlt sich dies aus. Beachten Sie, dass einige GPUs 64 bis 256 Punkte parallel testen können. Beachten Sie jedoch, dass das Übertragen von Daten von der CPU zur GPU und zurück immer teuer ist. Wenn Sie also nur ein paar Punkte gegen ein paar einfache Polygone testen, bei denen entweder die Punkte oder die Polygone dynamisch sind und sich häufig ändern, zahlt sich ein GPU-Ansatz selten aus aus.


26
+1 Fantastische Antwort. Bei der Verwendung der Hardware habe ich an anderen Stellen gelesen, dass dies langsam sein kann, da Sie Daten von der Grafikkarte zurückerhalten müssen. Aber ich mag das Prinzip, die CPU zu entlasten, sehr. Hat jemand gute Referenzen, wie dies in OpenGL gemacht werden könnte?
Gavin

3
+1 weil das so einfach ist! Das Hauptproblem ist, wenn Ihr Polygon und Ihr Testpunkt in einem Raster ausgerichtet sind (nicht ungewöhnlich), müssen Sie sich mit "doppelten" Schnittpunkten befassen, z. B. direkt durch einen Polygonpunkt! (ergibt eine Parität von zwei statt eins). Geht in diesen seltsamen Bereich: stackoverflow.com/questions/2255842/… . Die Computergrafik ist voll von diesen Sonderfällen: theoretisch einfach, in der Praxis haarig.
Jared Updike

7
@RMorrisey: Warum denkst du so? Ich sehe nicht ein, wie es für ein konkaves Polygon fehlschlagen würde. Der Strahl kann das Polygon mehrmals verlassen und wieder betreten, wenn das Polygon konkav ist, aber am Ende ist der Trefferzähler ungerade, wenn der Punkt innerhalb und sogar außerhalb liegt, auch für konkave Polygone.
Mecki

6
Der unter softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm beschriebene 'Fast Winding Number Algorithm' funktioniert ziemlich schnell ...
SP

10
Ihre Verwendung von / zum Trennen von x- und y-Koordinaten ist verwirrend. Sie lautet x geteilt durch y. Es ist viel klarer, x, y (dh x Komma y) zu verwenden. Insgesamt eine nützliche Antwort.
Asche

583

Ich denke, der folgende Code ist die beste Lösung (von hier übernommen ):

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

Argumente

  • nvert : Anzahl der Eckpunkte im Polygon. Ob der erste Scheitelpunkt am Ende wiederholt werden soll, wurde in dem oben genannten Artikel erörtert.
  • vertx, verty : Arrays, die die x- und y-Koordinaten der Eckpunkte des Polygons enthalten.
  • testx, testy : X- und y-Koordinate des Testpunktes.

Es ist sowohl kurz als auch effizient und funktioniert sowohl für konvexe als auch für konkave Polygone. Wie zuvor vorgeschlagen, sollten Sie zuerst das Begrenzungsrechteck überprüfen und Polygonlöcher separat behandeln.

Die Idee dahinter ist ziemlich einfach. Der Autor beschreibt es wie folgt:

Ich führe einen semi-unendlichen Strahl horizontal (mit zunehmendem x, festem y) aus dem Testpunkt heraus und zähle, wie viele Kanten er kreuzt. Bei jeder Kreuzung wechselt der Strahl zwischen innen und außen. Dies wird als Jordan-Kurvensatz bezeichnet.

Die Variable c wechselt jedes Mal von 0 auf 1 und von 1 auf 0, wenn der horizontale Strahl eine Kante kreuzt. Im Grunde genommen wird also nachverfolgt, ob die Anzahl der gekreuzten Kanten gerade oder ungerade ist. 0 bedeutet gerade und 1 bedeutet ungerade.


5
Frage. Was genau sind die Variablen, die ich übergebe? Was repräsentieren sie?
Tekknolagi

9
@Mick Es überprüft das verty[i]und verty[j]ist auf beiden Seiten testy, so dass sie nie gleich sind.
Peter Wood

4
Dieser Code ist nicht robust und ich würde die Verwendung nicht empfehlen. Hier ist ein Link mit einigen detaillierten Analysen: www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf
Mikola

13
Dieser Ansatz hat tatsächlich Einschränkungen (Randfälle): Das Überprüfen des Punkts (15,20) im Polygon [(10,10), (10,20), (20,20), (20,10)] wird zurückgegeben falsch statt wahr. Gleiches gilt für (10,20) oder (20,15). In allen anderen Fällen funktioniert der Algorithmus einwandfrei und die falsch-negativen Ergebnisse in Randfällen sind für meine Anwendung in Ordnung.
Alexander Pacha

10
@Alexander, dies ist in der Tat beabsichtigt: Wenn zwei unterschiedliche Polygone eine Kante teilen, werden alle Punkte entlang dieser Kante in einem und nur einem Polygon lokalisiert, indem linke und untere Grenzen im entgegengesetzten Sinne zu oberen und rechten Grenzen behandelt werden. ..eine nützliche Eigenschaft.
Wardw

69

Hier ist eine C # -Version der Antwort von nirg , die von diesem RPI-Professor stammt . Beachten Sie, dass für die Verwendung des Codes aus dieser RPI-Quelle eine Zuordnung erforderlich ist.

Oben wurde ein Begrenzungsrahmen-Häkchen hinzugefügt. Wie James Brown jedoch betont, ist der Hauptcode fast so schnell wie der Begrenzungsrahmen selbst, sodass der Begrenzungsrahmen den Gesamtvorgang tatsächlich verlangsamen kann, falls sich die meisten Punkte, die Sie prüfen, innerhalb des Begrenzungsrahmens befinden . Sie können also den Begrenzungsrahmen auschecken lassen oder alternativ die Begrenzungsrahmen Ihrer Polygone vorberechnen, wenn sich ihre Form nicht zu oft ändert.

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
Funktioniert super, danke, ich habe auf JavaScript konvertiert. stackoverflow.com/questions/217578/…
Philipp Lenssen

2
Dies ist> 1000x schneller als mit GraphicsPath.IsVisible !! Durch das Aktivieren des Begrenzungsrahmens wird die Funktion um etwa 70% langsamer.
James Brown

Nicht nur GraphicsPath.IsVisible () ist viel langsamer, sondern funktioniert auch nicht gut mit sehr kleinen Polygonen mit einer Seite im Bereich
von 0,01

50

Hier ist eine JavaScript-Variante der Antwort von M. Katz basierend auf Nirgs Ansatz:

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

Berechnen Sie die orientierte Winkelsumme zwischen dem Punkt p und jeder der Polygonspitzen. Wenn der gesamte Ausrichtungswinkel 360 Grad beträgt, befindet sich der Punkt im Inneren. Wenn die Summe 0 ist, liegt der Punkt außerhalb.

Ich mag diese Methode besser, weil sie robuster ist und weniger von der numerischen Genauigkeit abhängt.

Methoden, die die Gleichmäßigkeit der Anzahl von Schnittpunkten berechnen, sind begrenzt, da Sie während der Berechnung der Anzahl von Schnittpunkten einen Scheitelpunkt "treffen" können.

EDIT: Diese Methode funktioniert übrigens mit konkaven und konvexen Polygonen.

EDIT: Ich habe kürzlich einen ganzen Wikipedia-Artikel zu diesem Thema gefunden.


1
Nein, das ist nicht wahr. Dies funktioniert unabhängig von der Konvexität des Polygons.
David Segonds

2
@DarenW: Nur ein Acos pro Vertex; Andererseits sollte dieser Algorithmus am einfachsten in SIMD zu implementieren sein, da er absolut keine Verzweigung aufweist.
Jasper Bekkers

1
@emilio, wenn der Punkt weit vom Dreieck entfernt ist, sehe ich nicht, wie der Winkel, den der Punkt und zwei Spitzen des Dreiecks bilden, 90 Grad beträgt.
David Segonds

2
Verwenden Sie zuerst das Begrenzungsrahmen-Häkchen, um "Punkt ist weit" -Fälle zu lösen. Für trig können Sie vorgenerierte Tabellen verwenden.
JOM

3
Dies ist die optimale Lösung, da es sich um O (n) handelt und nur minimale Berechnungen erforderlich sind. Funktioniert für alle Polygone. Ich habe diese Lösung vor 30 Jahren bei meinem ersten IBM-Job untersucht. Sie haben es abgemeldet und verwenden es noch heute in ihren GIS-Technologien.
Dominic Cerisano

24

Diese Frage ist so interessant. Ich habe eine andere praktikable Idee, die sich von anderen Antworten auf diesen Beitrag unterscheidet. Die Idee ist, die Summe der Winkel zu verwenden, um zu entscheiden, ob sich das Ziel innerhalb oder außerhalb befindet. Besser bekannt als Wicklungsnummer .

Sei x der Zielpunkt. Das Array [0, 1, .... n] seien alle Punkte des Bereichs. Verbinden Sie den Zielpunkt mit jedem Grenzpunkt mit einer Linie. Wenn sich der Zielpunkt innerhalb dieses Bereichs befindet. Die Summe aller Winkel beträgt 360 Grad. Wenn nicht, sind die Winkel kleiner als 360.

In diesem Bild erhalten Sie ein grundlegendes Verständnis der Idee: Geben Sie hier die Bildbeschreibung ein

Mein Algorithmus geht davon aus, dass der Uhrzeigersinn die positive Richtung ist. Hier ist eine mögliche Eingabe:

[[-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]]

Das Folgende ist der Python-Code, der die Idee implementiert:

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

Der von Bobobobo zitierte Artikel von Eric Haines ist wirklich exzellent. Besonders interessant sind die Tabellen, in denen die Leistung der Algorithmen verglichen wird. Die Winkelsummationsmethode ist im Vergleich zu den anderen wirklich schlecht. Interessant ist auch, dass Optimierungen wie die Verwendung eines Suchrasters zur weiteren Unterteilung des Polygons in "In" - und "Out" -Sektoren den Test selbst bei Polygonen mit> 1000 Seiten unglaublich schnell machen können.

Wie auch immer, es ist noch früh, aber meine Stimme geht an die "Crossings" -Methode, die meiner Meinung nach ziemlich genau von Mecki beschrieben wird. Ich fand es jedoch am prägnantesten von David Bourke beschrieben und kodifiziert . Ich finde es toll, dass keine echte Trigonometrie erforderlich ist. Sie funktioniert konvex und konkav und funktioniert mit zunehmender Anzahl der Seiten recht gut.

Übrigens, hier ist eine der Leistungstabellen aus dem Artikel von Eric Haines, die sich mit zufälligen Polygonen befasst.

                       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

Schnelle Version der Antwort von nirg :

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

Dies hat ein potentielles Problem der Division durch Null bei der Berechnung von b. Sie müssen nur "b" und die nächste Zeile mit "c" berechnen, wenn die Berechnung für "a" zeigt, dass die Möglichkeit einer Überschneidung besteht. Keine Möglichkeit, wenn beide Punkte oben oder beide Punkte unten liegen - was durch die Berechnung für "a" beschrieben wird.
Anorskdev

11

Wirklich wie die Lösung von Nirg gepostet und von Bobobobo bearbeitet. Ich habe es gerade Javascript-freundlich und für meinen Gebrauch etwas lesbarer gemacht:

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

Ich habe daran gearbeitet, als ich unter Michael Stonebraker forschte - wissen Sie, der Professor, der sich Ingres , PostgreSQL usw. ausgedacht hat.

Wir haben festgestellt, dass der schnellste Weg darin bestand, zuerst einen Begrenzungsrahmen zu erstellen, weil er SUPER schnell ist. Wenn es außerhalb des Begrenzungsrahmens ist, ist es außerhalb. Ansonsten machst du die härtere Arbeit ...

Wenn Sie einen großartigen Algorithmus wünschen, schauen Sie im Open-Source-Projekt PostgreSQL-Quellcode nach, um die Geo-Arbeit ...

Ich möchte darauf hinweisen, dass wir nie einen Einblick in Rechts- und Linkshändigkeit bekommen haben (auch ausgedrückt als "Innen" - "Außen" -Problem ...


AKTUALISIEREN

Der Link der BKB lieferte eine Reihe vernünftiger Algorithmen. Ich habe an geowissenschaftlichen Problemen gearbeitet und brauchte daher eine Lösung, die in Längen- und Breitengraden funktioniert, und sie hat das eigentümliche Problem der Händigkeit - befindet sich der Bereich innerhalb des kleineren Bereichs oder des größeren Bereichs? Die Antwort ist, dass die "Richtung" der Verticies wichtig ist - entweder für Linkshänder oder für Rechtshänder. Auf diese Weise können Sie jeden Bereich als "innerhalb" eines bestimmten Polygons angeben. Daher verwendete meine Arbeit die auf dieser Seite aufgezählte Lösung drei.

Darüber hinaus verwendete meine Arbeit separate Funktionen für "Online" -Tests.

... Da jemand gefragt hat: Wir haben herausgefunden, dass Bounding-Box-Tests am besten sind, wenn die Anzahl der Verticies über eine bestimmte Anzahl hinausgeht. Führen Sie bei Bedarf einen sehr schnellen Test durch, bevor Sie den längeren Test durchführen. Ein Bounding-Box wird erstellt, indem Sie einfach die größtes x, kleinstes x, größtes y und kleinstes y und füge sie zusammen, um vier Punkte einer Box zu bilden ...

Ein weiterer Tipp für die folgenden: Wir haben alle unsere ausgefeilteren und "lichtdimmenden" Berechnungen in einem Gitterraum durchgeführt, alle an positiven Punkten in einer Ebene, und dann wieder in "echte" Längen- / Breitengrade projiziert, um mögliche Fehler von zu vermeiden Umwickeln, wenn man die Linie 180 der Länge überquert und wenn man mit Polarregionen umgeht. Hat super funktioniert!


Was ist, wenn ich den Begrenzungsrahmen nicht habe? :)
Scott Evernden

8
Sie können leicht einen Begrenzungsrahmen erstellen - es sind nur die vier Punkte, die das größte und kleinste x und das größte und das kleinste y verwenden. Mehr in Kürze.
Richard T

"... Vermeidung möglicher Fehler beim Umwickeln, wenn man die Längengradlinie 180 überquert und beim Umgang mit Polarregionen." Können Sie das vielleicht genauer beschreiben? Ich denke, ich kann herausfinden, wie ich alles nach oben bewegen kann, um zu vermeiden, dass mein Polygon den Längengrad 0 überschreitet, aber ich bin mir nicht
sicher,

6

Die Antwort von David Segond ist so ziemlich die allgemeine Standardantwort, und die von Richard T ist die häufigste Optimierung, obwohl es einige andere gibt. Andere starke Optimierungen basieren auf weniger allgemeinen Lösungen. Wenn Sie beispielsweise dasselbe Polygon mit vielen Punkten überprüfen möchten, kann das Triangulieren des Polygons die Dinge erheblich beschleunigen, da es eine Reihe sehr schneller TIN-Suchalgorithmen gibt. Wenn sich das Polygon und die Punkte bei niedriger Auflösung auf einer begrenzten Ebene befinden, z. B. bei einer Bildschirmanzeige, können Sie das Polygon in einer bestimmten Farbe auf einen speicherabgebildeten Anzeigepuffer malen und die Farbe eines bestimmten Pixels überprüfen, um festzustellen, ob es liegt in den Polygonen.

Wie viele Optimierungen basieren diese eher auf spezifischen als auf allgemeinen Fällen und bieten Vorteile, die auf der amortisierten Zeit und nicht auf der einmaligen Verwendung basieren.

Als ich in diesem Bereich arbeitete, fand ich Joeseph O'Rourkes 'Berechnungsgeometrie in C' ISBN 0-521-44034-3 eine große Hilfe.


4

Die triviale Lösung wäre, das Polygon in Dreiecke zu teilen und die Dreiecke wie hier erläutert zu testen

Wenn Ihr Polygon CONVEX ist, gibt es möglicherweise einen besseren Ansatz. Betrachten Sie das Polygon als eine Sammlung unendlicher Linien. Jede Zeile teilt den Raum in zwei Teile. Für jeden Punkt ist es leicht zu sagen, ob er sich auf der einen oder der anderen Seite der Linie befindet. Wenn sich ein Punkt auf derselben Seite aller Linien befindet, befindet er sich innerhalb des Polygons.


sehr schnell und kann auf allgemeinere Formen angewendet werden. Um 1990 hatten wir "Curvigons", bei denen einige Seiten Kreisbögen waren. Durch die Analyse dieser Seiten in kreisförmige Keile und ein Paar Dreiecke, die den Keil mit dem Ursprung verbinden (Polygonschwerpunkt), war das Testen von Treffern schnell und einfach.
DarenW

1
Haben Sie Referenzen zu diesen Curvigons?
Shoosh

Ich würde auch einen Schiedsrichter für die Curvigons lieben.
Joel in Gö

Sie haben eine wichtige Lösung für den Fall konvexer Polygone verpasst: Durch Vergleichen des Punkts mit einer Diagonale können Sie die Anzahl der Eckpunkte halbieren. Wenn Sie diesen Vorgang wiederholen, reduzieren Sie sich in Log (N) -Operationen auf ein einziges Dreieck und nicht auf N.
Yves Daoust,

4

Mir ist klar, dass dies alt ist, aber hier ist ein in Cocoa implementierter Ray-Casting-Algorithmus, falls jemand interessiert ist. Ich bin mir nicht sicher, ob dies der effizienteste Weg ist, aber es kann jemandem helfen.

- (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
Beachten Sie, dass Sie die Methode [NSBezierPath includesPoint:] verwenden können, wenn Sie dies wirklich in Cocoa tun.
ThomasW

4

Obj-C-Version von Nirgs Antwort mit Beispielmethode zum Testen von Punkten. Nirgs Antwort funktionierte gut für mich.

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

Beispielpolygon


2
Natürlich ist in Objective-C CGPathContainsPoint()dein Freund.
Zev Eisenberg

@ZevEisenberg aber das wäre zu einfach! Danke für den Hinweis. Ich werde dieses Projekt irgendwann ausgraben, um zu sehen, warum ich eine benutzerdefinierte Lösung verwendet habe. Ich wusste wahrscheinlich nichts überCGPathContainsPoint()
Jon

4

Es gibt nichts Schöneres als eine induktive Definition eines Problems. Der Vollständigkeit halber haben Sie hier eine Version im Prolog, die auch die Gedanken hinter dem Ray Casting verdeutlichen könnte :

Basierend auf der Simulation des Einfachheitsalgorithmus in http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

Einige Helferprädikate:

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]).

Die Gleichung einer Linie mit 2 Punkten A und B (Linie (A, B)) lautet:

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

Es ist wichtig, dass die Drehrichtung für die Linie für Grenzen im Uhrzeigersinn und für Löcher gegen den Uhrzeigersinn eingestellt wird. Wir werden prüfen, ob der Punkt (X, Y), dh der getestete Punkt, auf der linken Halbebene unserer Linie liegt (es ist Geschmackssache, es könnte auch die rechte Seite sein, aber auch die Richtung der Grenzen Linien müssen in diesem Fall geändert werden), um den Strahl vom Punkt nach rechts (oder links) zu projizieren und den Schnittpunkt mit der Linie zu bestätigen. Wir haben uns entschieden, den Strahl in horizontaler Richtung zu projizieren (wieder ist es Geschmackssache, es könnte auch in vertikaler Richtung mit ähnlichen Einschränkungen erfolgen), also haben wir:

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

Jetzt müssen wir wissen, ob sich der Punkt nur auf der linken (oder rechten) Seite des Liniensegments befindet, nicht auf der gesamten Ebene. Daher müssen wir die Suche nur auf dieses Segment beschränken. Dies ist jedoch einfach, da wir uns innerhalb des Segments befinden Nur ein Punkt in der Linie kann auf der vertikalen Achse höher als Y sein. Da dies eine stärkere Einschränkung ist, muss es das erste sein, das überprüft wird. Daher nehmen wir zuerst nur die Zeilen, die diese Anforderung erfüllen, und überprüfen dann deren Besitz. Nach dem Jordan-Kurven-Theorem muss sich jeder auf ein Polygon projizierte Strahl mit einer geraden Anzahl von Linien schneiden. Wenn wir fertig sind, werfen wir den Strahl nach rechts und schalten dann jedes Mal, wenn er eine Linie schneidet, seinen Zustand um. Bei unserer Implementierung werden wir jedoch die Länge des Lösungsbeutels überprüfen, der die angegebenen Einschränkungen erfüllt, und die Innership darüber entscheiden. Für jede Linie im Polygon muss dies erfolgen.

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

Die C # -Version von Nirgs Antwort ist hier: Ich werde nur den Code teilen. Es kann jemandem Zeit sparen.

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

Dies funktioniert in den meisten Fällen, ist aber falsch und funktioniert nicht immer richtig! Verwenden Sie die Lösung von M Katz, die richtig ist
Lukas Hanacek

3

Java-Version:

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

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

Hinweis: Denken Sie daran, dass, wenn Ihr Polygon ein Bereich innerhalb einer Karte ist, Latitude / Longitude Y / X-Werte im Gegensatz zu X / Y (Latitude = Y, Longitude = X) sind, da meines Wissens historische Implikationen von damals sind Der Längengrad war kein Maß.

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

MODUL:

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

Ich habe eine Python-Implementierung von Nirgs C ++ - Code erstellt :

Eingänge

  • bounding_points: Knoten, aus denen das Polygon besteht.
  • bounding_box_positions: Zu filternde Kandidatenpunkte. (In meiner Implementierung aus dem Begrenzungsrahmen erstellt.

    (Die Eingänge sind Listen von Tupeln in dem Format: [(xcord, ycord), ...])

Kehrt zurück

  • Alle Punkte innerhalb des Polygons.
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

Auch hier ist die Idee von genommen hier


2

Überrascht, dass niemand dies früher angesprochen hat, aber für die Pragmatiker, die eine Datenbank benötigen: MongoDB bietet hervorragende Unterstützung für Geo-Abfragen, einschließlich dieser.

Was Sie suchen ist:

db.neighborhoods.findOne ({Geometrie: {$ geoIntersects: {$ Geometrie: {Typ: "Punkt", Koordinaten: ["Länge", "Breite"]}}})

Neighborhoodsist die Sammlung, in der ein oder mehrere Polygone im Standard-GeoJson-Format gespeichert sind. Wenn die Abfrage null zurückgibt, wird sie sonst nicht geschnitten.

Sehr gut dokumentiert hier: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

Die Leistung für mehr als 6.000 Punkte, die in einem unregelmäßigen Polygonraster von 330 klassifiziert wurden, betrug weniger als eine Minute ohne jegliche Optimierung und einschließlich der Zeit zum Aktualisieren von Dokumenten mit ihrem jeweiligen Polygon.


1

Hier ist ein Punkt im Polygontest in C, der kein Raycasting verwendet. Und es kann für überlappende Bereiche (Selbstüberschneidungen) funktionieren, siehe das use_holesArgument.

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

Hinweis: Dies ist eine der weniger optimalen Methoden, da sie viele Aufrufe enthält atan2f, aber für Entwickler, die diesen Thread lesen, von Interesse sein kann (in meinen Tests ist er ~ 23x langsamer als bei Verwendung der Linienschnittmethode).


0

Um Treffer auf Polygon zu erkennen, müssen wir zwei Dinge testen:

  1. Wenn sich der Punkt innerhalb des Polygonbereichs befindet. (kann durch Ray-Casting-Algorithmus erreicht werden)
  2. Wenn sich der Punkt am Polygonrand befindet (kann mit demselben Algorithmus ausgeführt werden, der für die Punkterkennung auf der Polylinie (Linie) verwendet wird).

0

Um die folgenden Sonderfälle im Ray Casting-Algorithmus zu behandeln :

  1. Der Strahl überlappt eine Seite des Polygons.
  2. Der Punkt befindet sich innerhalb des Polygons und der Strahl passiert einen Scheitelpunkt des Polygons.
  3. Der Punkt liegt außerhalb des Polygons und der Strahl berührt nur einen Winkel des Polygons.

Überprüfen Sie, ob sich ein Punkt innerhalb eines komplexen Polygons befindet . Der Artikel bietet eine einfache Möglichkeit, sie zu beheben, sodass für die oben genannten Fälle keine spezielle Behandlung erforderlich ist.


0

Sie können dies tun, indem Sie überprüfen, ob der Bereich, der durch Verbinden des gewünschten Punkts mit den Scheitelpunkten Ihres Polygons gebildet wird, mit dem Bereich des Polygons selbst übereinstimmt.

Oder Sie könnten prüfen, ob die Summe der Innenwinkel von Ihrem Punkt zu jedem Paar von zwei aufeinanderfolgenden Polygonscheitelpunkten zu Ihrem Prüfpunkt 360 ergibt, aber ich habe das Gefühl, dass die erste Option schneller ist, da sie weder Unterteilungen noch Berechnungen umfasst der Umkehrung trigonometrischer Funktionen.

Ich weiß nicht, was passiert, wenn Ihr Polygon ein Loch enthält, aber es scheint mir, dass die Hauptidee an diese Situation angepasst werden kann

Sie können die Frage auch in einer Mathe-Community posten. Ich wette, sie haben eine Million Möglichkeiten, das zu tun


0

Wenn Sie nach einer Java-Skriptbibliothek suchen, gibt es eine JavaScript-Google Maps v3-Erweiterung für die Polygon-Klasse, um festzustellen, ob sich ein Punkt darin befindet oder nicht.

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

Google Extention Github



0

Die Antwort hängt davon ab, ob Sie einfache oder komplexe Polygone haben. Einfache Polygone dürfen keine Liniensegmentschnitte haben. Sie können also Löcher haben, aber Linien können sich nicht kreuzen. Komplexe Regionen können Linienschnittpunkte haben - so können sie überlappende Regionen oder Regionen haben, die sich nur durch einen einzelnen Punkt berühren.

Für einfache Polygone ist der beste Algorithmus der Ray Casting-Algorithmus (Crossing Number). Bei komplexen Polygonen erkennt dieser Algorithmus keine Punkte, die sich innerhalb der überlappenden Bereiche befinden. Für komplexe Polygone müssen Sie also den Wicklungszahlalgorithmus verwenden.

Hier ist ein ausgezeichneter Artikel mit C-Implementierung beider Algorithmen. Ich habe sie ausprobiert und sie funktionieren gut.

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


0

Scala-Version der Lösung von nirg (setzt voraus, dass die Vorprüfung des Begrenzungsrechtecks ​​separat erfolgt):

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

Hier ist die Golang-Version von @nirg answer (inspiriert vom C # -Code von @@ m-katz)

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
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.