1セル/ピクセルのシードからラスター内のランダムな形状のセルの塊を作成しますか?


11

タイトルにあるように、ラスター内のシードからセルの塊を「成長」させたいと思っています。私のベースラスターは1と0でいっぱいです。1は土地を示し、0は海/ NAエリアを示します。1から、シードとして60個のランダムなピクセル/セルを選択し、事前定義されたnoの接続された塊をランダムに成長させます。そのシードからのピクセル/セルの制限。この技法は「スプレッドダイ」と呼ばれることもあると聞きましたが、それについて多くを見つける運はありませんでした。シードセルの値は2になり、周囲の1から選択された次のセルも2に変換されます。2は、将来変換するために使用できなくなります。

RでのGISデータの読み取りと操作に精通しているので、Rでもこれを喜んで行うため、このスレッドは少し役立ちます。ただし、必要なのは、既存の束を囲むピクセルをランダムに選択する一連のルールです。

GIS設定でセルオートマトンのこのより基本的な形式を誰かが行った場合、アドバイスやガイダンスをいただければ幸いです。

例:

ターゲットは250セルです。値が1のセルをランダムに選択します。これは値2になります。次に、シードセルの隣接セルの1つが2になります。次に、いずれかのセルの隣接セルの1つが2の値が選択され、2になります。これは、250セルの番号が付けられた連続形状に達するまで続きます。

編集:さらなる質問

whuberの素晴らしい答えに基づいて、コードについていくつか質問があります。

  1. 成長したセルの値を、それらが作成された順序を表す変数値ではなく、単に「2」に割り当てるにはどうすればよいですか?
  2. 「1」の領域内に60個のセルの塊を作成する必要があります。ランダムな開始位置を選択する方法を考案しましexpandたが、親切に書いた関数を使用してすべてをループ内で動作させるのに苦労しています。互いに衝突せず、同じ最終マトリックス内に含まれる60個のクランプを作成する方法を提案できますか?

編集:問題の詳細な説明

セルの各塊は、定義されたサイズ、たとえば250セルの保護領域を表します。各エリアは、陸地を表す値1のセルから開始して成長し、海を表す値0のセルを避ける必要があります。ヌルモデルを作成するために、各反復で60の保護された領域でこれを1000回繰り返す必要があり、これらの領域の分布が偶然になることを示します。このため、60の領域すべてにわたるセルの総数は、1000回の反復のそれぞれで同一である必要があるため、比較可能です。したがって、エリアが接触していても問題ありませんが、衝突がある場合、理想的には、ターゲットの250に到達するまで、クランプは別の利用可能な方向に成長します。

これらの1000の保護されたエリアネットワークのそれぞれが作成されると、それらは生物多様性測定などの他のラスターデータに対するマスクとして使用され、(a)特定の種の範囲と交差するかどうか、および(b)これらのランダムなネットワークの範囲の特定の種保護地域のカバー。

これまでの@whuberの支援に感謝します。私を助けるためにもっと時間を費やすことは期待していませんが、あなたが要求したように私の状況を明確にしてみようと思いました。


R以外に、この分析に使用したい他のプログラミング言語/ソフトウェアは何ですか?
アーロン

また、ArcGISまたはQGISを使用することもできます。残念ながら、私はpythonにそれほど詳しくありません。bashターミナルを介したGDALも可能です。
JPD

回答:


12

他のプラットフォームでどのようにアプローチされるかを説明するためRに、少し無意味にコーディングされたソリューションを提供しRます。

R(および他のプラットフォーム、特に関数型プログラミングスタイルを好むプラットフォーム)の懸念は、大きな配列を絶えず更新すると非常に高価になる可能性があることです。代わりに、このアルゴリズムは、(a)これまでに入力されたすべてのセルがリストされ、(b)選択できるすべてのセル(入力されたセルの周囲)の独自のプライベートデータ構造を維持しますリストされています。このデータ構造の操作は、配列に直接インデックスを付けるよりも効率的ではありませんが、変更されたデータを小さなサイズに保つことで、計算時間が大幅に短縮されます。(のために最適化するための努力も行われていません。R内で作業を続けたい場合、状態ベクトルの事前割り当ては実行時間をいくらか節約するはずですR。)

