Bagaimana saya bisa menentukan apakah Titik 2D berada dalam Poligon?

497

Saya mencoba membuat titik 2D cepat di dalam algoritma poligon, untuk digunakan dalam pengujian-hit (misalnya Polygon.contains(p:Point)). Saran untuk teknik yang efektif akan dihargai.

Scott Evernden
sumber
Anda lupa memberi tahu kami tentang persepsi Anda tentang pertanyaan kidal atau kidal - yang juga dapat diartikan sebagai "di dalam" vs "di luar" - RT
Richard T
13
Ya, saya menyadari sekarang pertanyaannya menyisakan banyak detail yang tidak ditentukan, tetapi pada titik ini saya agak tertarik melihat beragam tanggapan.
Scott Evernden
4
Poligon bersisi 90 disebut enneacontagon dan poligon bersisi 10.000 disebut myriagon.
"Paling elegan" berada di luar target, karena saya kesulitan menemukan "kerja sama sekali" -algoritma. Saya harus mencari tahu sendiri: stackoverflow.com/questions/14818567/…
davidkonrad

Jawaban:

732

Untuk grafis, saya lebih suka tidak memilih bilangan bulat. Banyak sistem menggunakan bilangan bulat untuk lukisan UI (piksel ints setelah semua), tetapi macOS misalnya menggunakan float untuk semuanya. macOS hanya tahu titik dan satu titik dapat menerjemahkan ke satu piksel, tetapi tergantung pada resolusi monitor, itu mungkin diterjemahkan ke sesuatu yang lain. Pada layar retina setengah titik (0,5 / 0,5) adalah piksel. Namun, saya tidak pernah memperhatikan bahwa macOS UI secara signifikan lebih lambat daripada UI lainnya. Lagipula API 3D (OpenGL atau Direct3D) juga berfungsi dengan float dan perpustakaan grafik modern yang sangat sering memanfaatkan akselerasi GPU.

Sekarang Anda mengatakan kecepatan adalah perhatian utama Anda, oke, mari kita pergi untuk kecepatan. Sebelum Anda menjalankan algoritma canggih apa pun, pertama-tama lakukan tes sederhana. Buat kotak bounded aligned axis sekitar poligon Anda. Ini sangat mudah, cepat dan sudah bisa menyelamatkan Anda dari banyak perhitungan. Bagaimana cara kerjanya? Iterasi semua titik poligon dan temukan nilai min / maks X dan Y.

Misalnya Anda punya poin (9/1), (4/3), (2/7), (8/2), (3/6). Ini berarti Xmin adalah 2, Xmax adalah 9, Ymin adalah 1 dan Ymax adalah 7. Suatu titik di luar persegi panjang dengan dua sisi (2/1) dan (9/7) tidak dapat berada dalam poligon.

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

Ini adalah tes pertama yang dijalankan untuk setiap titik. Seperti yang Anda lihat, tes ini sangat cepat tetapi juga sangat kasar. Untuk menangani titik-titik yang berada dalam persegi panjang pembatas, kita membutuhkan algoritma yang lebih canggih. Ada beberapa cara bagaimana ini dapat dihitung. Metode mana yang bekerja juga tergantung pada kenyataan apakah poligon dapat memiliki lubang atau akan selalu padat. Berikut adalah contoh yang solid (satu cembung, satu cekung):

Poligon tanpa lubang

Dan ini satu lubang:

Poligon berlubang

Yang hijau memiliki lubang di tengah!

Algoritma termudah, yang dapat menangani ketiga kasus di atas dan masih cukup cepat bernama ray casting . Gagasan algoritmanya sangat sederhana: Gambarlah sinar virtual dari mana saja di luar poligon ke titik Anda dan hitung seberapa sering ia menyentuh sisi poligon. Jika jumlah hitnya genap, itu di luar poligon, jika aneh, itu di dalam.

Memperagakan bagaimana sinar memotong poligon

The algoritma nomor berliku akan menjadi alternatif, itu lebih akurat untuk titik-titik yang sangat dekat dengan garis poligon tetapi juga jauh lebih lambat. Ray casting mungkin gagal untuk titik yang terlalu dekat dengan sisi poligon karena ketelitian titik mengambang dan masalah pembulatan terbatas, tetapi pada kenyataannya itu hampir tidak menjadi masalah, seolah-olah suatu titik terletak dekat dengan sisi, seringkali secara visual bahkan tidak mungkin untuk suatu penampil untuk mengenali apakah sudah di dalam atau masih di luar.

