Determinare se il punto è circondato usando l'elaborazione raster


9

Sto tentando di migliorare un processo vettoriale / pitone attualmente estremamente ingombrante per un modello di rischio naturale. Al momento abbiamo una lunga sceneggiatura che genera linee di distanza / rilevamento da un determinato punto per determinare:

  1. il tipo di poligono che interseca (es. foresta, erba, palude, ecc.)
  2. la distanza da quel poligono
  3. quante di queste linee intersecano i poligoni, per determinare quanto sia "circondato".

C'è molto di più coinvolto, ma questo è l'essenza. Sto cercando di trovare un modo per migliorare questo e sono attualmente sconcertato sulla parte 3. L'idea è di determinare se un punto è completamente circondato da poligoni, entro 200 mIl punto A è circondato, mentre il punto B è circondato solo per circa il 50%

Quindi, nella mia immagine allegata, vorrei che il punto A fosse contrassegnato come a rischio più elevato rispetto al punto B poiché è completamente circondato dai miei poligoni. Questo si ripete per circa 13 milioni di punti, quindi non è un compito da poco e preferirei avere una superficie da cui ricavare valori, piuttosto che eseguire la nostra sceneggiatura. Sto pensando che ci debba essere una variazione degli strumenti di idrologia o del percorso dei costi per farlo, ma non riesco a capirlo.

Come ho potuto fare questo?


1
Viewshed è all'altezza del compito, ma avrà bisogno di un notevole aiuto quando applicato a 13 milioni di punti! Pensa innanzitutto a come eliminare punti i cui dintorni sono facili da controllare, come punti situati in regioni esterne ai poligoni che hanno diametri inferiori a (diciamo) 200 m: ciò potrebbe escludere "A" ma forse non "B". "B" non sarebbe mai escluso perché il suo punto di vista (considerando che le aree poligonali erano posizioni molto "alte" che bloccano la vista) si estende per oltre 200 metri dalla posizione di B.
whuber

Un buon punto @whuber. certamente sono in grado di ridurre il numero totale di punti effettivamente elaborati dalla prossimità e di fatto anche lat-long unici (sto parlando di indirizzi geocodificati in modo che i condomini possano essere ridotti da 50 punti a 1) ma continuerò a cercare in diversi milioni di località. Posso anche semplicemente scomporre tutto in blocchi sovrapposti, se necessario. Investigherà il punto di vista. Grazie!
Loz,

Un altro schermo veloce è calcolare una media focale della griglia dell'indicatore 0-1 dei tuoi poligoni usando un vicinato anulare: in qualsiasi cella il cui valore è 1, i tuoi poligoni occupano l'intero perimetro fuori dal raggio, da cui devono essere circondati. Questo è un calcolo veloce e potrebbe eliminare la stragrande maggioranza dei tuoi punti, a seconda di dove si trovano e quanto sono contorti i tuoi poligoni. Puoi anche velocizzare lo screening iniziale ricampionando prima la griglia a una risoluzione più grossolana, ad esempio 25-50 mo circa.
whuber

Un'altra potenziale fase di elaborazione, o fase di pre-elaborazione, sarebbe quella di passare i punti attraverso una versione rasterizzata del set di dati confrontando le statistiche di un quartiere attorno ai punti. Puoi sottrarre il tuo requisito "circondato" come una statistica del vicinato di punti, o se è necessario "circondato", potresti trovare i punti "facili" (cioè un punto completamente all'interno di un'area a rischio) usando un quartiere raster, analizzare i punti "facili" da tutti i punti, quindi utilizzare l'analisi vettoriale per il resto dei punti.
DPierce,

caspita la mia domanda ha sicuramente suscitato molto interesse! Grazie a tutti coloro che hanno contribuito con suggerimenti e commenti. Lavorerò a modo mio attraverso tutti loro e risponderò, ma tutti impiegheranno un po 'di tempo per testarmi. Prometto che alla fine risponderò!
Loz,

Risposte:


6

Esiste una soluzione per il percorso dei costi, ma dovrai codificarla tu stesso. Ecco come potrebbe apparire se applicato a tutti i punti dell'immagine nella domanda (leggermente ingrandito per accelerare i calcoli):

