从1个像元/像素的种子在栅格中创建随机形状的像元细胞?


11

就像我的标题所说,我希望从栅格中的种子“生长”成簇的细胞。我的基本栅格充满1和0,1代表陆地,0代表海洋/北美地区。我希望从1开始选择60个随机像素/单元作为我的种子,然后随机生长一个预定义编号的连接簇。该种子的像素/单元数限制。我听说该技术可以称为“散布染料”,但运气不好。种子单元将被设置为2,然后从周围的1中选择的下一个单元也将被转换为2。这样2将无法在将来进行转换。

这个线程确实有所帮助,因为我也很愿意在R中做到这一点,因为我熟悉在R中读取和操作GIS数据。但是,我需要的是一组规则,以随机选择围绕现有簇的像素。

如果有人在GIS环境中完成了这种更基本的细胞自动机形式,那么我将不胜感激。

例:

我的目标是250个单元格。我随机选择一个值为1的单元。将其转换为值2。然后,将= 1的种子单元的邻居之一转换为2。然后,将任一单元的邻居之一转换为值2。选择具有2值的值并将其变为2。这将继续进行,直到达到编号250个单元的连续形状为止。

编辑:进一步的问题

基于whuber的出色回答,我对代码有一些疑问:

  1. 我如何才能将已成长的单元格的值分配给一个“ 2”,而不是代表它们创建顺序的变量值?
  2. 我需要在“ 1”区域内创建60个单元格块。我已经设计出一些方法来选择随机的起始位置,但是很难使用expand您编写的函数使其在一个循环中全部正常工作。您能建议一种创建60个彼此不冲突且包含在同一最终矩阵中的块的方法吗?

编辑:问题的进一步解释

每个群集单元代表一个定义大小的保护区域,例如250个单元。每个区域都必须开始并成长为值为1的像元,因为它代表陆地,而要避免值为0的像元,因为这代表海洋。我需要在每次迭代中对60个受保护区域进行迭代1000次以创建一个空模型,以显示这些区域的偶然分布。因此,在1000次迭代中,所有60个区域中的单元总数必须相同,因此具有可比性。因此,如果区域接触就可以了,但是如果发生碰撞,则理想情况下,团块会朝另一个可用方向增长,直到达到250个目标为止。

一旦创建了这1000个保护区网络中的每一个,它们都将被用作掩盖其他栅格数据(例如生物多样性措施)的遮罩,以查看(a)它们是否与特定物种范围相交以及(b)特定物种占这些随机网络的百分比保护区的覆盖范围。

到目前为止,感谢@whuber的帮助,我不希望您花费更多的时间来帮助我,但我想我会根据您的要求尝试澄清我的处境。


除了R,您还想使用其他哪些编程语言/软件进行此分析?
亚伦

我也很高兴使用ArcGIS或QGIS。不幸的是,我对python并不那么熟悉。也可以通过bash终端进行GDAL。
JPD

Answers:


12

我将提供一种R以略微非编码R方式编码的解决方案,以说明在其他平台上可能如何使用该解决方案。

在关心R(以及一些其他的平台,特别是那些有利于功能的编程风格)是不断更新的大阵是非常昂贵的。取而代之的是,此算法保留了自己的私有数据结构,其中(a)列出了到目前为止已填充的所有单元格,并且(b)可供选择的所有单元格(在已填充单元格的周围)被列为。尽管处理这种数据结构比直接索引到数组中效率低,但是通过将修改后的数据保持在较小的大小,可能会花费更少的计算时间。(或者,也没有进行任何优化R。状态向量的预分配应该节省一些执行时间,如果您希望继续在内工作R)。

该代码已注释,应易于阅读。为了使算法尽可能完整,除了最后绘制结果外,它不使用任何附加组件。唯一棘手的部分是,为了提高效率和简化性,它更喜欢使用1D索引对2D网格进行索引。neighbors函数中发生转换,该函数需要2D索引才能确定单元格的可访问邻居是什么,然后将其转换为1D索引。这种转换是标准的,因此除了要指出在其他GIS平台中您可能希望反转列索引和行索引的角色之外,我将不对其进行进一步评论。(在中R,行索引在列索引之前更改。)

为了说明这一点,此代码采用x代表陆地的网格和类似河网的不可访问点的特征,从该网格中的特定位置(5、21)开始(在河的下弯附近)并随机扩展以覆盖250个点。总计时为0.03秒。(当数组的大小增加10,000到3000行乘5000列时,时间仅增加到0.09秒(只有3倍左右),证明了该算法的可伸缩性。)只需输出0、1和2的网格,它就会输出分配新单元格的顺序。在该图上,最早的细胞是绿色,从金逐渐变成鲑鱼色。

地图

显然,每个单元都使用八点邻域。对于其他邻域,只需修改的nbrhood开头附近的值expand:它是相对于任何给定像元的索引偏移量的列表。例如,可以将“ D4”邻域指定为matrix(c(-1,0, 1,0, 0,-1, 0,1), nrow=2)

显然,这种传播方法也有问题:它留下了漏洞。如果这不是预期的目的,则有多种方法可以解决此问题。例如,将可用的单元格保持在队列中,以便找到的最早单元格也是最早填充的单元格。仍然可以应用一些随机化,但是将不再选择具有统一(相等)概率的可用单元格。另一种更为复杂的方法是选择概率取决于其具有多少个已填充邻居的可用单元。一旦一个单元被包围,您可以使其选择的机会很高,以至于几乎没有孔被填补。

最后,我将评论说这不是一个细胞自动机(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等。

首先,如果有错误,请更改expandNA在第一行返回(a),并返回(b)中的值,indices而不是矩阵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网格时的结果(尺寸足够小且粗糙,可以使群集显得明显)。执行需要两秒钟。在3100 x 5000网格(大100倍)上,它需要更长的时间(24秒),但是时间安排合理。(在其他平台(例如C ++)上,时间安排几乎不取决于网格大小。)

60个集群


嗨,惠伯。非常感谢您,我真的很感激。我只是在做一些尝试,可能很快会再提出一些后续问题。
JPD

+1感谢您为GIS SE中一些更复杂的问题提供了如此详尽的答案。
雷达

@whuber。根据您的回答扩展了问题。再次感谢!
JPD

1
这个问题的答案#1很简单:更换线路y[indices] <- 1:length(indices)通过y[indices] <- 2。#2的答案几乎很简单:只需循环即可。
ub

@whuber。再次感谢您的更新。现在存在团块冲突的问题,导致团块的大小小于中定义的数量size.clusters。我怎样才能确保团块增长到正确的大小,因为目前,我假设它试图成长为现有的团块,但失败了,但仍然证明是成功的扩展。然后,我还打算将60个块的生产迭代1000次,以创建一个平均的空模型样式数据集。for循环中的每次随机定位都会变化吗?
JPD

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.