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.
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.
Jawaban:
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):
Dan ini satu lubang:
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.
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/v1y1
dan v1x2/v1y2
) dan vektor 2 ( v2x1/v2y1
dan v2x2/v2y2
). Jadi, Anda memiliki 2 vektor, 4 poin, 8 koordinat. YES
dan NO
jelas. YES
meningkatkan persimpangan, NO
tidak 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.0f
tetapi 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.
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;
}
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.
verty[i]
dan verty[j]
kedua sisi testy
, sehingga mereka tidak pernah sama.
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;
}
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;
}
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.
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:
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
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
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
}
}
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;
}
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!
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.
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.
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;
}
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");
}
}
CGPathContainsPoint()
adalah teman Anda.
CGPathContainsPoint()
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).
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;
}
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;
}
}
.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;
}
}
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
Aku sudah membuat implementasi Python dari nirg ini c ++ kode :
Input
bounding_box_positions: kandidat poin untuk disaring. (Dalam implementasi saya dibuat dari kotak pembatas.
(Input adalah daftar tupel dalam format: [(xcord, ycord), ...]
)
Kembali
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
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"]}}}})
Neighborhoods
adalah 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.
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_holes
argumennya.
/* 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).
Untuk menangani kasus khusus berikut dalam algoritma pengecoran Ray :
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.
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
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);
Ketika menggunakan qt(Qt 4.3+), salah satu dapat menggunakan QPolygon ini fungsi containsPoint
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.
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)
}
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
}