Bir 2B haritada bağlı (ancak mantıksal olarak farklı) su kütlelerini nasıl tespit edebilirim?


17

2B altıgen ızgara haritam var. Her altıgen hücrenin su veya okyanus olup olmadığını belirlemek için kullanılan bir yükseklik değeri vardır. Su kütlelerini belirlemek ve etiketlemek için iyi bir yol düşünmeye çalışıyorum. Okyanuslar ve iç denizler kolaydır (taşkın doldurma algoritması kullanarak).

Peki ya Akdeniz gibi su kütleleri ? Daha büyük olanlara bağlı su kütleleri ("denizler" ve "körfezler" sadece açıklığın büyüklüğüne göre değişir)?

Algılamaya çalıştığım şeyin bir örneği (teknik olarak bağlı olmasına rağmen, soldaki büyük okyanus gövdesinden farklı olarak etiketlenmesi gereken görüntünün ortasındaki mavi su kütlesi): Dünya haritası

Herhangi bir fikir?

Yanıtlar:


10

Açıkladığınız şey Segmentasyon Sorunu . Bunun aslında çözülmemiş bir sorun olduğunu söylediğim için üzgünüm. Ama bunun için tavsiye edebileceğim bir yöntem Graph-Cut tabanlı algoritma. Grafik Kes, görüntüyü yerel olarak bağlı düğümlerin grafiği olarak temsil eder. Grafiğin bağlı bileşenlerini, iki alt bileşen arasındaki sınırın Max-flow-min-cut teoremi ve Ford Fulkerson algoritması kullanılarak minimum uzunlukta olacağı şekilde yinelemeli olarak alt bölümlere ayırır .

Esasen, tüm su kiremitlerini bir grafiğe bağlarsınız. Bitişik su kiremitleri arasındaki farklara karşılık gelen grafikteki kenarlara ağırlık atayın. Ben sizin durumunuzda, tüm ağırlıklar 1 olabilir düşünüyorum. İstenilen bir sonuç elde etmek için farklı ağırlık düzenleri ile oynamak zorunda kalacak. Örneğin, kıyılara bitişiklik içeren bir ağırlık eklemeniz gerekebilir.

Ardından, grafiğin tüm bağlı bileşenlerini bulun. Bunlar bariz denizler / göller vb.

Son olarak, her bağlı bileşen için, iki yeni alt bileşeni birbirine bağlayan kenarlar minimum ağırlığa sahip olacak şekilde bileşeni özyineli olarak alt bölümlere ayırın . Tüm alt bileşenler minimum boyuta (yani bir denizin maksimum boyutu gibi) ulaşıncaya kadar veya iki bileşeni kesen kenarlar çok yüksek bir ağırlığa sahip oluncaya kadar tekrar tekrar alt gruplara ayırmaya devam edin. Son olarak, kalan tüm bağlı bileşenleri etiketleyin.

Bunun pratikte yapacağı şey, denizleri kanallardan birbirinden kesmek, ancak büyük okyanuslara yayılmamaktır.

İşte sözde kod:

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

DÜZENLEME : Bu arada, tüm kenar ağırlıkları 1 ise, minimum deniz boyutu 40'a, maksimum kesim boyutu 1'e ayarlanmış örnekle algoritmanın yapacağı şey:

Imgur

Parametrelerle oynayarak farklı sonuçlar elde edebilirsiniz. Örneğin en fazla 3 kesme boyutu, ana denizlerden oyulmuş çok sayıda körfezle sonuçlanacak ve 1 numaralı deniz, kuzey ve güneyde alt bölümlere ayrılacaktır. Minimum 20 deniz boyutu, merkezi denizin de yarıya bölünmesine neden olacaktır.


güçlü görünüyor. kesinlikle düşündürücü.
v.oddou

Bu gönderi için çok teşekkür ederim.
Örneğinizden

6

Ayrı ancak bağlantılı bir su kütlesini tanımlamanın hızlı ve kirli bir yolu, tüm su kütlelerini küçültmek ve boşlukların ortaya çıkıp çıkmadığını görmek olacaktır.

