Vorrei creare un buffer orientato per ogni poligono nel mio shapefile usando arcpy. Per orientato intendo che ho due angoli a1 e a2 che vincolano la direzione del buffer. Questo è rappresentato nel grafico seguente:
Qualche idea?
Vorrei creare un buffer orientato per ogni poligono nel mio shapefile usando arcpy. Per orientato intendo che ho due angoli a1 e a2 che vincolano la direzione del buffer. Questo è rappresentato nel grafico seguente:
Qualche idea?
Risposte:
Questa risposta colloca la domanda in un contesto più ampio, descrive un algoritmo efficiente applicabile alla rappresentazione sagomata di feature (come "vettori" o "linestring" di punti), mostra alcuni esempi della sua applicazione e fornisce un codice funzionante per l'utilizzo o il porting in un ambiente GIS.
Questo è un esempio di dilatazione morfologica. In generale, una dilatazione "diffonde" i punti di una regione nei loro quartieri; la raccolta di punti in cui si concludono è la "dilatazione". Le applicazioni in GIS sono numerose: modellare la diffusione del fuoco, il movimento delle civiltà, la diffusione delle piante e molto altro.
Matematicamente, e in una grandissima (ma utile) generalità, una dilatazione diffonde una serie di punti in una varietà riemanniana (come un piano, una sfera o un ellissoide). La diffusione è stipulata da un sottoinsieme del pacchetto tangente in questi punti. Ciò significa che in ciascuno dei punti viene data una serie di vettori (direzioni e distanze) (io chiamo questo un "quartiere"); ciascuno di questi vettori descrive un percorso geodetico che inizia nel suo punto base. Il punto base è "diffuso" alle estremità di tutti questi percorsi. (Per la definizione molto più limitata di "dilatazione" che viene convenzionalmente impiegata nell'elaborazione delle immagini, vedere l' articolo di Wikipedia . La funzione di diffusione è nota come mappa esponenziale in geometria differenziale.)
Il "buffering" di una funzione è uno degli esempi più semplici di tale dilatazione: un disco di raggio costante (il raggio del buffer) viene creato (almeno concettualmente) attorno a ciascun punto della funzione. L'unione di questi dischi è il buffer.
Questa domanda richiede il calcolo di una dilatazione leggermente più complicata in cui è consentito che la diffusione avvenga solo all'interno di un determinato intervallo di angoli (cioè all'interno di un settore circolare). Questo ha senso solo per le caratteristiche che non racchiudono una superficie curva in modo apprezzabile (come piccole caratteristiche sulla sfera o sull'ellissoide o qualsiasi caratteristica nel piano). Quando lavoriamo sull'aereo è anche importante orientare tutti i settori nella stessa direzione. (Se modellassimo la diffusione del fuoco dal vento, tuttavia, vorremmo che i settori fossero orientati con il vento e che le loro dimensioni potessero variare anche con la velocità del vento: questa è una motivazione per la definizione generale di dilatazione che ho dato. ) (Su superfici curve come un ellissoide è impossibile in generale orientare tutti i settori nella "stessa" direzione.)
Nelle seguenti circostanze la dilatazione è relativamente facile da calcolare:
La funzione è nell'aereo (ovvero, stiamo dilatando una mappa della funzione e si spera che la mappa sia abbastanza accurata).
La dilatazione sarà costante : la diffusione in ogni punto della caratteristica avverrà all'interno di quartieri congruenti con lo stesso orientamento.
Questo quartiere comune è convesso. La convessità semplifica e velocizza notevolmente i calcoli.
Questa domanda rientra in tali circostanze specializzate: richiede la dilatazione di poligoni arbitrari da parte di settori circolari le cui origini (i centri dei dischi da cui provengono) si trovano nei punti di base. Se questi settori non superano i 180 gradi, saranno convessi. (I settori più grandi possono sempre essere divisi a metà in due settori convessi; l'unione delle due dilatazioni più piccole darà il risultato desiderato.)
Poiché stiamo eseguendo calcoli euclidei - facendo la diffusione nel piano - possiamo dilatare un punto semplicemente traducendo il quartiere di dilatazione in quel punto. (Per poterlo fare, il quartiere ha bisogno di un'origineche corrisponderà al punto base. Ad esempio, l'origine dei settori in questa domanda è il centro del cerchio da cui sono formati. Questa origine si trova al confine del settore. Nell'operazione di buffering GIS standard, il vicinato è un cerchio con origine al centro; ora l'origine risiede all'interno del cerchio. La scelta di un'origine non è un grosso problema dal punto di vista computazionale, perché un cambiamento di origine sposta semplicemente l'intera dilatazione, ma può essere un grosso problema in termini di modellizzazione dei fenomeni naturali. La sector
funzione nel codice seguente mostra come è possibile specificare un'origine.)
Dilatare un segmento di linea può essere complicato, ma per un vicinato convesso possiamo creare la dilatazione come unione di dilatazioni dei due punti finali insieme a un parallelogramma scelto con cura. (Nell'interesse dello spazio non mi fermerò per dimostrare affermazioni matematiche come questa, ma incoraggio i lettori a provare le proprie prove perché è un esercizio perspicace.) Ecco un'illustrazione che utilizza tre settori (mostrato in rosa). Hanno raggi unitari e i loro angoli sono indicati nei titoli. Il segmento di linea stesso ha lunghezza 2, è orizzontale ed è mostrato in nero:
I parallelogrammi si trovano localizzando i punti rosa che sono il più lontano possibile dal segmento solo nella direzione verticale . Ciò fornisce due punti inferiori e due punti superiori lungo linee parallele al segmento. Non ci resta che unire i quattro punti in un parallelogramma (mostrato in blu). Notate, a destra, come questo abbia senso anche quando il settore stesso è solo un segmento di linea (e non un vero poligono): lì, ogni punto sul segmento è stato tradotto in una direzione 171 gradi ad est del nord per una distanza che va da 0 a 1. L'insieme di questi endpoint è il parallelogramma mostrato. I dettagli di questo calcolo compaiono nella buffer
funzione definita all'interno dilate.edges
del codice seguente.
Per dilatare una polilinea , formiamo l'unione delle dilatazioni dei punti e dei segmenti che la formano. Le ultime due righe dilate.edges
eseguono questo loop.
La dilatazione di un poligono richiede l'inclusione dell'interno del poligono insieme alla dilatazione del suo confine. (Questa affermazione fa alcune ipotesi sul quartiere di dilatazione. Uno è che tutti i quartieri contengano il punto (0,0), che garantisce che il poligono sia incluso nella sua dilatazione. Nel caso di quartieri variabili presuppone anche che la dilatazione di qualsiasi interno il punto del poligono non si estende oltre la dilatazione dei punti di confine. Questo è il caso di quartieri costanti.)
Diamo un'occhiata ad alcuni esempi di come funziona, prima con un nonagone (scelto per rivelare i dettagli) e poi con un cerchio (scelto per abbinare l'illustrazione nella domanda). Gli esempi continueranno a utilizzare gli stessi tre quartieri, ma ridotti a un raggio di 1/3.
In questa figura l'interno del poligono è grigio, le dilatazioni dei punti (settori) sono rosa e le dilatazioni dei bordi (parallelogrammi) sono blu.
Il "cerchio" è in realtà solo un 60 gon, ma si avvicina bene a un cerchio.
Quando la funzione di base è rappresentata da N punti e la vicinanza di dilatazione da M punti, questo algoritmo richiede uno sforzo O (N M) . Deve essere seguito semplificando il disordine dei vertici e dei bordi nell'unione, che può richiedere uno sforzo O (N M log (N M)): è qualcosa da chiedere al GIS di fare; non dovremmo programmarlo.
Lo sforzo computazionale potrebbe essere migliorato a O (M + N) per le caratteristiche di base convesse (perché puoi capire come viaggiare intorno al nuovo confine unendo opportunamente gli elenchi di vertici che descrivono i confini delle due forme originali). Ciò non richiederebbe alcuna pulizia successiva.
Quando la zona di dilatazione cambia lentamente dimensione e / o orientamento man mano che avanzi attorno alla funzione di base, la dilatazione del bordo può essere strettamente approssimata dallo scafo convesso dell'unione delle dilatazioni dei suoi punti finali. Se i due quartieri di dilatazione hanno punti M1 e M2, questo può essere trovato con sforzo O (M1 + M2) usando un algoritmo descritto in Shamos & Preparata, Geometria computazionale . Pertanto, lasciando K = M1 + M2 + ... + M (N) il numero totale di vertici nei quartieri di dilatazione N, possiamo calcolare la dilatazione nel tempo O (K * log (K)).
Perché dovremmo affrontare una tale generalizzazione se tutto ciò che vogliamo è un semplice buffer? Per le funzionalità di grandi dimensioni sulla terra, un quartiere di dilatazione (come un disco) che ha dimensioni costanti nella realtà può avere dimensioni variabili sulla mappa in cui vengono eseguiti questi calcoli. In questo modo otteniamo un modo per eseguire calcoli accurati per l'ellissoide pur continuando a godere di tutti i vantaggi della geometria euclidea.
Gli esempi sono stati prodotti con questo R
prototipo, che può essere facilmente portato nel tuo linguaggio preferito (Python, C ++, ecc.). Nella struttura è parallela all'analisi riportata in questa risposta e quindi non necessita di spiegazioni separate. I commenti chiariscono alcuni dettagli.
(Potrebbe essere interessante notare che i calcoli trigonometrici vengono utilizzati solo per creare le caratteristiche di esempio - che sono poligoni regolari - e i settori. Nessuna parte dei calcoli di dilatazione richiede alcuna trigonometria.)
#
# Dilate the vertices of a polygon/polyline by a shape.
#
dilate.points <- function(p, q) {
# Translate a copy of `q` to each vertex of `p`, resulting in a list of polygons.
pieces <- apply(p, 1, function(x) list(t(t(q)+x)))
lapply(pieces, function(z) z[[1]]) # Convert to a list of matrices
}
#
# Dilate the edges of a polygon/polyline `p` by a shape `q`.
# `p` must have at least two rows.
#
dilate.edges <- function(p, q) {
i <- matrix(c(0,-1,1,0), 2, 2) # 90 degree rotation
e <- apply(rbind(p, p[1,]), 2, diff) # Direction vectors of the edges
# Dilate a single edge from `x` to `x+v` into a parallelogram
# bounded by parts of the dilation shape that are at extreme distances
# from the edge.
buffer <- function(x, v) {
y <- q %*% i %*% v # Signed distances orthogonal to the edge
k <- which.min(y) # Find smallest distance, then the largest *after* it
l <- (which.max(c(y[-(1:k)], y[1:k])) + k-1) %% length(y)[1] + 1
list(rbind(x+q[k,], x+v+q[k,], x+v+q[l,], x+q[l,])) # A parallelogram
}
# Apply `buffer` to every edge.
quads <- apply(cbind(p, e), 1, function(x) buffer(x[1:2], x[3:4]))
lapply(quads, function(z) z[[1]]) # Convert to a list of matrices
}
#----------------------- (This ends the dilation code.) --------------------------#
#
# Display a polygon and its point and edge dilations.
# NB: In practice we would submit the polygon, its point dilations, and edge
# dilations to the GIS to create and simplify their union, producing a single
# polygon. We keep the three parts separate here in order to illustrate how
# that polygon is constructed.
#
display <- function(p, d.points, d.edges, ...) {
# Create a plotting region covering the extent of the dilated figure.
x <- c(p[,1], unlist(lapply(c(d.points, d.edges), function(x) x[,1])))
y <- c(p[,2], unlist(lapply(c(d.points, d.edges), function(x) x[,2])))
plot(c(min(x),max(x)), c(min(y),max(y)), type="n", asp=1, xlab="x", ylab="y", ...)
# The polygon itself.
polygon(p, density=-1, col="#00000040")
# The dilated points and edges.
plot.list <- function(l, c) lapply(l, function(p)
polygon(p, density=-1, col=c, border="#00000040"))
plot.list(d.points, "#ff000020")
plot.list(d.edges, "#0000ff20")
invisible(NULL) # Doesn't return anything
}
#
# Create a sector of a circle.
# `n` is the number of vertices to use for approximating its outer arc.
#
sector <- function(radius, arg1, arg2, n=1, origin=c(0,0)) {
t(cbind(origin, radius*sapply(seq(arg1, arg2, length.out=n),
function(a) c(cos(a), sin(a)))))
}
#
# Create a polygon represented as an array of rows.
#
n.vertices <- 60 # Inscribes an `n.vertices`-gon in the unit circle.
angles <- seq(2*pi, 0, length.out=n.vertices+1)
angles <- angles[-(n.vertices+1)]
polygon.the <- cbind(cos(angles), sin(angles))
if (n.vertices==1) polygon.the <- rbind(polygon.the, polygon.the)
#
# Dilate the polygon in various ways to illustrate.
#
system.time({
radius <- 1/3
par(mfrow=c(1,3))
q <- sector(radius, pi/12, 2*pi/3, n=120)
d.points <- dilate.points(polygon.the, q)
d.edges <- dilate.edges(polygon.the, q)
display(polygon.the, d.points, d.edges, main="-30 to 75 degrees")
q <- sector(radius, pi/3, 4*pi/3, n=180)
d.points <- dilate.points(polygon.the, q)
d.edges <- dilate.edges(polygon.the, q)
display(polygon.the, d.points, d.edges, main="-150 to 30 degrees")
q <- sector(radius, -9/20*pi, -9/20*pi)
d.points <- dilate.points(polygon.the, q)
d.edges <- dilate.edges(polygon.the, q)
display(polygon.the, d.points, d.edges, main="171 degrees")
})
Il tempo di calcolo per questo esempio (dall'ultima cifra), con N = 60 e M = 121 (a sinistra), M = 181 (al centro) e M = 2 (a destra), è stato di un quarto di secondo. Tuttavia, la maggior parte di questo era per il display. In genere, questo R
codice gestirà circa N M = 1,5 milioni al secondo (impiegando solo 0,002 secondi circa per eseguire tutti i calcoli di esempio mostrati). Tuttavia, l'aspetto del prodotto M N implica che dilatazioni di molte figure o figure complicate attraverso un quartiere dettagliato potrebbero richiedere molto tempo, quindi attenzione! Benchmark dei tempi per i problemi più piccoli prima di affrontarne uno grande. In tali circostanze si potrebbe cercare una soluzione basata su raster (che è molto più facile da implementare, che richiede essenzialmente un solo calcolo di vicinato).
Questo è piuttosto ampio, ma potresti: