Come creare un buffer orientato usando arcpy?


9

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: inserisci qui la descrizione dell'immagine

Qualche idea?


3
Uno avrebbe bisogno di maggiori informazioni sugli angoli. Da quale asse stai misurando gli angoli? CW o CCW? Come si individua ogni angolo sul poligono? Con quali tipi di poligoni abbiamo a che fare? (Un cerchio non è un poligono.)
Paul

1
Da +1 a @Paul ma ho pensato che un cerchio fosse un poligono fino a quando non ho letto questo .
PolyGeo

+1 anche! Ho usato il cerchio per illustrare facilmente il problema. I poligoni sono i risultati di una segmentazione in eCognition seguita da una classificazione per identificare una classe. Gli angoli a1 e a2 derivano dall'angolo azimutale di illuminazione dell'immagine satellitare segmentata. Nell'esempio, l'angolo azimutale sarebbe uguale a 0, a1 e a2 pari a 0 +/- 15 ° (fissato arbitrariamente a 15 °).
WAF,

2
@PolyGeo "Polygon" è usato in modo leggermente diverso in GIS che in matematica. Qui si riferisce a una rappresentazione digitale di una regione (bidimensionale) o alla sua chiusura . Le regioni sono in genere (ma non sempre) rappresentate da approssimazioni poligonali , ma - poiché sappiamo che le nostre rappresentazioni al computer sono solo approssimazioni - lasciamo cadere "approssimazione" e usiamo semplicemente "poligono".
whuber

Risposte:


20

Sommario

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.

sfondo

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


Implementazione

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 sectorfunzione 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:

Dilatazioni del segmento

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 bufferfunzione definita all'interno dilate.edgesdel codice seguente.

Per dilatare una polilinea , formiamo l'unione delle dilatazioni dei punti e dei segmenti che la formano. Le ultime due righe dilate.edgeseseguono 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.

Dilatazioni di un nonagon

In questa figura l'interno del poligono è grigio, le dilatazioni dei punti (settori) sono rosa e le dilatazioni dei bordi (parallelogrammi) sono blu.

Dilatazioni di un cerchio

Il "cerchio" è in realtà solo un 60 gon, ma si avvicina bene a un cerchio.


Prestazione

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.


Codice

Gli esempi sono stati prodotti con questo Rprototipo, 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 Rcodice 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).


Caspita, è molto dettagliato e affascinante. Non mi aspettavo di meno.
Paul,

1

Questo è piuttosto ampio, ma potresti:

  1. buffer il poligono originale
  2. trovare il punto di origine dei raggi "orientati" da creare sul confine del poligono (una sorta di punto di tangenza?)
  3. creare / estendere una linea da quel punto ad una distanza oltre il buffer usando l'angolo discusso nei commenti alla domanda.
  4. intersecare quella linea con il buffer e il poligono originale. Questo potrebbe probabilmente essere fatto contemporaneamente a 3) con args appropriati per estendere.
  5. estrarre il nuovo poligono "buffer orientato" dal set di poligoni risultante

Credo che l'OP significhi un "cuscinetto orientato" nel senso di una dilatazione morfologica di ogni forma da parte di un settore di un cerchio. (Questa descrizione fornisce immediatamente una soluzione raster; ma poiché gli shapefile sono in formato vettoriale, sarebbe auspicabile una soluzione vettoriale. È difficile da fare.)
whuber

Speriamo che l'OP chiarirà questo punto. Sono entrato nella mia linea di pensiero basata sulla grafica, che non è sempre la più sicura. In ogni caso, anche se si può posizionare una forma irregolare di cellule rispetto a una posizione calcolata (l'ho fatto in modo gridito ... mi sento vecchio!), Penso che una soluzione vettoriale sarebbe più pulita / sfrutta meglio le funzioni vettoriali di Arc . Il metodo generale è probabilmente analogo, indipendentemente dal modello di dati. Forse un po 'più di codifica per l'utente sul lato raster.
Roland,

In realtà, non è necessaria alcuna codifica sul lato raster :-). Può essere fatto in diversi modi, tra cui statistiche focali con un quartiere adeguatamente definito. Concordo sul fatto che una soluzione vettoriale sia preferibile qui: più pulita e più precisa. Per set di dati estremamente grandi o complessi potrebbe però impantanarsi, mentre una soluzione raster sarà veloce, quindi vale sempre la pena saperlo fare in entrambi i modi.
whuber

Stavo pensando ai focali , ma non ero sicuro che la forma + l'angolo del PO sarebbero stati difficili da combinare in un singolo quartiere .
Roland,

Solo il settore deve essere descritto dal quartiere, il che è facile da fare .
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.