Làm cách nào tôi có thể phát hiện các vùng nước được kết nối (nhưng khác biệt về mặt logic) trong bản đồ 2D?


17

Tôi có một bản đồ lưới lục giác 2D. Mỗi ô hex có giá trị chiều cao được sử dụng để xác định xem đó là nước hay đại dương. Tôi đang cố gắng nghĩ ra một cách hay để xác định và dán nhãn cho nước. Đại dương và biển nội địa rất dễ dàng (sử dụng thuật toán lấp lũ).

Nhưng những gì về cơ thể của nước như Địa Trung Hải ? Các khối nước được gắn vào những cái lớn hơn (trong đó "biển" và "vịnh" chỉ khác nhau bởi kích thước của lỗ mở)?

Đây là một ví dụ về những gì tôi đang cố gắng phát hiện (phần thân màu xanh của nước ở giữa hình ảnh, nên được dán nhãn khác với phần lớn hơn của đại dương bên trái, mặc dù được kết nối về mặt kỹ thuật): bản đồ thế giới

Có ý kiến ​​gì không?

Câu trả lời:


10

Những gì bạn đang mô tả là vấn đề phân khúc . Tôi rất tiếc phải nói rằng đó thực sự là một vấn đề chưa được giải quyết. Nhưng một phương pháp tôi muốn giới thiệu cho nó là thuật toán dựa trên đồ thị . Biểu đồ cắt biểu thị hình ảnh dưới dạng biểu đồ của các nút được kết nối cục bộ. Nó chia các thành phần được kết nối của đồ thị theo cách đệ quy sao cho đường viền giữa hai thành phần phụ có độ dài tối thiểu bằng cách sử dụng định lý Max-Flow-min-cut và thuật toán Ford Fulkerson .

Về cơ bản, bạn kết nối tất cả các gạch nước thành một biểu đồ. Gán trọng lượng cho các cạnh trong biểu đồ tương ứng với sự khác biệt giữa các ô nước liền kề. Tôi nghĩ trong trường hợp của bạn, tất cả các trọng số có thể là 1. Bạn sẽ phải chơi với các chế độ trọng lượng khác nhau để có được kết quả mong muốn. Ví dụ, bạn có thể phải thêm một số trọng lượng bao gồm kề.

Sau đó, tìm tất cả các thành phần được kết nối của biểu đồ. Đây là những biển / hồ rõ ràng và như vậy.

Cuối cùng, đối với mỗi thành phần được kết nối, chia nhỏ đệ quy thành phần sao cho các cạnh kết nối hai thành phần phụ mới có trọng lượng tối thiểu . Giữ phân chia đệ quy cho đến khi tất cả các thành phần phụ đạt kích thước tối thiểu (nghĩa là kích thước tối đa của biển) hoặc nếu các cạnh cắt hai thành phần có trọng lượng quá cao. Cuối cùng, nhãn tất cả các thành phần kết nối còn lại.

Những gì điều này sẽ làm trong thực tế là cắt biển với nhau tại các kênh, nhưng không vượt qua các đại dương lớn.

Đây là mã giả:

function SegmentGraphCut(Map worldMap, int minimumSeaSize, int maximumCutSize)
    Graph graph = new Graph();
    // First, build the graph from the world map.
    foreach Cell cell in worldMap:
        // The graph only contains water nodes
        if not cell.IsWater():
            continue;

        graph.AddNode(cell);

        // Connect every water node to its neighbors
        foreach Cell neighbor in cell.neighbors:
            if not neighbor.IsWater():
                continue;
            else:  
                // The weight of an edge between water nodes should be related 
                // to how "similar" the waters are. What that means is up to you. 
                // The point is to avoid dividing bodies of water that are "similar"
                graph.AddEdge(cell, neighbor, ComputeWeight(cell, neighbor));

   // Now, subdivide all of the connected components recursively:
   List<Graph> components = graph.GetConnectedComponents();

   // The seas will be added to this list
   List<Graph> seas = new List<Graph>();
   foreach Graph component in components:
       GraphCutRecursive(component, minimumSeaSize, maximumCutSize, seas);


// Recursively subdivides a component using graph cut until all subcomponents are smaller 
// than a minimum size, or all cuts are greater than a maximum cut size
function GraphCutRecursive(Graph component, int minimumSeaSize, int maximumCutSize, List<Graph> seas):
    // If the component is too small, we're done. This corresponds to a small lake,
    // or a small sea or bay
    if(component.size() <= minimumSeaSize):
        seas.Add(component);
        return;

    // Divide the component into two subgraphs with a minimum border cut between them
    // probably using the Ford-Fulkerson algorithm
    [Graph subpartA, Graph subpartB, List<Edge> cut] = GetMinimumCut(component);

    // If the cut is too large, we're done. This corresponds to a huge, bulky ocean
    // that can't be further subdivided
    if (GetTotalWeight(cut) > maximumCutSize):
        seas.Add(component);
        return;
    else:
        // Subdivide each of the new subcomponents
        GraphCutRecursive(subpartA, minimumSeaSize, maximumCutSize);
        GraphCutRecursive(subpartB, minimumSeaSize, maximumCutSize);

EDIT : Nhân tiện, đây là thuật toán sẽ làm gì với ví dụ của bạn với kích thước biển tối thiểu được đặt thành khoảng 40, với kích thước cắt tối đa là 1, nếu tất cả các trọng số cạnh là 1:

Imgur