Anda masih memiliki kotak pembatas di atas, ingat? Cukup pilih satu titik di luar kotak pembatas dan gunakan itu sebagai titik awal untuk ray Anda. Misalnya intinya di (Xmin - e/p.y)luar poligon pasti.

Tapi apa itu e? Nah, e(sebenarnya epsilon) memberikan kotak berlari beberapa padding . Seperti yang saya katakan, ray tracing gagal jika kita mulai terlalu dekat dengan garis poligon. Karena kotak pembatas mungkin sama dengan poligon (jika poligon adalah persegi panjang yang disejajarkan dengan sumbu, kotak pembatas sama dengan poligon itu sendiri!), Kita perlu beberapa pelapis untuk membuat ini aman, itu saja. Seberapa besar yang harus Anda pilih e? Tidak terlalu besar. Itu tergantung pada skala sistem koordinat yang Anda gunakan untuk menggambar. Jika lebar langkah piksel Anda adalah 1.0, maka pilih saja 1.0 (belum 0.1 juga akan berfungsi)

Sekarang kita memiliki sinar dengan koordinat awal dan akhir, masalahnya bergeser dari " adalah titik dalam poligon " ke " seberapa sering sinar memotong sisi poligon ". Karena itu kita tidak bisa hanya bekerja dengan titik poligon seperti sebelumnya, sekarang kita membutuhkan sisi sebenarnya. Sisi selalu ditentukan oleh dua poin.

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

Anda perlu menguji sinar terhadap semua sisi. Anggaplah sinar itu sebagai vektor dan setiap sisi adalah vektor. Sinar harus mengenai setiap sisi tepat sekali atau tidak sama sekali. Itu tidak bisa mengenai sisi yang sama dua kali. Dua garis dalam ruang 2D akan selalu berpotongan tepat sekali, kecuali jika paralel, dalam hal ini mereka tidak pernah berpotongan. Namun karena vektor memiliki panjang yang terbatas, dua vektor mungkin tidak sejajar dan masih tidak pernah berpotongan karena mereka terlalu pendek untuk saling bertemu.

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

Sejauh ini sangat baik, tetapi bagaimana Anda menguji jika dua vektor berpotongan? Berikut adalah beberapa kode C (tidak diuji), yang seharusnya bisa melakukan trik:

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

Nilai input adalah dua titik akhir dari vektor 1 ( v1x1/v1y1dan v1x2/v1y2) dan vektor 2 ( v2x1/v2y1dan v2x2/v2y2). Jadi, Anda memiliki 2 vektor, 4 poin, 8 koordinat. YESdan NOjelas. YESmeningkatkan persimpangan, NOtidak melakukan apa pun.

Bagaimana dengan COLLINEAR? Ini berarti kedua vektor terletak pada garis tak terbatas yang sama, tergantung pada posisi dan panjangnya, mereka tidak berpotongan sama sekali atau berpotongan dalam jumlah titik yang tak berujung. Saya tidak benar-benar yakin bagaimana menangani kasus ini, saya tidak akan menganggapnya sebagai persimpangan. Nah, kasus ini agak jarang dalam praktek karena kesalahan pembulatan titik mengambang; kode yang lebih baik mungkin tidak akan diuji == 0.0ftetapi untuk sesuatu seperti < epsilon, di mana epsilon adalah angka yang agak kecil.

Jika Anda perlu menguji jumlah poin yang lebih besar, Anda tentu bisa mempercepat semuanya dengan mempertahankan bentuk standar persamaan linear sisi poligon dalam memori, sehingga Anda tidak perlu menghitung ulang ini setiap waktu. Ini akan menghemat dua perkalian floating point dan tiga pengurangan floating point pada setiap tes dengan imbalan menyimpan tiga nilai floating point per sisi poligon dalam memori. Ini adalah memori yang khas vs waktu penghitungan tradeoff

Last but not least: Jika Anda dapat menggunakan perangkat keras 3D untuk memecahkan masalah, ada alternatif yang menarik. Biarkan GPU melakukan semua pekerjaan untuk Anda. Buat permukaan lukisan yang off screen. Isi sepenuhnya dengan warna hitam. Sekarang biarkan OpenGL atau Direct3D mengecat poligon Anda (atau bahkan semua poligon Anda jika Anda hanya ingin menguji apakah intinya ada di dalamnya, tetapi Anda tidak peduli yang mana) dan isi poligon dengan yang berbeda warna, misalnya putih. Untuk memeriksa apakah suatu titik berada dalam poligon, dapatkan warna titik ini dari permukaan gambar. Ini hanya pengambilan memori O (1).