Figura 0

Le cellule nere sono parti dei poligoni circostanti. I colori, che vanno dall'arancione chiaro (corto) al blu (lungo), mostrano la distanza massima (fino a un massimo di 50 celle) che può essere raggiunta dall'attraversamento della linea di vista senza intercettare le celle poligonali. (Qualsiasi cellula al di fuori dell'estensione di questa immagine viene trattata come parte dei poligoni.)

Discutiamo un modo efficace per farlo utilizzando una rappresentazione raster dei dati. In questa rappresentazione tutte le celle poligonali "circostanti" avranno, per esempio, valori diversi da zero e qualsiasi cella che può essere "vista attraverso" avrà un valore zero.

Passaggio 1: calcolo preliminare di una struttura di dati di vicinato

Devi prima decidere cosa significa per una cella bloccarne un'altra. Una delle regole più giuste che posso trovare è questa: usando le coordinate integrali per righe e colonne (e assumendo celle quadrate), consideriamo quali celle potrebbero bloccare la cella (i, j) dalla vista all'origine (0,0). Nomino la cella (i ', j') che è più vicina al segmento di linea che collega (i, j) a (0,0) tra tutte le celle le cui coordinate differiscono al massimo da i e j per 1. Perché questo non sempre produrre una soluzione unica (ad esempio, con (i, j) = (1,2) entrambi (0,1) e (1,1) funzioneranno ugualmente bene), sono necessari alcuni mezzi per risolvere i legami. Sarebbe bello che questa risoluzione dei legami rispetti le simmetrie dei quartieri circolari nelle reti: negare le coordinate o cambiare le coordinate preserva questi quartieri. Pertanto possiamo decidere quali celle bloccano (i,

L'illustrazione di questa regola è il seguente codice prototipo scritto in R. Questo codice restituisce una struttura di dati che sarà conveniente per determinare la "circonferenza" di celle arbitrarie in una griglia.

screen <- function(k=1) {
  #
  # Returns a data structure:
  #   $offset is an array of offsets
  #   $screened is a parallel array of screened offset indexes.
  #   $distance is a parallel array of distances.
  # The first index always corresponds to (0,0).
  #
  screened.by <- function(xy) {
    uv <- abs(xy)
    if (reversed <- uv[2] > uv[1]) {
      uv <- rev(uv)
    }
    i <- which.min(c(uv[1], abs(uv[1]-uv[2]), uv[2]))
    ij <- uv + c(floor((1-i)/3), floor(i/3)-1)
    if (reversed) ij <- rev(ij)
    return(ij * sign(xy))
  }
  #
  # For each lattice point within the circular neighborhood,
  # find the unique lattice point that screens it from the origin.
  #
  xy <- subset(expand.grid(x=(-k:k), y=(-k:k)), 
               subset=(x^2+y^2 <= k^2) & (x != 0 | y != 0))
  g <- t(apply(xy, 1, function(z) c(screened.by(z), z)))
  #
  # Sort by distance from the origin.
  #
  colnames(g) <- c("x", "y", "x.to", "y.to")
  ij <- unique(rbind(g[, 1:2], g[, 3:4]))
  i <- order(abs(ij[,1]), abs(ij[,2])); ij <- ij[i, , drop=FALSE]
  rownames(ij) <- 1:length(i)
  #
  # Invert the "screened by" relation to produce the "screened" relation.
  #
  # (Row, column) offsets.
  ij.df <- data.frame(ij, i=1:length(i))
  #
  # Distances from the origin (in cells).
  distance <- apply(ij, 1, function(u) sqrt(sum(u*u)))
  #
  # "Screens" relation (represented by indexes into ij).
  g <- merge(merge(g, ij.df), ij.df, 
             by.x=c("x.to", "y.to"), by.y=c("x","y"))
  g <- subset(g, select=c(i.x, i.y))
  h <- by(g$i.y, g$i.x, identity)

  return( list(offset=ij, screened=h, distance=distance) )
}

Il valore di è screen(12)stato usato per produrre questa rappresentazione di questa relazione di screening: le frecce puntano dalle cellule a quelle che le schermano immediatamente. Le tonalità sono proporzionate dalla distanza dall'origine, che è al centro di questo quartiere:

Figura 1

Questo calcolo è veloce e deve essere eseguito una sola volta per un determinato quartiere. Ad esempio, quando si affacciano 200 m su una griglia con celle di 5 m, le dimensioni del quartiere saranno 200/5 = 40 unità.

Passaggio 2: Applicazione del calcolo ai punti selezionati

Il resto è semplice: per determinare se una cella situata in (x, y) (nelle coordinate di riga e colonna) è "circondata" rispetto a questa struttura di dati di vicinato, eseguire il test ricorsivamente iniziando con un offset di (i, j) = (0,0) (l'origine del quartiere). Se il valore nella griglia poligonale in (x, y) + (i, j) è diverso da zero, la visibilità viene bloccata lì. Altrimenti, dovremo considerare tutti gli offset che avrebbero potuto essere bloccati in offset (i, j) (che si trovano nel tempo O (1) usando la struttura di dati restituita da screen). Se non ne sono bloccati, abbiamo raggiunto il perimetro e concludiamo che (x, y) non è circondato, quindi fermiamo il calcolo (e non ci preoccupiamo di ispezionare eventuali punti rimanenti nel vicinato).

Siamo in grado di raccogliere informazioni ancora più utili tenendo traccia della distanza più lontana dalla linea di mira raggiunta durante l'algoritmo. Se questo è inferiore al raggio desiderato, la cella è circondata; altrimenti non lo è.

Ecco un Rprototipo di questo algoritmo. È più lungo di quanto sembri, perché Rnon supporta nativamente la (semplice) struttura dello stack necessaria per implementare la ricorsione, quindi anche uno stack deve essere codificato. L'algoritmo effettivo inizia a circa due terzi del percorso e richiede solo una dozzina di righe. (E la metà di questi si limita a gestire la situazione attorno al bordo della griglia, verificando la presenza di indici fuori portata all'interno del vicinato. Ciò potrebbe essere reso più efficiente semplicemente espandendo la griglia del poligono di krighe e colonne attorno al suo perimetro, eliminando qualsiasi necessità di verificare l'intervallo di indice al costo di un po 'più di RAM per contenere la griglia poligonale.)

#
# Test a grid point `ij` for a line-of-sight connection to the perimeter
# of a circular neighborhood.  
#   `xy` is the grid.
#   `counting` determines whether to return max distance or count of stack ops.
#   `perimeter` is the assumed values beyond the extent of `xy`.
#
# Grid values of zero admit light; all others block visibility
# Returns maximum line-of-sight distance found within `nbr`.
#
panvisibility <- function(ij, xy, nbr=screen(), counting=FALSE, perimeter=1) {
  #
  # Implement a stack for the algorithm.
  #
  count <- 0 # Stack count
  stack <- list(ptr=0, s=rep(NA, dim(nbr$offset)[1]))
  push <- function(x) {
    n <- length(x)
    count <<- count+n         # For timing
    stack$s[1:n + stack$ptr] <<- x
    stack$ptr <<- stack$ptr+n
  }
  pop <- function() {
    count <<- count+1         # For timing
    if (stack$ptr <= 0) return(NULL)
    y <- stack$s[stack$ptr]
    #stack$s[stack$ptr] <<- NA # For debugging
    stack$ptr <<- stack$ptr - 1
    return(y)
  }
  #
  # Initialization.
  #
  m <- dim(xy)[1]; n <- dim(xy)[2]
  push(1) # Stack the *indexes* of nbr$offset and nbr$screened.
  dist.max <- -1
  #
  # The algorithm.
  #
  while (!is.null(i <- pop())) {
    cell <- nbr$offset[i, ] + ij
    if (cell[1] <= 0 || cell[1] > m || cell[2] <= 0 || cell[2] > n) {
      value <- perimeter
    } else {  
      value <- xy[cell[1], cell[2]]
    }
    if (value==0) {
      if (nbr$distance[i] > dist.max) dist.max <- nbr$distance[i]
      s <- nbr$screened[[paste(i)]]
      if (is.null(s)) {
        #exited = TRUE
        break
      }
      push(s)
    }
  }
  if (counting) return ( count )
  return(dist.max)
}

Figura 2: Esempio

In questo esempio, le celle poligonali sono nere. I colori forniscono la massima distanza di visuale (fino a 50 celle) per le celle non poligonali, che vanno dall'arancio chiaro per brevi distanze al blu scuro per le distanze più lunghe. (Le celle sono larghe e alte una unità.) Le strisce visibilmente evidenti sono create dalle piccole "isole" poligonali nel mezzo del "fiume": ognuna blocca una lunga fila di altre celle.

Analisi dell'algoritmo

La struttura dello stack implementa una ricerca approfondita del grafico di visibilità del vicinato per provare che una cella non è circondata. Laddove le celle sono lontane da qualsiasi poligono, questa ricerca richiederà l'ispezione delle sole celle O (k) per una circonferenza circolare k-raggio. I casi peggiori si verificano quando ci sono un piccolo numero di celle poligonali sparse all'interno del vicinato, ma anche così il confine del quartiere non è del tutto raggiungibile: questi richiedono l'ispezione di quasi tutte le celle in ogni quartiere, che è una O (k ^ 2) operazione.

Il seguente comportamento è tipico di ciò che verrà riscontrato. Per piccoli valori di k, a meno che i poligoni non riempiano la maggior parte della griglia, la maggior parte delle celle non poligonali sarà ovviamente non circoscritta e l'algoritmo si ridimensiona come O (k). Per valori intermedi, il ridimensionamento inizia ad apparire come O (k ^ 2). Man mano che k diventa veramente grande, la maggior parte delle cellule viene circondata e questo fatto può essere determinato molto prima che l'intero quartiere venga ispezionato: lo sforzo computazionale dell'algoritmo raggiunge quindi un limite pratico. Questo limite viene raggiunto quando il raggio di prossimità si avvicina al diametro delle più grandi regioni non poligonali connesse nella griglia.

Ad esempio, utilizzo l' countingopzione codificata nel prototipo di screenper restituire il numero di operazioni dello stack utilizzate in ciascuna chiamata. Questo misura lo sforzo computazionale. Il grafico seguente mostra il numero medio di operazioni di stack in funzione del raggio di prossimità. Mostra il comportamento previsto.

Figura 3

Possiamo usarlo per stimare il calcolo necessario per valutare 13 milioni di punti su una griglia. Supponiamo che venga usato un vicinato di k = 200/5 = 40. Quindi in media saranno necessarie alcune centinaia di operazioni di stack (a seconda della complessità della griglia poligonale e della posizione dei 13 milioni di punti rispetto ai poligoni), il che implica che in un linguaggio compilato efficiente, al massimo qualche migliaio di semplici operazioni numeriche sarà richiesto (aggiungi, moltiplica, leggi, scrivi, offset, ecc.). La maggior parte dei PC sarà in grado di valutare la circonferenza di circa un milione di punti a quel ritmo. (IlRl'implementazione è molto, molto più lenta di quella, perché è scarsa in questo tipo di algoritmo, motivo per cui può essere considerata solo un prototipo.) Di conseguenza, potremmo sperare che un'implementazione efficiente in un linguaggio ragionevolmente efficiente e appropriato - C ++ e Python viene in mente: potrebbe completare la valutazione di 13 milioni di punti in un minuto o meno, supponendo che l'intera griglia poligonale risieda nella RAM.

Quando una griglia è troppo grande per adattarsi alla RAM, questa procedura può essere applicata a porzioni piastrellate della griglia. Devono solo sovrapporsi per krighe e colonne; prendi i massimi alle sovrapposizioni quando mosaichi i risultati.

Altre applicazioni

Il "recupero" di uno specchio d'acqua è strettamente correlato alla "circonferenza" dei suoi punti. In effetti, se utilizziamo un raggio di vicinato uguale o maggiore del diametro del corpo idrico, creeremo una griglia del recupero (non direzionale) in ogni punto del corpo idrico. Usando un raggio di quartiere più piccolo otterremo almeno un limite inferiore per il recupero in tutti i punti di recupero più alti, che in alcune applicazioni può essere abbastanza buono (e può ridurre sostanzialmente lo sforzo computazionale). Una variante di questo algoritmo che limita la relazione "schermata da" verso direzioni specifiche sarebbe un modo per calcolare il recupero in modo efficiente in quelle direzioni. Si noti che tali varianti richiedono la modifica del codice per screen; il codice per panvisibilitynon cambia affatto.


2

Posso sicuramente vedere come si potrebbe desiderare di farlo con una soluzione raster, ma dato anche un numero ridotto di punti, mi aspetterei una griglia o una serie di griglie molto grandi / ad alta risoluzione e quindi difficili da elaborare. Detto questo, mi chiedo se lo sfruttamento della topologia in un gdb potrebbe essere più efficiente. Potresti trovare tutti i vuoti interni con qualcosa del tipo:

arcpy.env.workspace = 'myGDB'
arcpy.CreateTopology_management('myGDB', 'myTopology', '')    
arcpy.AddFeatureClassToTopology_management('myTopology', 'myFeatures', '1','1')    
arcpy.AddRuleToTopology_management ('myToplogy', 'Must Not Have Gaps (Area)', 'myFeatures', '', '', '')    
arcpy.ValidateTopology_management('myTopology', 'Full_Extent')
arcpy.ExportTopologyErrors_management('myTopology', 'myGDB', 'topoErrors')
arcpy.FeatureToPolygon_management('topoErrors_line','topoErrorsVoidPolys', '0.1')`

puoi quindi lavorare con il topoErrorsVoidPolystuo modello normale Intersect_analysis()o qualunque cosa. Potrebbe essere necessario pasticciare con l'estrazione dei polys di interesse da topoErrorsVoidPolys. @whuber ha un numero di post piuttosto eccellenti su questo genere di cose altrove qui su gis.stackexchange.com.


Questa è un'idea interessante di pre-elaborazione. Penso che potrebbe essere prontamente adattato per incorporare il limite di 200 m (tramite buffering e intersezione, ecc.) Il tuo punto sulle griglie che diventano piuttosto grandi è sicuramente una vera preoccupazione. Non esiste alcuna regola in GIS che dice che una soluzione a un problema deve essere basata esclusivamente su raster o su base vettoriale (sebbene esista un principio che dice che dovresti avere una ragione abbastanza buona per convertire da una rappresentazione all'altra nel mezzo di un'analisi; qui, come suggerisci, potrebbe esserci un beneficio sostanziale nel fare esattamente questo).
whuber

0

Se vuoi davvero diventare raster ... Farei qualcosa del genere di questo pseudo codice (non rabbrividire solo perché è ovvio che sono un ritorno al passato AML!: P)

  1. rasterizzare punti ("pts_g") e polys ("polys_g" (
  2. voids = regiongroup (con (isnull (polys_g), 1))
  3. potrebbe essere necessario fare qualcosa per affinare i vuoti per eliminare l'area poligonale / universo esterno indesiderata
  4. pts_surrounded = con (voids, pts_g)

Solo un po 'per inventarlo, quindi potrebbe essere necessario un perfezionamento.


La tua soluzione non fa alcun riferimento alla distanza limite (di, diciamo, 200 m), quindi non sembra rispondere correttamente alla domanda.
whuber

hai ragione. Questo vale anche per l'altra mia risposta. Suppongo che si possa usare Expand(), ma a quel punto penso che la risposta di @radouxju sarebbe funzionalmente equivalente e probabilmente più veloce. (niente contro il punto di vista, solo non usarlo molto).
Roland,

stava provando a modificare allo scadere del tempo. voglio espandere su Expand()per dire farlo su pts_ge usare solo Con()per intersecare polys_g.
Roland,

0

Se si utilizza un valore di distanza soglia (qui si parla di 200 m), la soluzione migliore è utilizzare l'analisi vettoriale:

1) creare un buffer di 200 m attorno a ciascun punto (in nero nell'illustrazione)

2) utilizzare lo strumento interseca (analisi) tra il buffer e i poligoni (in blu sull'illustrazione). Sembrerà più bello se lo fai tra i confini dei poligoni circostanti e il buffer, ma il risultato finale è lo stesso.

3) usa la funzione per poligono (gestione) per creare poligoni in cui i tuoi punti sono completamente circondati (in rosso sull'illustrazione)

4) selezionare i livelli per posizione (gestione) o unione spaziale (analisi) per identificare i punti che sono circondati. L'uso del join spaziale consente di disporre di informazioni sul poligono incorporato (area del poligono, statistiche zonali ...) che potrebbero essere utili per un'ulteriore elaborazione.

Alternative 2b) A seconda delle esigenze, è possibile selezionare in base alla posizione i poligoni circostanti entro una distanza di 200 m, quindi è possibile identificare alcuni tipi di "recinzione" ma non rigorosamente come in 2).

inserisci qui la descrizione dell'immagine

Considerando il "caso del labirinto", questo potrebbe aiutare: valutare quanto tempo deve "fuggire" dalla posizione.

  • È già possibile escludere dall'analisi i punti che sono completamente inclusi o completamente gratuiti

  • quindi converti i tuoi ostacoli in un raster e imposti i valori su NoData dove hai un poligono e sulla dimensione della cella in metri dove non lo fai (questo renderà i tuoi costi raster).

  • terzo, è possibile calcolare la distanza di costo utilizzando il raster di costo appena generato

  • infine, si utilizza una statistica zonale come tabella basata sui confini del buffer convertiti in raster (formando un annulus). Se riesci a scappare in tutte le direzioni, il minimo dovrebbe essere approssimativamente di 200 (a seconda della dimensione della cella dell'analisi). Ma se sei in un labirinto, il massimo sarà maggiore di 200. Quindi il massimo delle statistiche zonali meno 200 sarà un valore continuo che indica quanto sia difficile "scappare".


Si prega di chiarire la definizione di "circondato". La descrizione nella domanda suggerisce che un punto dovrebbe essere considerato "circondato" quando una parte del poligono è visibile in tutte le direzioni intorno a quel punto (fino a una distanza di 200 m). Come lo provi esattamente al punto (3)? (Non è facile usare un'analisi vettoriale!)
whuber

Ho aggiunto una piccola illustrazione, è più facile da spiegare in questo modo. Se il buffer non interseca un poligono in tutte le direzioni, il loop non verrà chiuso. E se il ciclo non è vicino, questo non farà un poligono.
Radouxju,

Non sono sicuro di cosa intendi con "loop" o "chiuso". Si noti che un punto può essere "circondato" anche quando nessun cerchio di raggio r (inferiore a 200 m) attorno ad esso è interamente contenuto nel poligono. Pensa a un labirinto: il poligono è tutto tranne i corridoi nel labirinto. Si può fuggire dal labirinto a partire da qualsiasi punto all'interno di esso, ma la maggior parte dei punti saranno "circondati", nel senso che l'esterno del labirinto non sarà visibile da loro.
whuber

dal mio punto di vista, circondato da qualche parte non puoi scappare. Sull'illustrazione, puoi scappare da B ma non da A. D'altra parte, B sembrerebbe essere circondato se usi il punto di vista (beh, forse non a 200 m in quanto non vi è alcuna barra di scala sull'immagine, ma dovresti vedere i confini del poligono guardando in tutte le direzioni). Penso che abbiamo bisogno di maggiori dettagli da @Loz
radouxju il

Non sarebbe affatto una domanda difficile se "non potesse sfuggire" fosse il criterio da controllare: basta un raggruppamento regionale del complemento del poligono, mantenere solo il componente esterno unico e verificare l'inclusione al suo interno. Penso che una lettura attenta della domanda - in particolare i suoi riferimenti a guardare tutti i possibili cuscinetti - chiarisca il senso in cui si intende "circondato", anche se concordo sul fatto che sia affermato piuttosto vagamente.
whuber
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.