Tôi đang cố gắng tạo một điểm 2D nhanh bên trong thuật toán đa giác, để sử dụng trong kiểm tra lần truy cập (ví dụ Polygon.contains(p:Point)
). Đề xuất cho các kỹ thuật hiệu quả sẽ được đánh giá cao.
Tôi đang cố gắng tạo một điểm 2D nhanh bên trong thuật toán đa giác, để sử dụng trong kiểm tra lần truy cập (ví dụ Polygon.contains(p:Point)
). Đề xuất cho các kỹ thuật hiệu quả sẽ được đánh giá cao.
Câu trả lời:
Đối với đồ họa, tôi không thích số nguyên. Nhiều hệ thống sử dụng số nguyên để vẽ UI (pixel là ints), nhưng macOS chẳng hạn sử dụng float cho mọi thứ. macOS chỉ biết các điểm và một điểm có thể dịch thành một pixel, nhưng tùy thuộc vào độ phân giải màn hình, nó có thể dịch sang một điểm khác. Trên màn hình võng mạc, một nửa điểm (0,5 / 0,5) là pixel. Tuy nhiên, tôi chưa bao giờ nhận thấy rằng các giao diện người dùng macOS chậm hơn đáng kể so với các giao diện người dùng khác. Sau khi tất cả các API 3D (OpenGL hoặc Direct3D) cũng hoạt động với các float và các thư viện đồ họa hiện đại thường rất lợi dụng khả năng tăng tốc GPU.
Bây giờ bạn nói tốc độ là mối quan tâm chính của bạn, được rồi, chúng ta hãy đi cho tốc độ. Trước khi bạn chạy bất kỳ thuật toán tinh vi nào, trước tiên hãy làm một bài kiểm tra đơn giản. Tạo một hộp giới hạn căn chỉnh trục xung quanh đa giác của bạn. Điều này rất dễ dàng, nhanh chóng và có thể an toàn cho bạn rất nhiều tính toán. Làm thế nào mà làm việc? Lặp lại tất cả các điểm của đa giác và tìm giá trị min / max của X và Y.
Ví dụ, bạn có điểm (9/1), (4/3), (2/7), (8/2), (3/6)
. Điều này có nghĩa là Xmin là 2, Xmax là 9, Ymin là 1 và Ymax là 7. Một điểm nằm ngoài hình chữ nhật có hai cạnh (2/1) và (9/7) không thể nằm trong đa giác.
// 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!
}
Đây là thử nghiệm đầu tiên để chạy cho bất kỳ điểm nào. Như bạn có thể thấy, bài kiểm tra này cực nhanh nhưng cũng rất thô. Để xử lý các điểm nằm trong hình chữ nhật giới hạn, chúng ta cần một thuật toán phức tạp hơn. Có một vài cách để có thể tính toán điều này. Phương pháp nào hoạt động cũng phụ thuộc vào thực tế nếu đa giác có thể có lỗ hoặc sẽ luôn rắn. Dưới đây là ví dụ về những vật rắn (một lồi, một lõm):
Và đây là một lỗ hổng:
Cái màu xanh có một cái lỗ ở giữa!
Thuật toán đơn giản nhất, có thể xử lý cả ba trường hợp trên và vẫn còn khá nhanh được đặt tên là đúc tia . Ý tưởng của thuật toán khá đơn giản: Vẽ một tia ảo từ bất cứ nơi nào bên ngoài đa giác đến điểm của bạn và tính tần suất nó chạm vào một bên của đa giác. Nếu số lần truy cập là chẵn thì nó nằm ngoài đa giác, nếu là số lẻ thì nó nằm bên trong.
Các thuật toán số quanh co sẽ là một sự thay thế, đó là chính xác hơn cho điểm là rất gần với một dòng đa giác nhưng nó cũng chậm hơn nhiều. Quá trình truyền tia có thể thất bại đối với các điểm quá gần với một đa giác vì độ chính xác của điểm nổi và các vấn đề làm tròn bị hạn chế, nhưng trong thực tế đó không phải là vấn đề, vì nếu một điểm nằm gần một bên, nó thường không thể nhìn thấy được người xem nhận ra nếu nó đã ở bên trong hay vẫn ở bên ngoài.
Bạn vẫn còn hộp giới hạn ở trên, nhớ không? Chỉ cần chọn một điểm bên ngoài hộp giới hạn và sử dụng nó làm điểm bắt đầu cho tia của bạn. Ví dụ, điểm (Xmin - e/p.y)
nằm ngoài đa giác cho chắc chắn.
Nhưng là e
gì? Chà, e
(thực ra là epsilon) cung cấp cho hộp giới hạn một số phần đệm . Như tôi đã nói, việc dò tia không thành công nếu chúng ta bắt đầu quá gần với một đường đa giác. Vì hộp giới hạn có thể bằng đa giác (nếu đa giác là hình chữ nhật thẳng hàng trục, hộp giới hạn bằng chính đa giác!), Chúng tôi cần một số phần đệm để đảm bảo an toàn, đó là tất cả. Bạn nên chọn lớn như thế nào e
? Không quá lớn. Nó phụ thuộc vào quy mô hệ thống tọa độ bạn sử dụng để vẽ. Nếu chiều rộng bước pixel của bạn là 1.0, thì chỉ cần chọn 1.0 (nhưng 0.1 cũng sẽ hoạt động tốt)
Bây giờ chúng ta có tia với tọa độ bắt đầu và kết thúc của nó, vấn đề chuyển từ " là điểm trong đa giác " sang " tần số của tia giao nhau với một đa giác ". Do đó, chúng ta không thể chỉ làm việc với các điểm đa giác như trước đây, bây giờ chúng ta cần các mặt thực tế. Một bên luôn được xác định bởi hai điểm.
side 1: (X1/Y1)-(X2/Y2)
side 2: (X2/Y2)-(X3/Y3)
side 3: (X3/Y3)-(X4/Y4)
:
Bạn cần phải kiểm tra các tia chống lại tất cả các bên. Coi tia là một vectơ và mọi phía là vectơ. Tia phải đánh mỗi bên chính xác một lần hoặc không bao giờ. Nó không thể đánh cùng một bên hai lần. Hai đường trong không gian 2D sẽ luôn giao nhau chính xác một lần, trừ khi chúng song song, trong trường hợp đó chúng không bao giờ giao nhau. Tuy nhiên, vì các vectơ có độ dài giới hạn, hai vectơ có thể không song song và vẫn không bao giờ giao nhau vì chúng quá ngắn để không bao giờ gặp nhau.
// 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
}
Cho đến nay rất tốt, nhưng làm thế nào để kiểm tra nếu hai vectơ cắt nhau? Đây là một số mã C (chưa được kiểm tra), nên thực hiện thủ thuật:
#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;
}
Các giá trị đầu vào là hai điểm cuối của vectơ 1 ( v1x1/v1y1
và v1x2/v1y2
) và vectơ 2 ( v2x1/v2y1
và v2x2/v2y2
). Vậy bạn có 2 vectơ, 4 điểm, 8 tọa độ. YES
và NO
rõ ràng YES
tăng giao lộ, NO
không làm gì cả.
Thế còn COLLINEAR thì sao? Điều đó có nghĩa là cả hai vectơ nằm trên cùng một đường thẳng vô hạn, tùy thuộc vào vị trí và độ dài, chúng không giao nhau chút nào hoặc chúng giao nhau trong vô số điểm. Tôi không chắc chắn làm thế nào để xử lý trường hợp này, tôi sẽ không tính nó là giao lộ. Chà, dù sao thì trường hợp này khá hiếm trong thực tế vì lỗi làm tròn dấu phẩy động; mã tốt hơn có thể sẽ không kiểm tra == 0.0f
mà thay vào đó là một cái gì đó như < epsilon
, trong đó epsilon là một con số khá nhỏ.
Nếu bạn cần kiểm tra số lượng điểm lớn hơn, bạn chắc chắn có thể tăng tốc toàn bộ một chút bằng cách giữ các dạng phương trình tuyến tính chuẩn của các cạnh đa giác trong bộ nhớ, do đó bạn không phải tính toán lại các điểm này mỗi lần. Điều này sẽ giúp bạn tiết kiệm hai phép nhân dấu phẩy động và ba phép trừ điểm nổi trên mỗi bài kiểm tra để đổi lấy việc lưu trữ ba giá trị dấu phẩy động trên mỗi cạnh đa giác trong bộ nhớ. Đó là một ký ức điển hình so với thời gian tính toán đánh đổi.
Cuối cùng nhưng không kém phần quan trọng: Nếu bạn có thể sử dụng phần cứng 3D để giải quyết vấn đề, có một sự thay thế thú vị. Chỉ cần để GPU làm tất cả công việc cho bạn. Tạo một bề mặt tranh mà tắt màn hình. Điền nó hoàn toàn với màu đen. Bây giờ, hãy để OpenGL hoặc Direct3D vẽ đa giác của bạn (hoặc thậm chí tất cả các đa giác của bạn nếu bạn chỉ muốn kiểm tra xem điểm có nằm trong bất kỳ điểm nào trong số chúng không, nhưng bạn không quan tâm đến cái nào) và điền vào đa giác bằng một khác màu, ví dụ màu trắng. Để kiểm tra xem một điểm có nằm trong đa giác hay không, hãy lấy màu của điểm này từ bề mặt bản vẽ. Đây chỉ là một bộ nhớ O (1).
Tất nhiên phương pháp này chỉ có thể sử dụng được nếu bề mặt vẽ của bạn không phải là rất lớn. Nếu nó không thể vừa với bộ nhớ GPU, phương pháp này chậm hơn so với thực hiện trên CPU. Nếu nó phải rất lớn và GPU của bạn hỗ trợ các trình đổ bóng hiện đại, bạn vẫn có thể sử dụng GPU bằng cách thực hiện việc truyền tia được trình bày ở trên dưới dạng trình tạo bóng GPU, điều này hoàn toàn có thể. Đối với số lượng đa giác lớn hơn hoặc số lượng điểm lớn để kiểm tra, điều này sẽ được đền đáp, hãy xem xét một số GPU sẽ có thể kiểm tra song song 64 đến 256 điểm. Tuy nhiên, xin lưu ý rằng việc truyền dữ liệu từ CPU sang GPU và trở lại luôn tốn kém, vì vậy, chỉ cần thử nghiệm một vài điểm so với một vài đa giác đơn giản, trong đó các điểm hoặc đa giác đều động và sẽ thay đổi thường xuyên, cách tiếp cận GPU sẽ hiếm khi trả tiền tắt.
Tôi nghĩ rằng đoạn mã sau đây là giải pháp tốt nhất (lấy từ đây ):
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;
}
Nó vừa ngắn vừa hiệu quả, vừa hoạt động cho đa giác lồi và lõm. Như đã đề xuất trước đó, bạn nên kiểm tra hình chữ nhật giới hạn trước và xử lý các lỗ đa giác riêng biệt.
Ý tưởng đằng sau này khá đơn giản. Tác giả mô tả nó như sau:
Tôi chạy một tia bán vô hạn theo chiều ngang (tăng x, cố định y) từ điểm kiểm tra và đếm xem nó có bao nhiêu cạnh. Ở mỗi lần giao nhau, tia chuyển đổi giữa bên trong và bên ngoài. Đây được gọi là định lý đường cong Jordan.
Biến c đang chuyển từ 0 sang 1 và 1 sang 0 mỗi lần tia ngang đi qua bất kỳ cạnh nào. Vì vậy, về cơ bản, nó theo dõi xem số lượng các cạnh được chẵn hay lẻ. 0 có nghĩa là chẵn và 1 có nghĩa là lẻ.
verty[i]
và verty[j]
là một trong hai phía testy
, vì vậy chúng không bao giờ bằng nhau.
Đây là phiên bản C # của câu trả lời được đưa ra bởi nirg , xuất phát từ giáo sư RPI này . Lưu ý rằng việc sử dụng mã từ nguồn RPI đó yêu cầu ghi công.
Một kiểm tra hộp giới hạn đã được thêm vào đầu. Tuy nhiên, như James Brown chỉ ra, mã chính gần như nhanh như chính hộp kiểm tra giới hạn, vì vậy kiểm tra hộp giới hạn thực sự có thể làm chậm hoạt động tổng thể, trong trường hợp hầu hết các điểm bạn đang kiểm tra đều nằm trong hộp giới hạn . Vì vậy, bạn có thể để hộp kiểm tra giới hạn, hoặc một giải pháp thay thế sẽ là tính toán trước các hộp giới hạn của đa giác của bạn nếu chúng không thay đổi hình dạng quá thường xuyên.
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;
}
Đây là một biến thể JavaScript của câu trả lời của M. Katz dựa trên phương pháp của 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;
}
Tính tổng các góc định hướng giữa điểm p và từng điểm đa giác. Nếu tổng góc định hướng là 360 độ, điểm nằm bên trong. Nếu tổng bằng 0, điểm nằm ngoài.
Tôi thích phương pháp này tốt hơn vì nó mạnh hơn và ít phụ thuộc vào độ chính xác của số.
Các phương pháp tính toán đồng đều số lượng giao lộ bị giới hạn bởi vì bạn có thể 'chạm' một đỉnh trong quá trình tính toán số lượng giao lộ.
EDIT: Bằng cách này, phương pháp này hoạt động với đa giác lõm và lồi.
EDIT: Gần đây tôi đã tìm thấy một bài viết toàn bộ Wikipedia về chủ đề này.
Câu hỏi này rất thú vị. Tôi có một ý tưởng khả thi khác với các câu trả lời khác cho bài viết này. Ý tưởng là sử dụng tổng số góc để quyết định mục tiêu là bên trong hay bên ngoài. Được gọi là số quanh co .
Gọi x là điểm đích. Đặt mảng [0, 1, .... n] là tất cả các điểm của khu vực. Kết nối điểm mục tiêu với mọi điểm biên bằng một đường. Nếu điểm mục tiêu là bên trong khu vực này. Tổng của tất cả các góc sẽ là 360 độ. Nếu không các góc sẽ nhỏ hơn 360.
Tham khảo hình ảnh này để có được sự hiểu biết cơ bản về ý tưởng:
Thuật toán của tôi giả định theo chiều kim đồng hồ là hướng tích cực. Đây là một đầu vào tiềm năng:
[[-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]]
Sau đây là mã python thực hiện ý tưởng:
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
Các bài viết Eric Haines trích dẫn bởi bobobobo thực sự tuyệt vời. Đặc biệt thú vị là các bảng so sánh hiệu suất của các thuật toán; phương pháp tổng hợp góc thực sự xấu so với các phương pháp khác. Điều thú vị nữa là việc tối ưu hóa như sử dụng lưới tra cứu để chia nhỏ đa giác thành các phần "vào" và "ra" có thể thực hiện bài kiểm tra cực kỳ nhanh ngay cả trên đa giác có> 1000 cạnh.
Dù sao, đó là những ngày đầu nhưng phiếu bầu của tôi thuộc về phương pháp "giao cắt", đó là khá nhiều những gì Mecki mô tả tôi nghĩ. Tuy nhiên, tôi thấy nó được mô tả và mã hóa một cách súc tích nhất bởi David Bourke . Tôi thích rằng không có lượng giác thực sự cần thiết, và nó hoạt động cho lồi và lõm, và nó thực hiện hợp lý khi số lượng các mặt tăng lên.
Nhân tiện, đây là một trong những bảng hiệu suất từ bài viết của Eric Haines để quan tâm, thử nghiệm trên các đa giác ngẫu nhiên.
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
Phiên bản Swift của câu trả lời của 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
}
}
Thực sự thích giải pháp được đăng bởi Nirg và được chỉnh sửa bởi bobobobo. Tôi chỉ làm cho nó thân thiện với javascript và dễ đọc hơn một chút để sử dụng:
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;
}
Tôi đã thực hiện một số công việc trên lưng này khi tôi là một nhà nghiên cứu dưới thời Michael Stonebraker - bạn biết đấy, giáo sư đã đưa ra Ingres , PostgreQuery , v.v.
Chúng tôi nhận ra rằng cách nhanh nhất là trước tiên làm một hộp giới hạn bởi vì nó cực nhanh. Nếu nó nằm ngoài hộp giới hạn, nó ở bên ngoài. Nếu không, bạn làm công việc khó khăn hơn ...
Nếu bạn muốn có một thuật toán tuyệt vời, hãy tìm đến mã nguồn PostgreSQL của dự án nguồn mở cho công việc địa lý ...
Tôi muốn chỉ ra rằng, chúng tôi không bao giờ có bất kỳ cái nhìn sâu sắc nào về tay phải và tay trái (cũng có thể biểu hiện như một vấn đề "bên trong" so với "bên ngoài" ...
CẬP NHẬT
Liên kết của BKB cung cấp một số lượng lớn các thuật toán hợp lý. Tôi đang nghiên cứu các vấn đề về Khoa học Trái đất và do đó cần một giải pháp hoạt động theo vĩ độ / kinh độ, và nó có vấn đề đặc biệt về sự thuận tay - là khu vực bên trong khu vực nhỏ hơn hay khu vực lớn hơn? Câu trả lời là "hướng" của các vấn đề đỉnh - đó là thuận tay trái hoặc tay phải và theo cách này bạn có thể chỉ ra một trong hai khu vực là "bên trong" bất kỳ đa giác nào. Như vậy, công việc của tôi đã sử dụng giải pháp ba liệt kê trên trang đó.
Ngoài ra, công việc của tôi đã sử dụng các chức năng riêng biệt cho các bài kiểm tra "trên đường".
... Vì có người hỏi: chúng tôi đã tìm ra rằng các thử nghiệm hộp giới hạn là tốt nhất khi số lượng đỉnh vượt quá số - hãy thực hiện một thử nghiệm rất nhanh trước khi thực hiện thử nghiệm dài hơn nếu cần thiết ... Một hộp giới hạn được tạo ra bằng cách đơn giản là lấy lớn nhất x, nhỏ nhất x, lớn nhất y và nhỏ nhất y và đặt chúng lại với nhau để tạo ra bốn điểm của một hộp ...
Một mẹo khác dành cho những người theo sau: chúng tôi đã thực hiện tất cả các tính toán tinh vi và "làm mờ ánh sáng" của chúng tôi trong một không gian lưới tất cả các điểm tích cực trên một mặt phẳng và sau đó chiếu lại vào kinh độ / vĩ độ "thực", do đó tránh được các lỗi có thể xảy ra quấn quanh khi một đường chéo 180 kinh độ và khi xử lý các vùng cực. Làm việc tuyệt vời!
Câu trả lời của David Segond gần như là câu trả lời chung tiêu chuẩn và Richard T là cách tối ưu hóa phổ biến nhất, mặc dù có một số khác. Tối ưu hóa mạnh mẽ khác dựa trên các giải pháp ít chung hơn. Ví dụ: nếu bạn định kiểm tra cùng một đa giác với rất nhiều điểm, thì việc sắp xếp đa giác có thể tăng tốc mọi thứ lên rất nhanh vì có một số thuật toán tìm kiếm TIN rất nhanh. Một cách khác là nếu đa giác và các điểm nằm trên một mặt phẳng giới hạn ở độ phân giải thấp, giả sử màn hình hiển thị, bạn có thể vẽ đa giác lên bộ đệm hiển thị được ánh xạ bộ nhớ theo một màu nhất định và kiểm tra màu của một pixel nhất định để xem nó có nằm không trong đa giác.
Giống như nhiều tối ưu hóa, những điều này dựa trên các trường hợp cụ thể chứ không phải chung và mang lại lợi ích dựa trên thời gian khấu hao thay vì sử dụng một lần.
Làm việc trong lĩnh vực này, tôi thấy Hình học tính toán của Joeseph O'Rourkes trong C 'ISBN 0-521-44034-3 là một trợ giúp tuyệt vời.
Giải pháp tầm thường sẽ là chia đa giác thành các hình tam giác và đánh thử các hình tam giác như được giải thích ở đây
Nếu đa giác của bạn là CONVEX, có thể có một cách tiếp cận tốt hơn. Nhìn vào đa giác như một tập hợp các dòng vô hạn. Mỗi dòng chia không gian thành hai. đối với mọi điểm, thật dễ dàng để nói nếu nó ở một bên hoặc bên kia của dòng. Nếu một điểm nằm trên cùng một phía của tất cả các dòng thì nó nằm trong đa giác.
Tôi nhận ra điều này đã cũ, nhưng đây là một thuật toán đúc tia được triển khai trong Ca cao, trong trường hợp bất kỳ ai cũng quan tâm. Không chắc chắn đó là cách hiệu quả nhất để làm mọi việc, nhưng nó có thể giúp đỡ ai đó.
- (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;
}
Phiên bản Obj-C của câu trả lời của nirg với phương pháp mẫu cho các điểm kiểm tra. Câu trả lời của Nirg đã làm việc tốt cho tôi.
- (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()
là bạn của bạn.
CGPathContainsPoint()
Không có gì đáng tin cậy hơn một định nghĩa quy nạp của một vấn đề. Để hoàn thiện ở đây, bạn có một phiên bản trong prolog, điều này cũng có thể làm rõ những ý tưởng đằng sau việc truyền tia :
Dựa trên mô phỏng thuật toán đơn giản trong http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Một số vị ngữ trợ giúp:
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]).
Phương trình của một đường thẳng cho 2 điểm A và B (Đường thẳng (A, B)) là:
(YB-YA)
Y - YA = ------- * (X - XA)
(XB-YB)
Điều quan trọng là hướng quay của đường được đặt ở vị trí đồng hồ cho ranh giới và chống đồng hồ cho lỗ. Chúng tôi sẽ kiểm tra xem điểm (X, Y), tức là điểm được kiểm tra nằm ở nửa mặt phẳng bên trái của đường thẳng của chúng tôi (đó là vấn đề của hương vị, nó cũng có thể là phía bên phải, nhưng cũng là hướng của ranh giới các đường phải được thay đổi trong trường hợp đó), điều này là để chiếu tia từ điểm sang phải (hoặc trái) và xác nhận giao điểm với đường. Chúng tôi đã chọn chiếu tia theo hướng ngang (một lần nữa, đó là vấn đề của hương vị, nó cũng có thể được thực hiện theo chiều dọc với các hạn chế tương tự), vì vậy chúng tôi có:
(XB-XA)
X < ------- * (Y - YA) + XA
(YB-YA)
Bây giờ chúng ta cần biết liệu điểm đó chỉ ở bên trái (hoặc bên phải) của phân đoạn dòng chứ không phải toàn bộ mặt phẳng, vì vậy chúng ta cần hạn chế tìm kiếm chỉ trong phân khúc này, nhưng điều này rất dễ vì nằm trong phân khúc chỉ một điểm trong đường thẳng có thể cao hơn Y trong trục tung. Vì đây là một hạn chế mạnh mẽ hơn nên nó phải là người đầu tiên kiểm tra, vì vậy chúng tôi chỉ thực hiện trước tiên những dòng đáp ứng yêu cầu này và sau đó kiểm tra quyền sở hữu của nó. Theo định lý Jordan Curve, bất kỳ tia nào chiếu tới đa giác đều phải cắt nhau tại một số dòng chẵn. Vậy là xong, chúng ta sẽ ném tia sang phải và sau đó mỗi khi nó cắt một đường, chuyển trạng thái của nó. Tuy nhiên, trong quá trình thực hiện, chúng tôi rất muốn kiểm tra chiều dài của túi giải pháp đáp ứng các hạn chế nhất định và quyết định sự vô hiệu đối với nó. đối với mỗi dòng trong đa giác, điều này phải được thực hiện.
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).
Phiên bản C # của câu trả lời của nirg có ở đây: Tôi sẽ chỉ chia sẻ mã. Nó có thể tiết kiệm một số thời gian.
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;
}
Phiên bản 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;
}
}
Cổng .Net:
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;
}
}
PHIÊN BẢN VBA:
Lưu ý: Hãy nhớ rằng nếu đa giác của bạn là một khu vực trong bản đồ thì Vĩ độ / Kinh độ là các giá trị Y / X trái ngược với X / Y (Vĩ độ = Y, Kinh độ = X) do những gì tôi hiểu là những ẩn ý lịch sử từ khi trở về Kinh độ không phải là một phép đo.
PHƯƠNG PHÁP LỚP: 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
BÀI:
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
Tôi đã thực hiện Python triển khai mã c ++ của nirg :
Đầu vào
ràng buộc_box_poseitions: điểm ứng cử viên để lọc. (Trong triển khai của tôi được tạo ra từ hộp giới hạn.
(Các đầu vào là danh sách các bộ dữ liệu ở định dạng [(xcord, ycord), ...]
:)
Trả về
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
Một lần nữa, ý tưởng được lấy từ đây
Ngạc nhiên không ai đưa ra điều này sớm hơn, nhưng đối với những người thực dụng đòi hỏi phải có cơ sở dữ liệu: MongoDB có hỗ trợ tuyệt vời cho các truy vấn Geo bao gồm cả truy vấn này.
Những gì bạn đang tìm kiếm là:
db.neQUs.findOne ({hình học: {$ GeoIntersects: {$ hình học: {loại: "Điểm", tọa độ: ["kinh độ", "vĩ độ"]}}}})
Neighborhoods
là bộ sưu tập lưu trữ một hoặc nhiều đa giác ở định dạng GeoJson tiêu chuẩn. Nếu truy vấn trả về null thì nó không được giao nhau.
Tài liệu rất tốt ở đây: https://docs.mongodb.com/manual/tutorial/geospatial-tutorial/
Hiệu suất cho hơn 6.000 điểm được phân loại trong lưới đa giác 330 không đều là chưa đầy một phút mà không tối ưu hóa gì cả và bao gồm cả thời gian để cập nhật tài liệu với đa giác tương ứng.
Đây là một điểm trong kiểm tra đa giác trong C không sử dụng phương pháp đúc tia. Và nó có thể làm việc cho các khu vực chồng chéo (tự giao nhau), xem use_holes
đối số.
/* 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];
}
Lưu ý: đây là một trong những phương pháp ít tối ưu hơn vì nó bao gồm rất nhiều cuộc gọi đến atan2f
, nhưng nó có thể được các nhà phát triển đọc luồng này quan tâm (trong các thử nghiệm của tôi, nó chậm hơn ~ 23 lần sau đó sử dụng phương pháp giao cắt đường).
Để phát hiện lần truy cập trên Đa giác, chúng tôi cần kiểm tra hai điều:
Để xử lý các trường hợp đặc biệt sau trong thuật toán đúc Ray :
Kiểm tra xác định xem một điểm có nằm trong đa giác phức tạp không . Bài viết cung cấp một cách dễ dàng để giải quyết chúng vì vậy sẽ không có điều trị đặc biệt cần thiết cho các trường hợp trên.
Bạn có thể làm điều này bằng cách kiểm tra xem khu vực được hình thành bằng cách kết nối điểm mong muốn với các đỉnh của đa giác của bạn có khớp với diện tích của đa giác không.
Hoặc bạn có thể kiểm tra xem tổng các góc bên trong từ điểm của bạn đến từng cặp hai đỉnh đa giác liên tiếp đến điểm kiểm tra của bạn có tính tổng đến 360 hay không, nhưng tôi có cảm giác rằng tùy chọn đầu tiên nhanh hơn vì nó không liên quan đến phép chia cũng như tính toán nghịch đảo của các hàm lượng giác.
Tôi không biết điều gì xảy ra nếu đa giác của bạn có một lỗ hổng bên trong nó nhưng dường như với tôi rằng ý tưởng chính có thể được điều chỉnh cho tình huống này
Bạn cũng có thể gửi câu hỏi trong một cộng đồng toán học. Tôi cá là họ có một triệu cách để làm điều đó
Nếu bạn đang tìm kiếm một thư viện java-script, có một phần mở rộng javascript google maps v3 cho lớp Polygon để phát hiện xem một điểm có nằm trong đó hay không.
var polygon = new google.maps.Polygon([], "#000000", 1, 1, "#336699", 0.3);
var isWithinPolygon = polygon.containsLatLng(40, -90);
Khi đang sử dụng qt(Qt 4.3+), người ta có thể sử dụng chức năng QPolygon của containsPoint
Câu trả lời phụ thuộc vào việc bạn có đa giác đơn giản hay phức tạp. Đa giác đơn giản không được có bất kỳ giao điểm phân đoạn dòng nào. Vì vậy, chúng có thể có các lỗ nhưng các đường không thể giao nhau. Các vùng phức tạp có thể có các giao điểm đường - vì vậy chúng có thể có các vùng chồng lấp hoặc các vùng chạm vào nhau chỉ bằng một điểm duy nhất.
Đối với đa giác đơn giản, thuật toán tốt nhất là thuật toán đúc Ray (Crossing number). Đối với các đa giác phức tạp, thuật toán này không phát hiện các điểm nằm trong các vùng chồng lấp. Vì vậy, đối với các đa giác phức tạp, bạn phải sử dụng thuật toán số Winding.
Đây là một bài viết tuyệt vời với C thực hiện cả hai thuật toán. Tôi đã thử chúng và chúng hoạt động tốt.
Phiên bản Scala của giải pháp bằng nirg (giả sử kiểm tra trước hình chữ nhật giới hạn được thực hiện riêng):
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)
}
Đây là phiên bản golang của câu trả lời @nirg (lấy cảm hứng từ mã C # của @@ 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
}