Creazione di gruppi di celle a forma casuale in un raster da semi di 1 cella / pixel?


11

Come dice il mio titolo, desidero "far crescere" gruppi di cellule dai semi all'interno di un raster. Il mio raster di base è pieno di 1 e 0, 1 indica terra e 0 di mare / aree NA. Dalle 1 desidero selezionare 60 pixel / celle casuali come semi, e quindi far crescere casualmente un gruppo collegato di un no predefinito. di pixel / celle limite da quel seme. Ho sentito che la tecnica può essere definita "tintura diffusa", ma non ho avuto fortuna a trovarla molto. La cellula seme verrebbe trasformata in un valore di 2 e quindi anche la cella successiva scelta tra le 1 circostanti verrebbe convertita in 2. I 2 non saranno quindi disponibili per la conversione in futuro.

Questo thread aiuta un po ', dato che sarei anche disposto a farlo in R poiché ho familiarità con la lettura e la manipolazione dei dati GIS in R. Tuttavia, ciò di cui ho bisogno è un insieme di regole per selezionare casualmente i pixel che circondano un gruppo esistente .

Se qualcuno ha eseguito questa forma più elementare di automi cellulari in un ambiente GIS, apprezzerei qualsiasi consiglio / guida.

Esempio:

Ho un obiettivo di 250 cellule. Seleziono casualmente una cella che ha un valore di 1. Questo viene trasformato in un valore di 2. Quindi, uno dei vicini della cellula seme che = 1 viene trasformato in un 2. Quindi, uno dei vicini di una delle celle con un valore 2 viene selezionato e trasformato in un 2. Questo continuerebbe fino a quando non viene raggiunta una forma continua con numerazione di 250 celle.

Modifica: ulteriori domande

Sulla base della grande risposta di Whuber, ho alcune domande sul codice:

  1. Come faccio ad assegnare i valori delle celle sviluppate solo a un '2' piuttosto che a valori variabili che rappresentano l'ordine in cui sono stati creati?
  2. Devo creare 60 gruppi di celle all'interno della mia area di "1". Ho escogitato dei modi per scegliere posizioni di partenza casuali, ma faccio fatica a far funzionare tutto in un ciclo usando la expandfunzione che hai gentilmente scritto. Puoi suggerire un modo per creare 60 blocchi che non si scontrano tra loro e sono contenuti nella stessa matrice finale?

Modifica: ulteriore spiegazione del problema

Ogni gruppo di celle rappresenterà un'area protetta di dimensioni definite, ad esempio 250 celle. Ogni area deve iniziare e crescere in celle con un valore di 1 in quanto rappresenta la terra ed evita le celle con un valore di 0, in quanto rappresenta il mare. Ho bisogno di iterare questo 1000 volte con 60 aree protette in ogni iterazione per creare un modello nullo, mostrando quali saranno le distribuzioni di queste aree per caso. Per questo motivo, il conteggio totale delle celle in tutte e 60 le aree dovrà essere identico in ciascuna delle 1000 iterazioni in modo che siano comparabili. Pertanto, va bene se le aree si toccano, ma se c'è una collisione, idealmente il gruppo dovrebbe crescere in un'altra direzione disponibile fino a raggiungere l'obiettivo di 250.

Una volta create ciascuna di queste 1000 reti di aree protette, verranno utilizzate come maschera contro altri dati raster come le misure di biodiversità per vedere (a) se intersecano determinate gamme di specie e (b) quale% di determinate specie varia queste reti casuali di copertura di aree protette.

Grazie a @whuber per il tuo aiuto finora, non mi aspetto che passi più tempo ad aiutarmi, ma ho pensato che avrei cercato di chiarire la mia situazione come hai richiesto.


Oltre a R, quali altri linguaggi / software di programmazione sei interessato a utilizzare per questa analisi?
Aaron

Sarei anche felice di usare ArcGIS o QGIS. Sfortunatamente non ho molta familiarità con Python. GDAL attraverso un terminale bash è anche una possibilità.
JPD,

Risposte:


12

Offrirò una Rsoluzione codificata in modo leggermente non R-illustrativo per illustrare come potrebbe essere affrontata su altre piattaforme.

La preoccupazione in R(così come in alcune altre piattaforme, in particolare quelle che favoriscono uno stile di programmazione funzionale) è che l'aggiornamento costante di un array di grandi dimensioni può essere molto costoso. Invece, quindi, questo algoritmo mantiene la propria struttura di dati privata in cui sono elencati (a) tutte le celle che sono state riempite finora e (b) tutte le celle disponibili per essere scelte (attorno al perimetro delle celle riempite) sono elencati. Sebbene la manipolazione di questa struttura di dati sia meno efficiente rispetto all'indicizzazione diretta in un array, mantenendo i dati modificati su dimensioni ridotte, probabilmente richiederà molto meno tempo di calcolo. (Non è stato fatto Ralcuno sforzo per ottimizzarlo . La pre-allocazione dei vettori statali dovrebbe risparmiare un po 'di tempo di esecuzione, se si preferisce continuare a lavorare all'interno R.)

