Làm cách nào để kết hợp các đa giác phức tạp?


81

Cho hai đa giác:

POLYGON((1 0, 1 8, 6 4, 1 0))
POLYGON((4 1, 3 5, 4 9, 9 5, 4 1),(4 5, 5 7, 6 7, 4 4, 4 5))

Làm cách nào để tính toán liên hợp (đa giác kết hợp)?

nhập mô tả hình ảnh ở đây

Ví dụ của Dave sử dụng máy chủ SQL để tạo liên hợp, nhưng tôi cần phải hoàn thành điều tương tự trong mã. Tôi đang tìm kiếm một công thức toán học hoặc ví dụ mã bằng bất kỳ ngôn ngữ nào thể hiện phép toán thực tế. Tôi đang cố gắng tạo ra các bản đồ kết hợp linh hoạt các quốc gia thành các khu vực. Tôi đã hỏi một câu hỏi liên quan ở đây: Nhóm các hình dạng địa lý

Câu trả lời:


67

Đây là một câu hỏi rất hay. Tôi đã triển khai thuật toán tương tự trên c # một thời gian trước. Thuật toán xây dựng một đường bao chung của hai đa giác (tức là Tạo một liên hợp không có lỗ). Nó đây.


Mục tiêu

Bước 1. Tạo đồ thị mô tả các đa giác.

Đầu vào: đa giác thứ nhất (n điểm), đa giác thứ hai (m điểm). Đầu ra: đồ thị. Đỉnh - đa giác điểm giao điểm.

Chúng ta nên tìm các giao lộ. Lặp lại tất cả các cạnh của đa giác trong cả hai đa giác [O (n * m)] và tìm bất kỳ giao điểm nào.

  • Nếu không tìm thấy giao điểm, chỉ cần thêm các đỉnh và nối chúng với cạnh.

  • Nếu tìm thấy bất kỳ giao lộ nào, hãy sắp xếp chúng theo độ dài đến điểm bắt đầu, thêm tất cả các đỉnh (bắt đầu, kết thúc và giao điểm) và nối chúng (đã được sắp xếp theo thứ tự) với cạnh. Đồ thị

Bước 2. Kiểm tra đồ thị đã xây dựng

Nếu chúng tôi không tìm thấy bất kỳ giao điểm nào khi xây dựng biểu đồ, chúng tôi có một trong các điều kiện sau:

  1. Polygon1 chứa polygon2 - return polygon1
  2. Polygon2 chứa polygon1 - return polygon2
  3. Polygon1 và polygon2 không cắt nhau. Trả về đa giác1 VÀ đa giác2.

Bước 3. Tìm đỉnh dưới cùng bên trái.

Tìm tọa độ x và y nhỏ nhất (minx, miny). Sau đó tìm khoảng cách nhỏ nhất giữa (minx, miny) và các điểm của đa giác. Điểm này sẽ là điểm dưới cùng bên trái.

Điểm dưới cùng bên trái

Bước 4. Dựng đường bao chung.

Chúng tôi bắt đầu xem qua biểu đồ từ điểm dưới cùng bên trái và tiếp tục cho đến khi chúng tôi quay trở lại nó. Lúc đầu, chúng tôi đánh dấu tất cả các cạnh là không được kiểm tra. Trên mỗi lần lặp, bạn nên chọn điểm tiếp theo và đánh dấu điểm đó là đã truy cập.

Để chọn điểm tiếp theo, hãy chọn một cạnh có góc bên trong lớn nhất theo hướng ngược chiều kim đồng hồ.

Tôi tính toán hai vectơ: vectơ1 cho cạnh hiện tại và vectơ2 cho mỗi cạnh chưa xem tiếp theo (như được trình bày trong hình).

Đối với vectơ tôi tính:

  1. Sản phẩm vô hướng (sản phẩm chấm). Nó trả về một giá trị liên quan đến một góc giữa các vectơ.
  2. Sản phẩm vector (sản phẩm chéo). Nó trả về một vector mới. Nếu tọa độ z của vectơ này là dương, tích vô hướng cho tôi góc vuông theo hướng ngược chiều kim đồng hồ. Khác (tọa độ z là âm), tôi tính toán góc giữa các vectơ là 360 - góc từ tích vô hướng.

