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):
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:
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 R
prototipo di questo algoritmo. È più lungo di quanto sembri, perché R
non 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 k
righe 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)
}
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' counting
opzione codificata nel prototipo di screen
per 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.
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. (IlR
l'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 k
righe 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 panvisibility
non cambia affatto.