Tentu saja metode ini hanya dapat digunakan jika permukaan gambar Anda tidak harus besar. Jika tidak dapat masuk ke memori GPU, metode ini lebih lambat daripada melakukannya di CPU. Jika harus besar dan GPU Anda mendukung shader modern, Anda masih dapat menggunakan GPU dengan menerapkan ray casting yang ditunjukkan di atas sebagai shader GPU, yang tentu saja dimungkinkan. Untuk sejumlah besar poligon atau sejumlah besar poin yang akan diuji, ini akan terbayar, pertimbangkan beberapa GPU akan dapat menguji 64 hingga 256 poin secara paralel. Namun perlu dicatat bahwa mentransfer data dari CPU ke GPU dan kembali selalu mahal, jadi untuk hanya menguji beberapa poin terhadap beberapa poligon sederhana, di mana poin atau poligon itu dinamis dan akan sering berubah, pendekatan GPU jarang membayar mati.

Mecki
sumber
26
+1 Jawaban yang fantastis. Saat menggunakan perangkat keras untuk melakukannya, saya sudah membaca di tempat lain bahwa itu bisa lambat karena Anda harus mendapatkan kembali data dari kartu grafis. Tapi saya suka prinsip mengambil banyak beban dari CPU. Adakah yang punya referensi bagus tentang bagaimana ini bisa dilakukan di OpenGL?
Gavin
3
+1 karena ini sangat sederhana! Masalah utama adalah jika poligon dan titik uji Anda berbaris pada kisi (tidak jarang) maka Anda harus berurusan dengan persimpangan "duplikat", misalnya, langsung melalui titik poligon! (Menghasilkan paritas dua bukannya satu). Masuk ke area aneh ini: stackoverflow.com/questions/2255842/… . Komputer Grafik penuh dengan kasus-kasus khusus ini: sederhana dalam teori, berbulu dalam praktek.
Jared Updike
7
@RMorrisey: Mengapa menurut Anda begitu? Saya gagal melihat bagaimana hal itu akan gagal untuk poligon cekung. Sinar dapat pergi dan masuk kembali poligon beberapa kali ketika poligon cekung, tetapi pada akhirnya, hit counter akan aneh jika titiknya ada di dalam dan bahkan jika berada di luar, juga untuk poligon cekung.
Mecki
6
'Algoritma Angka Berliku Cepat', dijelaskan di softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm bekerja sangat cepat ...
SP
10
Penggunaan / untuk memisahkan koordinat x dan y Anda membingungkan, terbaca sebagai x dibagi dengan y. Jauh lebih jelas untuk menggunakan x, y (yaitu x koma y) Secara keseluruhan, jawaban yang bermanfaat.
Ash
583

Saya pikir potongan kode berikut adalah solusi terbaik (diambil dari sini ):

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

Argumen

  • nvert : Jumlah simpul dalam poligon. Apakah mengulang verteks pertama di akhir telah dibahas dalam artikel yang disebutkan di atas.
  • vertx, verty : Array yang berisi koordinat x dan y dari titik poligon.
  • testx, testy : X- dan y-koordinat titik uji.

Keduanya pendek dan efisien dan berfungsi baik untuk poligon cembung dan cekung. Seperti yang disarankan sebelumnya, Anda harus memeriksa persegi panjang pembatas terlebih dahulu dan memperlakukan lubang poligon secara terpisah.

Gagasan di balik ini cukup sederhana. Penulis menggambarkannya sebagai berikut:

Saya menjalankan sinar semi-tak terbatas secara horizontal (meningkatkan x, memperbaiki y) keluar dari titik uji, dan menghitung berapa banyak sisi yang dilintasi. Pada setiap persimpangan, sinar beralih antara bagian dalam dan luar. Ini disebut teorema kurva Jordan.

Variabel c beralih dari 0 ke 1 dan 1 ke 0 setiap kali sinar horizontal melewati setiap tepi. Jadi pada dasarnya itu melacak apakah jumlah tepi yang dilintasi genap atau ganjil. 0 berarti genap dan 1 berarti ganjil.