Il codice è commentato e dovrebbe essere semplice da leggere. Per rendere l'algoritmo il più completo possibile, non utilizza alcun componente aggiuntivo se non alla fine per tracciare il risultato. L'unica parte complicata è che per efficienza e semplicità preferisce indicizzare nelle griglie 2D usando gli indici 1D. Si verifica una conversione nella neighborsfunzione, che necessita dell'indicizzazione 2D per capire quali potrebbero essere i vicini accessibili di una cella e quindi li converte nell'indice 1D. Questa conversione è standard, quindi non commenterò ulteriormente se non per sottolineare che in altre piattaforme GIS potresti voler invertire i ruoli degli indici di colonna e riga. (In R, gli indici di riga cambiano prima degli indici di colonna.)

Per illustrare, questo codice prende una griglia che xrappresenta la terra e una caratteristica simile a un fiume di punti inaccessibili, inizia in una posizione specifica (5, 21) in quella griglia (vicino alla curva inferiore del fiume) e la espande in modo casuale per coprire 250 punti . Il tempo totale è di 0,03 secondi. (Quando la dimensione dell'array viene aumentata di un fattore compreso tra 10.000 e 3000 righe per 5000 colonne, la temporizzazione sale solo a 0,09 secondi - un fattore di soli 3 o giù di lì - dimostrando la scalabilità di questo algoritmo.) Invece di semplicemente emettendo una griglia di 0, 1 e 2, genera la sequenza con cui sono state allocate le nuove celle. Sulla figura le prime cellule sono verdi, che si laureano in oro color salmone.

Carta geografica

Dovrebbe essere evidente che viene utilizzato un quartiere di otto punti di ogni cella. Per altri quartieri, modifica semplicemente il nbrhoodvalore vicino all'inizio di expand: è un elenco di offset dell'indice relativi a una determinata cella. Ad esempio, un quartiere "D4" potrebbe essere specificato come matrix(c(-1,0, 1,0, 0,-1, 0,1), nrow=2).

È anche evidente che questo metodo di diffusione ha i suoi problemi: lascia buchi dietro. Se questo non è ciò che si intendeva, ci sono vari modi per risolvere questo problema. Ad esempio, mantenere le celle disponibili in una coda in modo che le prime celle trovate siano anche le prime riempite. È ancora possibile applicare una certa randomizzazione, ma le celle disponibili non saranno più scelte con probabilità uniformi (uguali). Un altro modo più complicato sarebbe selezionare le celle disponibili con probabilità che dipendono dal numero di vicini pieni che hanno. Una volta che una cella viene circondata, puoi fare la sua possibilità di selezione così in alto che pochi buchi rimarrebbero vuoti.

Concluderò commentando che questo non è proprio un automa cellulare (CA), che non procederebbe cellula per cellula, ma aggiornerebbe invece intere fasce di cellule in ogni generazione. La differenza è sottile: con la CA, le probabilità di selezione per le celle non sarebbero uniformi.

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

Con lievi modifiche, possiamo expandcreare un loop per creare più cluster. Si consiglia di differenziare i cluster da un identificatore, che qui eseguirà 2, 3, ..., ecc.

Innanzitutto, cambia expandper restituire (a) NAalla prima riga se c'è un errore e (b) i valori in indicespiuttosto che una matrice y. (Non perdere tempo a creare una nuova matrice ycon ogni chiamata.) Con questa modifica, il looping è facile: scegli un avvio casuale, prova ad espandersi attorno ad esso, accumula gli indici del cluster in indicescaso di successo e ripeti fino a quando non hai finito. Una parte fondamentale del ciclo è limitare il numero di iterazioni nel caso in cui non sia possibile trovare molti cluster contigui: questo viene fatto count.max.

Ecco un esempio in cui 60 centri di cluster vengono scelti in modo uniforme a caso.

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=" "))

Ecco il risultato quando applicato a una griglia di 310 per 500 (reso sufficientemente piccolo e ruvido perché i cluster siano evidenti). Ci vogliono due secondi per eseguire; su una griglia da 3100 per 5000 (100 volte più grande) ci vuole più tempo (24 secondi) ma i tempi si stanno ridimensionando ragionevolmente bene. (Su altre piattaforme, come C ++, i tempi non dovrebbero dipendere dalle dimensioni della griglia.)

60 cluster


Ciao Whuber. Grazie mille per questo, lo apprezzo molto. Sto solo sperimentando un po 'e potrei tornare presto con alcune domande di follow-up.
JPD,

+1 Grazie per aver fornito risposte così approfondite ad alcune delle domande più complesse su GIS SE.
Radar

@whuber. Ho ampliato un po 'la domanda in base alla tua risposta. Grazie ancora!
JPD,

1
La risposta al n. 1 è banale: sostituisci la riga y[indices] <- 1:length(indices)con y[indices] <- 2. La risposta a # 2 è quasi altrettanto semplice: basta un ciclo.
whuber

@whuber. Grazie ancora per l'aggiornamento. Esiste ora il problema dello scontro di gruppi e il conseguente ridimensionamento dei gruppi meno del numero definito in size.clusters. Come posso assicurarmi che un gruppo cresca delle dimensioni corrette, dato che, al momento, presumo che cerchi di crescere in un gruppo esistente, fallisca, ma si registri ancora come espansione di successo. Quindi intendo anche iterare la produzione dei 60 gruppi 1000 volte creando un set di dati medio in stile modello nullo. Il posizionamento casuale varierebbe ogni volta all'interno di un forloop?
JPD,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.