Kết quả là tôi nhận được một cạnh (và một đỉnh tiếp theo tương ứng) với góc lớn nhất.

Tôi thêm vào danh sách kết quả từng đỉnh đã qua. Danh sách kết quả là đa giác liên hợp. Vectơ

Nhận xét

  1. Thuật toán này cho phép chúng ta hợp nhất nhiều đa giác - để áp dụng lặp đi lặp lại với các cặp đa giác.
  2. Nếu bạn có một đường bao gồm nhiều đường cong và đường cong hơn, bạn nên làm phẳng đường này trước.

2
Tôi nghĩ rằng cần phải đề cập rằng để so sánh các tích vô hướng, các vectơ nên được chuẩn hóa trước khi tính toán tích vô hướng của chúng (tức là chia tọa độ vectơ cho độ dài của nó). Nhưng dù sao, cảm ơn cho câu trả lời này.
eyalzba

Thuật toán này có tên hay do bạn tự sáng tạo?
Andrés Oviedo

Tôi đọc nó ở đâu đó, nhưng bây giờ tôi không nhớ ở đâu và khi =)
xtmq

LƯU Ý: Xem liên hợp đa giác không có lỗ , biểu đồ này cho thấy một sơ đồ khác: hai đa giác chồng lên nhau, NHƯNG có một "lỗ" mà cả hai đều không che được. Theo nhận xét của @ xtmq ở đó, thuật toán này "lấp đầy" lỗ hổng đó (mặc dù nó không phải là một phần của đa giác đầu vào). Thay vào đó, nếu bạn muốn "giữ lại" các lỗ đó dưới dạng lỗ, thì (a) tính toán các lỗ và (b) trả về "tập hợp các lỗ" [Trên một số hệ thống / chế độ đồ họa, các lỗ này có thể được bao gồm trong tập đa giác đầu ra , và sẽ dẫn đến lỗ khi vẽ] ....
ToolmakerSteve

2
... Để thực hiện "(a) tính toán các lỗ", hãy tìm các điểm chưa bao giờ được ghé thăm bởi "Bước 4. Xây dựng đường bao chung". Sử dụng một trong những điểm này để "bắt đầu" lỗ. Thực hiện thuật toán "đường bao" tương tự, loại trừ bất kỳ điểm nào đã được đa giác đầu ra chính sử dụng. Đa giác kết quả là một "lỗ". Lặp lại cho đến khi TẤT CẢ các điểm đã được bao gồm trong một số đa giác hoặc lỗ.
ToolmakerSteve

11

Đây là một chủ đề đầy thách thức nhưng được hiểu rõ, thường được đặt dưới tên "phép toán Boolean chính quy trên đa giác." Bạn có thể nhìn vào câu trả lời MathOverflow này , bao gồm hình bên dưới (từ thư viện cắt ghép của Alan Murta ), với kết hợp màu hồng là kết hợp của OP :


      BooleanOps



2
Anh chàng này thực sự đã viết cuốn sách về điều này;)
Constantin

6

Bạn cần xác định điểm nào nằm bên trong . Sau khi loại bỏ những điểm này, bạn có thể chèn một tập hợp các điểm "bên ngoài" vào tập hợp khác. Các điểm chèn của bạn (ví dụ: nơi bạn có mũi tên trong hình bên phải) là nơi bạn phải xóa các điểm khỏi bộ đầu vào.


1
+1 để liên kết với Bourke. Ba mươi giây chậm hơn và I'd've đánh đập bạn nó :)
David Seiler

4

Câu hỏi hay! Tôi chưa bao giờ thử điều này trước đây, nhưng tôi sẽ bắt kịp nó ngay bây giờ.

Đầu tiên: Bạn cần biết vị trí hai hình này chồng lên nhau. Để làm điều này, bạn có thể nhìn vào mọi cạnh trong Đa giác A và xem nơi nó giao nhau và cạnh trong Đa giác B. Trong ví dụ này, phải có hai điểm giao nhau.

Sau đó: Tạo hình công đoàn. Bạn có thể lấy tất cả các đỉnh ở A và B, cũng như các điểm giao nhau, sau đó loại trừ các đỉnh nằm trong hình dạng cuối cùng. Để tìm những điểm này, có vẻ như bạn chỉ cần tìm bất kỳ đỉnh nào của A nằm trong B và bất kỳ đỉnh nào của B nằm trong A.