nirg
sumber
5
Pertanyaan. Apa sebenarnya variabel yang saya lewati? Apa yang mereka wakili?
tekknolagi
9
@Mick Ini memeriksa itu verty[i]dan verty[j]kedua sisi testy, sehingga mereka tidak pernah sama.
Peter Wood
4
Kode ini tidak kuat, dan saya tidak akan merekomendasikan menggunakannya. Berikut ini tautan yang memberikan beberapa analisis terperinci: www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf
Mikola
13
Pendekatan ini sebenarnya TIDAK memiliki batasan (kasus tepi): Memeriksa Titik (15,20) dalam Poligon [(10,10), (10,20), (20,20), (20,10)] akan kembali salah bukan benar. Sama dengan (10,20) atau (20,15). Untuk semua kasus lain, algoritme berfungsi dengan sangat baik dan false-negative pada kasus tepi tidak masalah untuk aplikasi saya.
Alexander Pacha
10
@Alexander, ini sebenarnya dengan desain: dengan menangani batas kiri & bawah dalam arti yang berlawanan dengan batas atas & kanan, jika dua poligon berbeda berbagi tepi, setiap titik di sepanjang tepi ini akan terletak di satu dan hanya satu poligon. ..a properti yang bermanfaat.
menangkal
69

Ini adalah versi C # dari jawaban yang diberikan oleh nirg , yang berasal dari profesor RPI ini . Perhatikan bahwa penggunaan kode dari sumber RPI tersebut membutuhkan atribusi.

Kotak kotak pembatas telah ditambahkan di bagian atas. Namun, seperti yang ditunjukkan oleh James Brown, kode utama hampir secepat kotak pembatas memeriksa sendiri, sehingga kotak pembatas memeriksa sebenarnya dapat memperlambat keseluruhan operasi, dalam kasus bahwa sebagian besar poin yang Anda periksa ada di dalam kotak pembatas . Jadi Anda bisa membiarkan kotak pembatas memeriksa, atau alternatifnya adalah dengan mengkompilasi kotak pembatas poligon Anda jika kotak itu tidak terlalu sering berubah bentuk.

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;
}
M Katz
sumber
5
Bermanfaat, terima kasih, saya mengonversi ke JavaScript. stackoverflow.com/questions/217578/…
Philipp Lenssen
2
Ini> 1000x lebih cepat daripada menggunakan GraphicsPath.IsVisible !! Kotak pembatas membuat fungsi sekitar 70% lebih lambat.
James Brown
Tidak hanya GraphicsPath.IsVisible () lebih lambat tetapi juga tidak bekerja dengan baik dengan poligon yang sangat kecil dengan sisi dalam kisaran 0,01f
Patrick dari tim NDepend
50

Berikut adalah varian JavaScript dari jawaban oleh M. Katz berdasarkan pendekatan Nirg:

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;
}
Philipp Lenssen
sumber
32

Hitung jumlah sudut yang berorientasi antara titik p dan masing-masing apeks poligon. Jika sudut total berorientasi 360 derajat, titik di dalam. Jika totalnya 0, intinya di luar.

Saya suka metode ini lebih baik karena lebih kuat dan kurang tergantung pada ketepatan numerik.

Metode yang menghitung pemerataan jumlah persimpangan terbatas karena Anda dapat 'menekan' puncak selama perhitungan jumlah persimpangan.

EDIT: By The Way, metode ini bekerja dengan poligon cekung dan cembung.

EDIT: Baru-baru ini saya menemukan seluruh artikel Wikipedia tentang topik itu.

David Segonds
sumber
1
Tidak, ini tidak benar. Ini berfungsi terlepas dari cembungnya poligon.
David Segonds
2
@ DarenW: Hanya satu acos per titik; di sisi lain, algoritma ini harus menjadi yang paling mudah untuk diterapkan di SIMD karena sama sekali tidak memiliki percabangan
Jasper Bekkers
1
@EMILIO, jika titik jauh dari segitiga, saya tidak melihat bagaimana sudut yang dibentuk oleh titik dan dua apeks dari segitiga akan 90 derajat.
David Segonds
2
Pertama, gunakan kotak pembatas cek untuk menyelesaikan kasus "titik jauh". Untuk trigonometri, Anda bisa menggunakan tabel pregenerated.
JOM
3
Ini adalah solusi optimal, karena ini adalah O (n), dan membutuhkan perhitungan minimal. Bekerja untuk semua poligon. Saya meneliti solusi ini 30 tahun yang lalu di pekerjaan IBM pertama saya. Mereka menandatanganinya, dan masih menggunakannya hari ini dalam teknologi SIG mereka.
Dominic Cerisano
24

Pertanyaan ini sangat menarik. Saya punya ide lain yang bisa diterapkan berbeda dari jawaban lain untuk posting ini. Idenya adalah menggunakan jumlah sudut untuk memutuskan apakah target di dalam atau di luar. Lebih dikenal dengan nomor berliku .

