Làm cách nào để xác định xem Điểm 2D có nằm trong Đa giác không?


497

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.


Bạn đã quên nói với chúng tôi về nhận thức của bạn về câu hỏi thuận tay phải hay tay trái - cũng có thể được hiểu là "bên trong" so với "bên ngoài" - RT
Richard T

13
Vâng, tôi nhận ra bây giờ câu hỏi để lại nhiều chi tiết không xác định, nhưng tại thời điểm này tôi rất thích nhìn thấy sự đa dạng của các câu trả lời.
Scott Evernden

4
Một đa giác 90 mặt được gọi là một enneacontagon và một đa giác 10.000 mặt được gọi là một đa giác.

"Thanh lịch nhất" nằm ngoài mục tiêu, vì tôi gặp khó khăn trong việc tìm kiếm một "công việc hoàn toàn" - thuật toán. Tôi phải tự mình tìm ra: stackoverflow.com/questions/14818567/iêu
davidkonrad

Câu trả lời:


732

Đố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):

Đa giác không có lỗ

Và đây là một lỗ hổng:

Đa giác có lỗ

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.

Trình diễn cách tia cắt qua đa giác

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à egì? 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/v1y1v1x2/v1y2) và vectơ 2 ( v2x1/v2y1v2x2/v2y2). Vậy bạn có 2 vectơ, 4 điểm, 8 tọa độ. YESNOrõ ràng YEStăng giao lộ, NOkhô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.0fmà 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.


26
+1 câu trả lời tuyệt vời. Khi sử dụng phần cứng để làm điều đó, tôi đã đọc ở những nơi khác rằng nó có thể bị chậm vì bạn phải lấy lại dữ liệu từ card đồ họa. Nhưng tôi thích nguyên tắc giảm tải CPU rất nhiều. Có ai có bất kỳ tài liệu tham khảo tốt nào về cách điều này có thể được thực hiện trong OpenGL không?
Gavin

3
+1 vì điều này quá đơn giản! Vấn đề chính là nếu đa giác và điểm kiểm tra của bạn xếp hàng trên một lưới (không phổ biến) thì bạn phải xử lý các giao điểm "trùng lặp", ví dụ, đi thẳng qua một điểm đa giác! (mang lại một chẵn lẻ hai thay vì một). Đi vào khu vực kỳ lạ này: stackoverflow.com/questions/2255842/ . Đồ họa máy tính có đầy đủ các trường hợp đặc biệt: đơn giản về lý thuyết, lông trong thực tế.
Jared Updike

7
@RMorrisey: Tại sao bạn nghĩ như vậy? Tôi không thấy làm thế nào nó sẽ thất bại cho một đa giác lõm. Tia có thể rời đi và nhập lại đa giác nhiều lần khi đa giác lõm, nhưng cuối cùng, bộ đếm trúng sẽ là số lẻ nếu điểm nằm trong và ngay cả khi nó ở bên ngoài, cũng cho đa giác lõm.
Mecki

6
'Thuật toán số cuộn nhanh', được mô tả tại softsurfer.com/Archive/alerskym_0103/alacticm_0103.htm hoạt động khá nhanh ...
SP

10
Việc sử dụng / để tách tọa độ x và y của bạn là khó hiểu, nó đọc là x chia cho y. Rõ ràng hơn nhiều khi sử dụng x, y (tức là x dấu phẩy y) Nhìn chung, một câu trả lời hữu ích.
Tro

583

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

Tranh luận

  • nvert : Số đỉnh trong đa giác. Có lặp lại đỉnh đầu tiên ở cuối hay không đã được thảo luận trong bài viết được đề cập ở trên.
  • vertx, verty : Mảng chứa tọa độ x và y của các đỉnh của đa giác.
  • testx, testy : X- và tọa độ y của điểm kiểm tra.

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ẻ.


5
Câu hỏi. Chính xác các biến mà tôi vượt qua nó là gì? Họ đại diện cho cái gì?
tekknolagi

9
@Mick Nó kiểm tra xem verty[i]verty[j]là một trong hai phía testy, vì vậy chúng không bao giờ bằng nhau.
Peter Wood

4
Mã này không mạnh mẽ và tôi không khuyên bạn nên sử dụng nó. Đây là một liên kết đưa ra một số phân tích chi tiết: www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf
Mikola

13
Cách tiếp cận này thực sự có giới hạn (trường hợp cạnh): Kiểm tra điểm (15,20) trong Đa giác [(10,10), (10,20), (20,20), (20,10)] sẽ trở lại sai thay vì đúng Tương tự với (10,20) hoặc (20,15). Đối với tất cả các trường hợp khác, thuật toán hoạt động hoàn toàn tốt và các phủ định sai trong các trường hợp cạnh đều ổn cho ứng dụng của tôi.
Alexander Pacha

10
@Alexander, thực tế là theo thiết kế: bằng cách xử lý các ranh giới trái và dưới theo nghĩa ngược lại với các ranh giới trên và phải, nếu hai đa giác riêng biệt chia sẻ một cạnh, bất kỳ điểm nào dọc theo cạnh này sẽ nằm ở một và chỉ một đa giác. .. một tài sản hữu ích.
phường

69

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

5
Hoạt động rất tốt, cảm ơn, tôi đã chuyển đổi sang JavaScript. stackoverflow.com/questions/217578/ từ
Philipp Lenssen

2
Tốc độ này nhanh hơn 1000 lần so với sử dụng GraphicsPath.IsVisible !! Kiểm tra hộp giới hạn làm cho chức năng chậm hơn khoảng 70%.
James Brown