コードはコメント化されており、読みやすくする必要があります。アルゴリズムをできるだけ完全にするために、結果をプロットするために最後を除いてアドオンを使用しません。唯一のトリッキーな部分は、効率と単純さのために、1Dインデックスを使用して2Dグリッドにインデックスを付けることを好みます。neighbors関数内で変換が行われます。この変換では、セルのアクセス可能な隣接セルを特定し、それらを1Dインデックスに変換するために2Dインデックスが必要です。この変換は標準であるため、他のGISプラットフォームでは列と行のインデックスの役割を逆にしたい場合があることを指摘する以外は、これについてはコメントしません。(ではR、列インデックスが変更される前に行インデックスが変更されます。)

説明のために、このコードは、x土地とアクセスできないポイントの川のような特徴を表すグリッドを取り、そのグリッドの特定の位置(5、21)(川の下部ベンド近く)で開始し、250ポイントをカバーするようにランダムに拡張します。合計タイミングは0.03秒です。(アレイのサイズが10,000行から3000行、5000列の係数で増加した場合、タイミングは0.09秒(3倍程度)になり、このアルゴリズムのスケーラビリティを示します。) 0、1、2のグリッドを出力するだけで、新しいセルが割り当てられたシーケンスを出力します。図では、最も初期の細胞は緑色で、金色からサケ色に変化しています。

地図

各セルの8ポイント近傍が使用されていることは明らかです。他の近傍の場合、単にnbrhoodの開始付近の値を変更しますexpand。これは、任意のセルに関連するインデックスオフセットのリストです。たとえば、「D4」近傍はとして指定できますmatrix(c(-1,0, 1,0, 0,-1, 0,1), nrow=2)

この拡散方法には問題があることも明らかです。それは、穴を残します。それが意図したものではない場合、この問題を修正するさまざまな方法があります。たとえば、使用可能なセルをキューに入れて、見つかった最も古いセルが最も早く埋められるようにします。一部のランダム化は引き続き適用できますが、使用可能なセルは均一(等しい)確率で選択されなくなります。もう1つのより複雑な方法は、使用可能なセルを選択することです。使用可能なセルは、セルが満たされている隣接セルの数によって異なります。セルが囲まれると、選択の可能性が非常に高くなるため、空いている穴がほとんどなくなります。

最後に、これはセルオートマトン(CA)ではなく、セルごとに進行するのではなく、各世代のセルのスワス全体を更新するというコメントで終わります。違いは微妙です。CAでは、セルの選択確率は均一ではありません。

#
# Expand a patch randomly within indicator array `x` (1=unoccupied) by
# `n.size` cells beginning at index `start`.
#
expand <- function(x, n.size, start) {
  if (x[start] != 1) stop("Attempting to begin on an unoccupied cell")
  n.rows <- dim(x)[1]
  n.cols <- dim(x)[2]
  nbrhood <- matrix(c(-1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1), nrow=2)
  #
  # Adjoin one more random cell and update `state`, which records
  # (1) the immediately available cells and (2) already occupied cells.
  #
  grow <- function(state) {
    #
    # Find all available neighbors that lie within the extent of `x` and
    # are unoccupied.
    #
    neighbors <- function(i) {
      n <- c((i-1)%%n.rows+1, floor((i-1)/n.rows+1)) + nbrhood
      n <- n[, n[1,] >= 1 & n[2,] >= 1 & n[1,] <= n.rows & n[2,] <= n.cols, 
             drop=FALSE]             # Remain inside the extent of `x`.
      n <- n[1,] + (n[2,]-1)*n.rows  # Convert to *vector* indexes into `x`.
      n <- n[x[n]==1]                # Stick to valid cells in `x`.
      n <- setdiff(n, state$occupied)# Remove any occupied cells.
      return (n)
    }
    #
    # Select one available cell uniformly at random.
    # Return an updated state.
    #
    j <- ceiling(runif(1) * length(state$available))
    i <- state$available[j]
    return(list(index=i,
                available = union(state$available[-j], neighbors(i)),
                occupied = c(state$occupied, i)))
  }
  #
  # Initialize the state.
  # (If `start` is missing, choose a value at random.)
  #
  if(missing(start)) {
    indexes <- 1:(n.rows * n.cols)
    indexes <- indexes[x[indexes]==1]
    start <- sample(indexes, 1)
  }
  if(length(start)==2) start <- start[1] + (start[2]-1)*n.rows
  state <- list(available=start, occupied=c())
  #
  # Grow for as long as possible and as long as needed.
  #
  i <- 1
  indices <- c(NA, n.size)
  while(length(state$available) > 0 && i <= n.size) {
    state <- grow(state)
    indices[i] <- state$index
    i <- i+1
  }
  #
  # Return a grid of generation numbers from 1, 2, ... through n.size.
  #
  indices <- indices[!is.na(indices)]
  y <- matrix(NA, n.rows, n.cols)
  y[indices] <- 1:length(indices)
  return(y)
}
#
# Create an interesting grid `x`.
#
n.rows <- 3000
n.cols <- 5000
x <- matrix(1, n.rows, n.cols)
ij <- sapply(1:n.cols, function(i) 
      c(ceiling(n.rows * 0.5 * (1 + exp(-0.5*i/n.cols) * sin(8*i/n.cols))), i))