Biarkan x menjadi titik target. Biarkan array [0, 1, .... n] menjadi semua titik area. Hubungkan titik target dengan setiap titik perbatasan dengan garis. Jika titik target ada di dalam area ini. Jumlah semua sudut akan 360 derajat. Jika tidak, sudutnya akan kurang dari 360.

Lihat gambar ini untuk mendapatkan pemahaman dasar tentang ide: masukkan deskripsi gambar di sini

Algoritme saya menganggap searah jarum jam adalah arah positif. Berikut adalah input potensial:

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

Berikut ini adalah kode python yang mengimplementasikan ide:

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
Junbang Huang
sumber
21

Artikel Eric Haines dikutip oleh bobobobo sangat bagus. Yang sangat menarik adalah tabel yang membandingkan kinerja algoritma; metode penjumlahan sudut benar-benar buruk dibandingkan dengan yang lain. Yang juga menarik adalah optimisasi seperti menggunakan kotak pencarian untuk membagi poligon menjadi beberapa sektor "dalam" dan "keluar" dapat membuat pengujian sangat cepat bahkan pada poligon dengan sisi> 1000.

Ngomong-ngomong, ini masih hari-hari awal tapi pilihanku pergi ke metode "penyeberangan", yang menurut saya cukup banyak menggambarkan Mecki. Namun saya menemukan itu dijelaskan dan dikodifikasikan oleh David Bourke dengan sangat mudah . Saya suka bahwa tidak ada trigonometri nyata yang diperlukan, dan itu berfungsi untuk cembung dan cekung, dan berkinerja cukup baik karena jumlah sisi meningkat.

Ngomong-ngomong, inilah salah satu tabel kinerja dari artikel Eric Haines untuk minat, pengujian pada poligon acak.

                       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
Gavin
sumber
11

Versi cepat jawaban oleh 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
    }
}
bzz
sumber
Ini memiliki potensi membagi dengan nol masalah dalam menghitung b. Hanya perlu menghitung "b" dan baris berikutnya dengan "c" jika perhitungan untuk "a" menunjukkan bahwa ada kemungkinan berpotongan. Tidak ada kemungkinan jika kedua poin di atas, atau kedua poin di bawah ini - yang dijelaskan oleh perhitungan untuk "a".
anorskdev
11

Sangat suka solusi yang diposting oleh Nirg dan diedit oleh bobobobo. Saya baru saja membuatnya ramah javascript dan sedikit lebih mudah dibaca untuk saya gunakan:

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;
}
Dave Seidman
sumber
8

Saya melakukan beberapa pekerjaan di belakang ini ketika saya adalah seorang peneliti di bawah Michael Stonebraker - Anda tahu, profesor yang datang dengan Ingres , PostgreSQL , dll.

Kami menyadari bahwa cara tercepat adalah pertama-tama melakukan kotak berlari karena itu SUPER cepat. Jika di luar kotak pembatas, itu di luar. Jika tidak, Anda melakukan pekerjaan yang lebih sulit ...

Jika Anda menginginkan algoritma yang hebat, lihat proyek open source kode sumber PostgreSQL untuk pekerjaan geo ...

Saya ingin menunjukkan, kami tidak pernah mendapatkan wawasan tentang kidal vs kanan (juga dapat diungkapkan sebagai masalah "dalam" vs "di luar" ...


MEMPERBARUI

Tautan BKB menyediakan sejumlah algoritma yang masuk akal. Saya sedang mengerjakan masalah-masalah Ilmu Bumi dan karena itu membutuhkan solusi yang bekerja di lintang / bujur, dan memiliki masalah khusus tentang kidal - apakah area di dalam area yang lebih kecil atau area yang lebih besar? Jawabannya adalah bahwa "arah" dari hal-hal vertikal penting - itu kidal atau tangan kanan dan dengan cara ini Anda dapat menunjukkan salah satu area sebagai "di dalam" setiap poligon yang diberikan. Karena itu, pekerjaan saya menggunakan solusi tiga yang disebutkan pada halaman itu.

Selain itu, pekerjaan saya menggunakan fungsi terpisah untuk pengujian "on the line".

... Karena seseorang bertanya: kami menemukan bahwa tes kotak pembatas adalah yang terbaik ketika jumlah vertikal melampaui beberapa angka - lakukan tes yang sangat cepat sebelum melakukan tes yang lebih lama jika perlu ... Kotak pembatas dibuat dengan hanya mengambil x terbesar, x terkecil, y terbesar dan y terkecil dan menyatukannya untuk membuat empat poin dari sebuah kotak ...

Kiat lain untuk mereka yang mengikuti: kami melakukan semua komputasi yang lebih canggih dan "meredupkan cahaya" dalam ruang grid semuanya dalam titik-titik positif pada pesawat dan kemudian memproyeksikan kembali ke garis bujur / lintang "nyata", sehingga menghindari kemungkinan kesalahan membungkus ketika satu garis silang 180 garis bujur dan ketika menangani wilayah kutub. Bekerja dengan baik!

Richard T
sumber
Bagaimana jika saya tidak memiliki kotak pembatas? :)
Scott Evernden
8
Anda dapat dengan mudah membuat kotak pembatas - hanya empat poin yang menggunakan x terbesar dan paling sedikit dan paling besar dan paling kecil. Lebih cepat lagi.
Richard T
"... menghindari kemungkinan kesalahan pembungkus ketika satu garis silang 180 garis bujur dan ketika menangani daerah kutub." dapatkah Anda menjelaskan hal ini secara lebih rinci? Saya pikir saya bisa mencari cara untuk memindahkan semuanya 'ke atas' untuk menghindari poligon saya melintasi 0 bujur, tapi saya tidak jelas tentang bagaimana menangani poligon yang mengandung salah satu kutub ...
tiritea
6