Có câu hỏi thực sự là làm thế nào để chúng tôi tính toán hai điểm giao nhau thêm vào đó ?
Pacerier


2

Một giải pháp tôi đã thấy bằng cách sử dụng cây BSP được mô tả ở đây .

Về cơ bản, nó mô tả giao điểm dưới dạng liên hợp các cạnh của đa giác A nằm bên trong đa giác B (bao gồm cả các cạnh một phần và được tính toán bằng cách sử dụng cây BSP ). Sau đó, bạn có thể định nghĩa A  /  B là ~ (~ A  / \ ~ B ), trong đó ~ biểu thị sự đảo ngược chiều quanh co của đa giác, / biểu thị sự kết hợp và / \ biểu thị giao điểm.


1

Khi nhóm các quốc gia, tôi hy vọng không có sự chồng chéo - bạn có thể sử dụng một thuật toán khá ngây thơ để tìm kiếm các đỉnh được chia sẻ - một chế độ xem đơn giản sẽ là lặp qua các điểm trên một đa giác, xem nó có nằm trên bất kỳ đa giác nào khác của bạn không và chia sẻ cùng một điểm tiếp theo hoặc trước đó để xem có trùng khớp hay không. Sau đó, chỉ cần xóa đỉnh được chia sẻ để tạo liên hợp của bạn


2
"Khi nhóm các quốc gia, tôi hy vọng không có sự chồng chéo" ... không phải tất cả các quốc gia đều đồng ý về biên giới của riêng mình hoặc các nước láng giềng, mặc dù sẽ rất tốt nếu họ làm như vậy.
FrustratedWithFormsDesigner

2
@FrustratedWithFormsDesigner thực sự, nhưng hầu hết các nhà vẽ bản đồ sẽ gán khu vực tranh chấp cho đồng minh chính trị của họ hoặc như một thực thể riêng biệt theo đúng nghĩa của nó - đó cũng là lý do tại sao tôi mô tả thuật toán của mình là ngây thơ ...
Rowland Shaw


1

Đây là câu hỏi rất cũ nhưng chức năng Union_ từ Boost đã làm việc cho tôi.

Xem đoạn mã này bên dưới:

#include <iostream>
#include <vector>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>

#include <boost/foreach.hpp>


int main()
{
    typedef boost::geometry::model::polygon<boost::geometry::model::d2::point_xy<double> > polygon;

    polygon green, blue;

    boost::geometry::read_wkt(
        "POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))", green);

    boost::geometry::read_wkt(
        "POLYGON((5 5, 5 15, 15 15, 15 5, 5 5))", blue);

    std::vector<polygon> output;
    boost::geometry::union_(green, blue, output);

    int i = 0;
    std::cout << "green || blue:" << std::endl;
    BOOST_FOREACH(polygon const& p, output)
    {
        std::cout << i++ << ": " << boost::geometry::area(p) << std::endl;

        for (int i = 0; i < p.outer().size(); i++)
        {
            std::cout << p.outer().at(i).x() << " " << p.outer().at(i).y() << std::endl;
        }
    }



    return 0;
}

Hãy nhớ "sửa" các đa giác của bạn nếu cần thiết. Xem stackoverflow.com/questions/22258784/…
anumi

1

Tôi đã gặp phải vấn đề tương tự và tôi đã giải quyết vấn đề bằng cách sử dụng cách sau

Cython wrapper cho bản dịch C ++ của thư viện Clipper của Angus Johnson (phiên bản 6.4.2) https://github.com/fonttools/pyclipper

pc = pyclipper.Pyclipper()
def get_poly_union(polygons):
    pc.AddPaths(polygons, pyclipper.PT_SUBJECT, True)
    solution = pc.Execute(pyclipper.CT_UNION, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO)
    return solution[0]

print_image = image.copy()
solution = get_poly_union(polygons_array) 
#polygons_array=[polygon,polygon,polygon, ...,polygon] and polygon=[point,point,point...,point]

cv2.drawContours(print_image, [np.asarray(solution)], -1, (0, 255, 0), 2)

plt.imshow(print_image)

Clipper có sẵn trực tiếp bằng c ++ tại đây: angusj.com/delphi/clipper.php
Catskul
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.