Không chỉ GraphicsPath.IsVisible () chậm hơn mà còn không hoạt động tốt với các đa giác rất nhỏ có mặt trong phạm vi 0,01f
Patrick từ nhóm NDepend

50

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

32

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.


1
Không, điều này không đúng. Điều này hoạt động bất kể độ lồi của đa giác.
David Segonds

2
@DarenW: Chỉ một acos trên mỗi đỉnh; mặt khác, thuật toán này phải dễ thực hiện nhất trong SIMD vì nó hoàn toàn không có phân nhánh.
Jasper Bekkers

1
@emilio, nếu điểm cách xa tam giác, tôi không thấy góc tạo bởi điểm và hai điểm của tam giác sẽ là 90 độ.
David Segonds

2
Đầu tiên sử dụng kiểm tra hộp giới hạn để giải quyết các trường hợp "điểm là xa". Đối với trig, bạn có thể sử dụng các bảng được tạo trước.
JOM

3
Đây là giải pháp tối ưu, vì nó là O (n) và yêu cầu tính toán tối thiểu. Hoạt động cho tất cả các đa giác. Tôi đã nghiên cứu giải pháp này 30 năm trước tại công việc IBM đầu tiên của tôi. Họ đã ký vào đó và ngày nay vẫn sử dụng nó trong các công nghệ GIS của họ.
Đaminh Cerisano

24

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: nhập mô tả hình ảnh ở đây

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

21

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

11

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

Điều này có một sự phân chia tiềm năng bởi vấn đề không trong việc tính toán b. Chỉ cần tính "b" và dòng tiếp theo với "c" nếu phép tính cho "a" cho thấy có khả năng giao nhau. Không có khả năng nếu cả hai điểm ở trên hoặc cả hai điểm bên dưới - được mô tả bằng phép tính cho "a".
anorskdev

11

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

8

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!


Điều gì xảy ra nếu tôi không có hộp giới hạn? :)
Scott Evernden

8
Bạn có thể dễ dàng tạo một hộp giới hạn - đó chỉ là bốn điểm sử dụng lớn nhất và ít nhất x và lớn nhất và ít nhất y. Sớm hơn.
Richard T

"... tránh các lỗi có thể xảy ra khi quấn quanh một đường kinh độ 180 và khi xử lý các vùng cực." bạn có thể mô tả điều này chi tiết hơn? Tôi nghĩ rằng tôi có thể tìm ra cách di chuyển mọi thứ 'lên' để tránh đa giác của mình vượt qua kinh độ 0, nhưng tôi không rõ cách xử lý các đa giác có chứa một trong hai cực ...
tiritea

6

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.


4

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.


rất nhanh, và có thể được áp dụng cho các hình dạng tổng quát hơn. trở lại khoảng năm 1990, chúng ta đã có "đường cong" trong đó một số mặt là các cung tròn. Bằng cách phân tích các cạnh đó thành các hình nêm tròn và một cặp hình tam giác nối với nêm đến gốc (đa giác trung tâm), thử nghiệm nhấn rất nhanh và dễ dàng.
DarenW

1
Có bất kỳ tài liệu tham khảo về các curvigons?
shoosh

Tôi cũng thích một ref cho các curvigons.
Joel ở Gô

Bạn đã bỏ lỡ một giải pháp quan trọng cho trường hợp đa giác lồi: bằng cách so sánh điểm với đường chéo, bạn có thể giảm một nửa số đỉnh. Và lặp lại quá trình này, bạn giảm xuống một tam giác duy nhất trong các hoạt động Log (N) thay vì N.
Yves Daoust

4

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

5
Lưu ý rằng nếu bạn thực sự làm điều đó trong Ca cao, thì bạn có thể sử dụng phương thức [NSBezierPath chứaPoint:].
ThomasW

4

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

đa giác mẫu


2
Tất nhiên, trong Objective-C, CGPathContainsPoint()là bạn của bạn.
Zev Eisenberg

@ZevEisenberg nhưng điều đó sẽ quá dễ dàng! Cảm ơn đã lưu ý. Tôi sẽ đào dự án đó vào một lúc nào đó để xem tại sao tôi sử dụng giải pháp tùy chỉnh. Tôi có thể không biết vềCGPathContainsPoint()
Jon

4

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

3

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

Điều này hoạt động trong hầu hết các trường hợp nhưng nó sai và không hoạt động đúng luôn! sử dụng giải pháp từ M Katz là chính xác
Lukas Hanacek

3

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

}

2

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

2

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

2

Tôi đã thực hiện Python triển khai c ++ của nirg :

Đầu vào

  • ràng buộc_point: các nút tạo nên đa giác.
  • 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ề

  • Tất cả các điểm nằm trong đa giác.
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


2

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ĩ độ"]}}}})

Neighborhoodslà 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.


1

Đâ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).


0

Để 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:

  1. Nếu Điểm nằm trong khu vực đa giác. (có thể được thực hiện bằng thuật toán đúc Ray)
  2. Nếu Điểm nằm trên đường viền đa giác (có thể được thực hiện bằng cùng một thuật toán được sử dụng để phát hiện điểm trên đa tuyến (đường thẳng)).

0

Để xử lý các trường hợp đặc biệt sau trong thuật toán đúc Ray :

  1. Tia chồng lên một trong các cạnh của đa giác.
  2. Điểm nằm bên trong đa giác và tia đi qua một đỉnh của đa giác.
  3. Điểm nằm ngoài đa giác và tia chỉ chạm vào một trong các góc của đa giác.

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.


0

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 đó


0

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

Google mở rộng Github



0

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.

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


0

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

0

Đâ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
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.