Jawaban David Segond adalah cukup banyak jawaban umum standar, dan Richard T adalah optimasi yang paling umum, meskipun ada beberapa yang lain. Optimalisasi kuat lainnya didasarkan pada solusi yang kurang umum. Sebagai contoh jika Anda akan memeriksa poligon yang sama dengan banyak poin, triangulasi poligon dapat mempercepat semuanya karena ada sejumlah algoritma pencarian TIN yang sangat cepat. Lain adalah jika poligon dan titik berada pada bidang terbatas pada resolusi rendah, katakanlah tampilan layar, Anda dapat melukis poligon ke buffer layar dipetakan memori dalam warna yang diberikan, dan memeriksa warna piksel yang diberikan untuk melihat apakah itu terletak dalam poligon.

Seperti banyak optimasi, ini didasarkan pada kasus-kasus spesifik dan bukan umum, dan menghasilkan keuntungan berdasarkan waktu diamortisasi daripada penggunaan tunggal.

Bekerja di bidang ini, saya menemukan Geometri Komputasi Joeseph O'Rourkes dalam C 'ISBN 0-521-44034-3 sangat membantu.

SmacL
sumber
4

Solusi sepele adalah dengan membagi poligon menjadi segitiga dan tekan uji segitiga seperti dijelaskan di sini

Jika poligon Anda adalah CONVEX mungkin ada pendekatan yang lebih baik. Lihatlah poligon sebagai kumpulan garis yang tak terbatas. Setiap garis membagi ruang menjadi dua. untuk setiap titik mudah untuk mengatakan apakah itu di satu sisi atau sisi lain dari garis. Jika suatu titik berada di sisi yang sama dari semua garis maka itu berada di dalam poligon.

shoosh
sumber
sangat cepat, dan dapat diterapkan pada bentuk yang lebih umum. kembali sekitar tahun 1990, kami memiliki "kurva" di mana beberapa sisi adalah busur melingkar. Dengan menganalisis sisi-sisi tersebut menjadi irisan melingkar dan sepasang segitiga yang bergabung dengan irisan ke titik asal (poligon sentroid), pengujian cepat cepat dan mudah.
DarenW
1
punya referensi tentang kurva ini?
shoosh
Saya juga akan senang wasit untuk curvigons.
Joel di Go
Anda melewatkan solusi penting untuk kasus poligon cembung: dengan membandingkan titik dengan diagonal, Anda dapat mengurangi separuh jumlah simpul. Dan mengulangi proses ini, Anda mengurangi menjadi satu segitiga tunggal dalam operasi Log (N) daripada N.
Yves Daoust
4

Saya menyadari ini sudah tua, tetapi di sini adalah algoritma pengecoran sinar diimplementasikan dalam Kakao, kalau-kalau ada yang tertarik. Tidak yakin itu adalah cara paling efisien untuk melakukan sesuatu, tetapi mungkin membantu seseorang.

- (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;
}
diatrevolo
sumber
5
Perhatikan bahwa jika Anda benar-benar melakukannya di Cocoa, maka Anda dapat menggunakan metode [NSBezierPath berisiPoint:].
ThomasW
4

Versi Obj-C dari jawaban nirg dengan metode sampel untuk poin pengujian. Jawaban Nirg bekerja dengan baik untuk saya.

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

sampel poligon