x[t(ij)] <- 0; x[t(ij - c(1,0))] <- 0; x[t(ij + c(1,0))] <- 0
#
# Expand around a specified location in a random but reproducible way.
#
set.seed(17)
system.time(y <- expand(x, 250, matrix(c(5, 21), 1)))
#
# Plot `y` over `x`.
#
library(raster)
plot(raster(x[n.rows:1,], xmx=n.cols, ymx=n.rows), col=c("#2020a0", "#f0f0f0"))
plot(raster(y[n.rows:1,] , xmx=n.cols, ymx=n.rows), 
     col=terrain.colors(255), alpha=.8, add=TRUE)

少し変更を加えると、ループオーバーexpandして複数のクラスターを作成できます。クラスタを識別子で区別することをお勧めします。識別子はここで2、3、...などを実行します。

まず、(a)エラーがある場合は最初の行expandに戻りNA、(b)indicesmatrixではなくinの値を返すように変更しますy。(y呼び出しごとに新しいマトリックスを作成するのに時間を浪費しないでください。)この変更により、ループが簡単になります。ランダムな開始を選択し、その周りに拡大して、indices成功した場合はクラスターインデックスを蓄積し、完了するまで繰り返します。ループの重要な部分は、多数の連続するクラスターが見つからない場合に反復回数を制限することcount.maxです。これはで行われます。

60個のクラスター中心がランダムに均一に選択される例を次に示します。

size.clusters <- 250
n.clusters <- 60
count.max <- 200
set.seed(17)
system.time({
  n <- n.rows * n.cols
  cells.left <- 1:n
  cells.left[x!=1] <- -1 # Indicates occupancy of cells
  i <- 0
  indices <- c()
  ids <- c()
  while(i < n.clusters && length(cells.left) >= size.clusters && count.max > 0) {
    count.max <- count.max-1
    xy <- sample(cells.left[cells.left > 0], 1)
    cluster <- expand(x, size.clusters, xy)
    if (!is.na(cluster[1]) && length(cluster)==size.clusters) {
      i <- i+1
      ids <- c(ids, rep(i, size.clusters))
      indices <- c(indices, cluster)
      cells.left[indices] <- -1
    }                
  }
  y <- matrix(NA, n.rows, n.cols)
  y[indices] <- ids
})
cat(paste(i, "cluster(s) created.", sep=" "))

310 x 500のグリッドに適用した場合の結果を以下に示します(クラスターが見えるように十分に小さく粗くします)。実行には2秒かかります。3100 x 5000グリッド(100倍大きい)ではより長い時間(24秒)かかりますが、タイミングはかなりうまくスケーリングされています。(C ++などの他のプラットフォームでは、タイミングはグリッドサイズにほとんど依存しません。)

60クラスター


こんにちは、Whuber。これに感謝し、本当に感謝しています。私は少し実験をしているところですが、すぐにいくつかのフォローアップの質問があります。
JPD

+1 GIS SEに関するより複雑な質問のいくつかに、このような徹底的な回答を提供していただきありがとうございます。
レーダー

@whuber。あなたの答えに基づいて質問を少し拡大しました。再度、感謝します!
JPD

1
#1への答えは簡単です:行を置き換えるy[indices] <- 1:length(indices)ことでy[indices] <- 2。#2への答えはほとんど単純です:ただループします。
whuber

@whuber。更新していただきありがとうございます。塊の衝突の問題があり、その結果、塊のサイズがで定義された数よりも小さくなりsize.clustersます。現時点では、塊が正しいサイズに成長することを確認する方法はありますか?次に、60の塊の生成を1000回繰り返して、平均的なヌルモデルスタイルのデータセットを作成します。ランダムな位置はforループ内で毎回異なりますか?
JPD

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.