Bằng cách chơi với các tham số, bạn có thể nhận được kết quả khác nhau. Chẳng hạn, kích thước cắt tối đa là 3, sẽ dẫn đến nhiều vịnh khác được chạm khắc từ các biển chính và biển số 1 sẽ bị chia nhỏ ở một nửa phía bắc và phía nam. Kích thước biển tối thiểu là 20 sẽ dẫn đến việc biển trung tâm cũng bị chia đôi.


có vẻ mạnh mẽ chắc chắn nghĩ xúi giục.
v.oddou

Cảm ơn bạn rất nhiều cho bài viết này. Tôi đã xoay sở để có được thứ gì đó hợp lý từ ví dụ của bạn
Kaelan Cooter

6

Một cách nhanh chóng và bẩn thỉu để xác định một khối nước riêng biệt nhưng được kết nối sẽ là thu nhỏ tất cả các vùng nước và xem liệu các khoảng trống xuất hiện.

Trong ví dụ trên tôi nghĩ rằng việc loại bỏ tất cả các khối nước có 2 hoặc ít hơn các khối nước được kết nối với chúng (được đánh dấu màu đỏ) sẽ cung cấp cho bạn kết quả mong muốn cộng với một số nhiễu cạnh. Sau khi bạn đã dán nhãn cho các cơ quan, bạn có thể "chảy" nước về trạng thái ban đầu và lấy lại các viên gạch đã bị loại bỏ cho các cơ quan hiện đang tách biệt.

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

Một lần nữa, đây là một giải pháp nhanh chóng và bẩn thỉu, nó có thể không đủ tốt cho các giai đoạn sản xuất sau này nhưng nó sẽ đủ để "làm việc này ngay bây giờ" và chuyển sang một tính năng khác.


5

Đây là một thuật toán hoàn chỉnh mà tôi nghĩ sẽ tạo ra kết quả tốt.

  1. Thực hiện xói mòn hình thái trên diện tích mặt nước - có nghĩa là, tạo một bản sao của bản đồ mà trên đó mỗi ngói được coi là nước duy nhất nếu nó và tất cả các quốc gia láng giềng (hoặc một khu vực lớn hơn, nếu bạn có con sông rộng hơn một gạch) là nước . Điều này sẽ dẫn đến tất cả các dòng sông biến mất hoàn toàn.

    (Điều này sẽ coi nước đảo ở phần bên trái của biển nội địa của bạn là sông. Nếu đây là một vấn đề, bạn có thể sử dụng một quy tắc khác như câu trả lời của một vrinek đề xuất thay vào đó, các bước sau sẽ vẫn hoạt động miễn là bạn có một số loại xóa sông sông Bước ở đây.)

  2. Tìm các thành phần nước được kết nối của bản đồ bị xói mòn và cung cấp cho mỗi người một nhãn duy nhất . (Tôi giả sử bạn đã biết cách thực hiện việc này.) Cái này bây giờ dán nhãn mọi thứ trừ sông và nước bờ (nơi xói mòn có ảnh hưởng).

  3. Đối với mỗi ô nước trong bản đồ gốc , hãy tìm các nhãn có trên các ô nước lân cận trong bản đồ bị xói mòn và sau đó:

    • Nếu bản thân gạch có nhãn trong bản đồ bị xói mòn, thì đó là nước giữa biển; cho nó nhãn trong bản đồ gốc.
    • Nếu bạn chỉ tìm thấy một nhãn lân cận riêng biệt, thì đó là bờ hoặc cửa sông; cho nó cái nhãn đó
    • Nếu bạn tìm thấy không có nhãn, thì đó là một dòng sông; để nó một mình.
    • Nếu bạn tìm thấy nhiều nhãn, thì đó là một nút cổ chai ngắn giữa hai cơ thể lớn hơn; bạn có thể muốn coi nó giống như một dòng sông hoặc kết hợp hai cơ thể dưới một nhãn hiệu duy nhất.

    (Lưu ý rằng đối với bước này, bạn phải giữ các lưới nhãn riêng biệt (hoặc có hai trường nhãn trong một cấu trúc) cho bản đồ bị xói mòn (mà bạn đọc từ đó) và bản gốc (mà bạn viết), hoặc sẽ có vòng lặp- lỗi phụ thuộc đơn hàng.)

  4. Nếu bạn cũng muốn gắn nhãn các dòng sông riêng lẻ, thì sau các bước trên, hãy tìm tất cả các thành phần được kết nối còn lại của nước không nhãn và dán nhãn chúng.


1

Theo ý tưởng của vrinek, làm thế nào về việc trồng đất (hoặc thu nhỏ nước) để các bộ phận ban đầu bạn được kết nối sẽ bị ngắt kết nối sau khi đất được trồng?

Điều này có thể được thực hiện như vậy:

  1. Xác định bạn muốn trồng bao nhiêu đất: 1 hex? 2 hình lục giác? Giá trị này làn

  2. Ghé thăm tất cả các nút đất và đặt tất cả các nút lân cận của chúng lên độ sâu ntới các nút đất (ghi vào một bản sao, để không nhận được một vòng lặp vô hạn)

  3. Chạy lại thuật toán lấp đầy ban đầu của bạn để xác định những gì hiện được kết nối và những gì không


0

Bạn có một ý tưởng sơ bộ về nơi vịnh là? Nếu vậy, bạn có thể sửa đổi vùng lấp đầy của mình để theo dõi số lượng ô lân cận nhưng chưa được khám phá (cùng với danh sách các ô được truy cập). Nó bắt đầu bằng 6 trong bản đồ hex và bất cứ khi nào giá trị đó giảm xuống dưới một điểm nhất định, thì bạn biết bạn đang nhấn "mở".

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.