Jon
sumber
2
Tentu saja, di Objective-C, CGPathContainsPoint()adalah teman Anda.
Zev Eisenberg
@ZevEisenberg tapi itu terlalu mudah! Terima kasih atas catatannya. Saya akan menggali proyek itu di beberapa titik untuk melihat mengapa saya menggunakan solusi khusus. Saya mungkin tidak tahu tentangCGPathContainsPoint()
Jon
4

Tidak ada yang lebih baik daripada definisi induktif dari suatu masalah. Demi kelengkapan di sini Anda memiliki versi dalam prolog yang mungkin juga menjelaskan pemikiran di balik pengecoran sinar :

Berdasarkan simulasi algoritma kesederhanaan di http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

Beberapa predikat pembantu:

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

Persamaan garis yang diberikan 2 poin A dan B (Garis (A, B)) adalah:

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

Adalah penting bahwa arah rotasi untuk garis diatur ke clock-wise untuk batas dan anti-clock-wise untuk lubang. Kita akan memeriksa apakah titik (X, Y), yaitu titik yang diuji adalah di setengah bidang kiri dari garis kita (itu adalah masalah selera, itu juga bisa menjadi sisi kanan, tetapi juga arah batas) garis harus diubah dalam hal ini), ini adalah untuk memproyeksikan sinar dari titik ke kanan (atau ke kiri) dan mengakui persimpangan dengan garis. Kami telah memilih untuk memproyeksikan sinar ke arah horizontal (sekali lagi ini masalah selera, bisa juga dilakukan secara vertikal dengan pembatasan serupa), jadi kami memiliki:

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

Sekarang kita perlu tahu apakah titik di sisi kiri (atau kanan) dari segmen garis saja, bukan seluruh bidang, jadi kita perlu membatasi pencarian hanya untuk segmen ini, tetapi ini mudah karena berada di dalam segmen hanya satu titik dalam garis yang bisa lebih tinggi dari Y pada sumbu vertikal. Karena ini adalah batasan yang lebih kuat, ini perlu menjadi yang pertama untuk memeriksa, jadi kami mengambil hanya garis-garis pertama yang memenuhi persyaratan ini dan kemudian memeriksa kepemilikannya. Dengan teorema Jordan Curve, setiap sinar yang diproyeksikan ke poligon harus berpotongan pada sejumlah garis. Jadi kita sudah selesai, kita akan melempar sinar ke kanan dan kemudian setiap kali memotong garis, beralih ke keadaan. Namun dalam implementasi kami, kami akan memeriksa panjang paket solusi yang memenuhi batasan yang diberikan dan memutuskan hubungan keanggotaan di dalamnya. untuk setiap baris dalam poligon ini harus dilakukan.

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).
jdavid_1385
sumber
3

Versi C # jawaban nirg ada di sini: Saya hanya akan membagikan kodenya. Mungkin menghemat waktu seseorang.

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;
        }
Uğur Gümüşhan
sumber
ini bekerja di sebagian besar kasus tetapi itu salah dan tidak berfungsi dengan baik selalu! gunakan solusi dari M Katz yang benar
Lukas Hanacek
3

Versi Java:

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

}
YongJiang Zhang
sumber
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;
        }
    }
Aladar
sumber
2

VBA VERSION:

Catatan: Ingat bahwa jika poligon Anda adalah area dalam peta yang Latitude / Longitude adalah nilai Y / X yang bertentangan dengan X / Y (Latitude = Y, Longitude = X) karena dari apa yang saya pahami adalah implikasi historis dari jalan kembali ketika Bujur bukan ukuran.

MODUL KELAS: 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
Colin Stadig
sumber
2

Aku sudah membuat implementasi Python dari nirg ini c ++ kode :