Yukarıdaki örnekte, kendilerine bağlı (veya kırmızı işaretli) 2 veya daha az su kiremitine sahip tüm su kiremitlerini kaldırmanın, size istenen sonuç artı bazı kenar gürültüsü sağlayacağını düşünüyorum. Gövdeleri etiketledikten sonra, suyu orijinal durumuna "aktarabilir" ve kaldırılan karoları şimdi ayrı gövdeler için geri alabilirsiniz.

resim açıklamasını buraya girin

Yine, bu hızlı ve kirli bir çözümdür, üretimin sonraki aşamaları için yeterince iyi olmayabilir, ancak "şimdilik bu işi almak" ve başka bir özelliğe geçmek yeterli olacaktır.


5

İşte iyi sonuçlar üreteceğini düşündüğüm eksiksiz bir algoritma.

  1. Su alanında morfolojik erozyon uygulayın - yani, her bir karonun su olarak kabul edildiği haritanın bir kopyasını oluşturun sadece ve tüm komşularının (veya bir karodan daha geniş nehirlere sahipseniz daha büyük bir alanın) su olması durumunda yapın. . Bu, tüm nehirlerin tamamen kaybolmasına neden olacaktır.

    (Bu, iç denizin sol tarafındaki adalı suyun nehir olduğunu düşünecektir. Bu bir sorunsa, vrinek'in yanıtı önerdiği gibi farklı bir kural kullanabilirsiniz. bunun yerine ; aşağıdaki adımlar sizin sürece devam edecektir burada bir çeşit “nehirleri sil” adımı bulun.)

  2. Aşınmış haritanın bağlı su bileşenlerini bulun ve her birine benzersiz bir etiket verin. (Bunu nasıl yapacağınızı zaten bildiğinizi varsayıyorum.) Bu şimdi nehirler ve kıyı suyu (erozyonun etkisi olduğu yerler) hariç her şeyi etiketliyor.

  3. Her su karo için orijinal harita üzerinde etiket hediye bulmak komşu , aşınmış haritada su fayanslarında bulunan ve ardından:

    • Eğer kiremit aşınmış haritada bir etiket varsa, o zaman deniz suyu; bu etiketi orijinal haritada verin.
    • Sadece tek bir komşu etiket bulursanız, o zaman kıyı veya nehir ağzıdır; bu etiketi ver.
    • Etiket bulamazsanız, o zaman bir nehirdir; yalnız bırakın.
    • Birden fazla etiket bulursanız, iki büyük gövde arasında kısa bir darboğazdır; ya bir nehir gibi düşünmek ya da iki cesedi tek bir etiket altında birleştirmek isteyebilirsiniz.

    (Bu adım için aşınmış harita (okuduğunuz) ve orijinal (yazdığınız) için ayrı etiket ızgaraları bulundurmanız (veya bir yapıda iki etiket alanı olması) gerektiğini veya yineleme olacağını unutmayın. siparişe bağlı hatalar.)

  4. Tek tek nehirleri de benzersiz bir şekilde etiketlemek istiyorsanız, yukarıdaki adımlardan sonra, etiketlenmemiş suyun kalan tüm bağlı bileşenlerini bulun ve etiketleyin.


1

Vrinek'in fikrini takiben, toprak büyüdükten sonra (veya suyu küçülterek) toprak büyüdükten sonra parçaların bağlantısı kesilecek mi?

Bu şu şekilde yapılabilir:

  1. Toprağı ne kadar büyütmek istediğinizi tanımlayın: 1 altıgen? 2 altıgen? Bu değern

  2. Tüm kara düğümlerini ziyaret edin ve tüm komşularını nkara düğümlerine kadar derinliğe ayarlayın (sonsuz bir döngü elde etmemek için bir kopyaya yazın)

  3. Neyin bağlı olduğunu ve neyin bağlı olmadığını belirlemek için orijinal taşkın doldurma algoritmanızı tekrar çalıştırın


0

Körfezin nerede olduğu hakkında kabaca bir fikrin var mı? Öyleyse, sel dolgunuzu, komşu ancak keşfedilmemiş hücrelerin sayısını (ziyaret edilen hücrelerin bir listesiyle birlikte) izlemek için değiştirebilirsiniz. Altıgen bir haritada 6 ile başlar ve bu değer belirli bir noktanın altına düştüğünde, bir "açıklığa" vurduğunuzu bilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.