Input

  • bounding_points: node yang membentuk poligon.
  • bounding_box_positions: kandidat poin untuk disaring. (Dalam implementasi saya dibuat dari kotak pembatas.

    (Input adalah daftar tupel dalam format: [(xcord, ycord), ...])

Kembali

  • Semua titik yang ada di dalam poligon.
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

Sekali lagi, ide diambil dari sini

Noresourses
sumber
2

Tidak ada yang terkejut yang mengemukakan ini sebelumnya, tetapi bagi para pragmatis yang membutuhkan database: MongoDB memiliki dukungan yang sangat baik untuk pertanyaan Geo termasuk yang ini.

Apa yang Anda cari adalah:

db.neolitans.findOne ({geometry: {$ geoIntersects: {$ geometry: {type: "Point", koordinat: ["bujur", "latitude"]}}}})

Neighborhoodsadalah koleksi yang menyimpan satu atau lebih poligon dalam format GeoJson standar. Jika kueri mengembalikan nol, itu tidak berpotongan sebaliknya.

Didokumentasikan dengan sangat baik di sini: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/

Kinerja lebih dari 6.000 poin yang diklasifikasikan dalam 330 grid poligon beraturan kurang dari satu menit tanpa optimasi sama sekali dan termasuk waktu untuk memperbarui dokumen dengan poligon masing-masing.

Santiago M. Quintero
sumber
1

Inilah poin dalam tes poligon dalam C yang tidak menggunakan ray-casting. Dan itu dapat bekerja untuk area yang tumpang tindih (persimpangan diri), lihat use_holesargumennya.

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

Catatan: ini adalah salah satu metode yang kurang optimal karena mencakup banyak panggilan atan2f, tetapi mungkin menarik bagi pengembang yang membaca utas ini (dalam pengujian saya ~ 23x lebih lambat daripada menggunakan metode persimpangan garis).

gagasanman42
sumber
0

Untuk mendeteksi hit pada Polygon kita perlu menguji dua hal:

  1. Jika Point berada di dalam area poligon. (dapat dicapai oleh Algoritma Ray-Casting)
  2. Jika Point berada di perbatasan poligon (dapat dicapai dengan algoritma yang sama yang digunakan untuk deteksi titik pada polyline (line)).
VJ
sumber
0

Untuk menangani kasus khusus berikut dalam algoritma pengecoran Ray :

  1. Sinar tumpang tindih dengan salah satu sisi poligon.
  2. Titik tersebut berada di dalam poligon dan sinar melewati titik puncak poligon.
  3. Intinya berada di luar poligon dan sinar hanya menyentuh salah satu sudut poligon.

Periksa Menentukan Apakah Suatu Titik Di Dalam Poligon Yang Kompleks . Artikel ini menyediakan cara mudah untuk menyelesaikannya sehingga tidak ada perawatan khusus yang diperlukan untuk kasus-kasus di atas.

Justin
sumber
0

Anda dapat melakukan ini dengan memeriksa apakah area yang terbentuk dengan menghubungkan titik yang diinginkan ke simpul poligon Anda cocok dengan area poligon itu sendiri.

Atau Anda dapat memeriksa apakah jumlah sudut dalam dari titik Anda ke setiap pasangan dari dua simpul poligon berurutan ke titik pemeriksaan Anda berjumlah 360, tetapi saya merasa bahwa opsi pertama lebih cepat karena tidak melibatkan pembagian atau perhitungan kebalikan dari fungsi trigonometri.

Saya tidak tahu apa yang terjadi jika poligon Anda memiliki lubang di dalamnya, tetapi menurut saya ide utama dapat disesuaikan dengan situasi ini.

Anda juga dapat memposting pertanyaan di komunitas matematika. Saya yakin mereka memiliki satu juta cara untuk melakukan itu

pengguna5193682
sumber
0

Jika Anda mencari perpustakaan java-script ada ekstensi google maps javascript google v3 untuk kelas Polygon untuk mendeteksi apakah suatu titik berada di dalamnya.

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

Google Extention Github

shana
sumber
0

Ketika menggunakan (Qt 4.3+), salah satu dapat menggunakan QPolygon ini fungsi containsPoint

Peter
sumber
0

Jawabannya tergantung pada apakah Anda memiliki poligon yang sederhana atau kompleks. Poligon sederhana tidak boleh memiliki persimpangan segmen garis. Jadi mereka bisa memiliki lubang tetapi garis tidak bisa saling silang. Daerah kompleks dapat memiliki persimpangan garis - sehingga mereka dapat memiliki daerah yang tumpang tindih, atau daerah yang saling bersinggungan hanya dengan satu titik.

Untuk poligon sederhana, algoritma terbaik adalah algoritma Ray casting (Crossing number). Untuk poligon yang rumit, algoritma ini tidak mendeteksi titik yang ada di dalam wilayah yang tumpang tindih. Jadi untuk poligon kompleks Anda harus menggunakan algoritma bilangan Winding.

Berikut ini adalah artikel yang sangat baik dengan implementasi C dari kedua algoritma. Saya mencobanya dan mereka bekerja dengan baik.

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

Timmy_A
sumber
0

Versi scala solusi oleh nirg (mengasumsikan pre-check rectangle rectangle dilakukan secara terpisah):

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)
}
Michael-7
sumber
0

Ini adalah versi golang dari jawaban @nirg (terinspirasi oleh kode C # oleh @@ 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
}
SamTech
sumber