Como criar um buffer orientado usando o arcpy?

9

Eu gostaria de criar um buffer orientado para cada polígono no meu shapefile usando o arcpy. Por orientação, quero dizer que tenho dois ângulos a1 e a2 que restringem a direção do buffer. Isso é representado no gráfico abaixo: insira a descrição da imagem aqui

Alguma ideia?

WAF
fonte
3
Seria necessário mais informações sobre os ângulos. De que eixo você está medindo os ângulos? CW ou CCW? Como você localiza cada ângulo no polígono? Com que tipos de polígonos estamos lidando? (Um círculo não é um polígono.) #
Paul
11
+1 a @Paul, mas achei que um círculo era um polígono até ler isso .
PolyGeo
+1 também! Usei o círculo para ilustrar facilmente o problema. Os polígonos são os resultados de uma segmentação no eCognition seguida por uma classificação para identificar uma classe. Os ângulos a1 e a2 derivam do ângulo de azimute da iluminação da imagem de satélite segmentada. No exemplo, o ângulo do azimute seria igual a 0, a1 e a2 igual a 0 +/- 15 ° (arbitrariamente fixado em 15 °).
WAF
2
@PolyGeo "Polygon" é usado de maneira um pouco diferente no SIG e na matemática. Aqui, refere-se a uma representação digital de uma (bi-dimensional) a região ou o seu fecho . As regiões são tipicamente (mas nem sempre) representadas por aproximações poligonais , mas - como sabemos que as representações de nosso computador são apenas aproximações - eliminamos "aproximação" e usamos apenas "polígono".
whuber

Respostas:

20

Sumário

Essa resposta coloca a questão em um contexto maior, descreve um algoritmo eficiente aplicável à representação de recursos de shapefile (como "vetores" ou "cadeias de linhas" de pontos), mostra alguns exemplos de sua aplicação e fornece código de trabalho para usar ou portar um ambiente GIS.

fundo

Este é um exemplo de dilatação morfológica. Em geral, uma dilatação "espalha" os pontos de uma região em seus bairros; a coleção de pontos onde eles terminam é a "dilatação". As aplicações no SIG são numerosas: modelando a propagação do fogo, o movimento das civilizações, a disseminação das plantas e muito mais.

Matematicamente, e em uma generalidade muito grande (mas útil), uma dilatação espalha um conjunto de pontos em uma variedade Riemanniana (como um plano, esfera ou elipsóide). A expansão é estipulada por um subconjunto do feixe tangente nesses pontos. Isso significa que em cada um dos pontos é dado um conjunto de vetores (direções e distâncias) (eu chamo isso de "vizinhança"); cada um desses vetores descreve um caminho geodésico começando em seu ponto base. O ponto base é "espalhado" até os fins de todos esses caminhos. (Para a definição muito mais limitada de "dilatação" convencionalmente empregada no processamento de imagens, consulte o artigo da Wikipedia . A função de espalhamento é conhecida como mapa exponencial em geometria diferencial.)

O "buffer" de um recurso é um dos exemplos mais simples dessa dilatação: um disco de raio constante (o raio do buffer) é criado (pelo menos conceitualmente) em torno de cada ponto do recurso. A união desses discos é o buffer.

Essa pergunta pede o cálculo de uma dilatação um pouco mais complicada, onde a propagação é permitida apenas dentro de um determinado intervalo de ângulos (isto é, dentro de um setor circular). Isso faz sentido apenas para recursos que não envolvem uma superfície consideravelmente curva (como pequenos recursos na esfera ou elipsóide ou quaisquer recursos no plano). Quando estamos trabalhando no plano, também é significativo orientar todos os setores na mesma direção. (Se estivéssemos modelando a propagação do fogo pelo vento, gostaríamos que os setores fossem orientados com o vento e seus tamanhos também pudessem variar com a velocidade do vento: essa é uma motivação para a definição geral de dilatação que dei. ) (Em superfícies curvas como um elipsóide, é impossível, em geral, orientar todos os setores na direção "mesma".)

Nas seguintes circunstâncias, a dilatação é relativamente fácil de calcular:

  • O recurso está no plano (ou seja, estamos dilatando um mapa do recurso e, com sorte, o mapa é bastante preciso).

  • A dilatação será constante : a propagação em todos os pontos do elemento ocorrerá em vizinhanças congruentes da mesma orientação.

  • Este bairro comum é convexo. A convexidade simplifica e acelera muito os cálculos.

Essa questão se encaixa nessas circunstâncias especializadas: pede a dilatação de polígonos arbitrários por setores circulares cujas origens (os centros dos discos de onde vieram) estão localizadas nos pontos de base. Desde que esses setores não ultrapassem 180 graus, serão convexos. (Os setores maiores sempre podem ser divididos ao meio em dois setores convexos; a união das duas dilatações menores dará o resultado desejado.)


Implementação

Como estamos realizando cálculos euclidianos - fazendo a expansão no plano - podemos dilatar um ponto apenas traduzindo a vizinhança da dilatação para esse ponto. (Para fazer isso, o bairro precisa de uma origemisso corresponderá ao ponto base. Por exemplo, a origem dos setores nesta questão é o centro do círculo a partir do qual eles são formados. Essa origem está na fronteira do setor. Na operação padrão de buffer GIS, a vizinhança é um círculo com origem no centro; agora a origem está no interior do círculo. Escolher uma origem não é um grande problema computacionalmente, porque uma mudança de origem apenas muda toda a dilatação, mas pode ser um grande problema em termos de modelagem de fenômenos naturais. A sectorfunção no código abaixo ilustra como uma origem pode ser especificada.)

A dilatação de um segmento de linha pode ser complicada, mas para uma vizinhança convexa, podemos criar a dilatação como a união das dilatações dos dois pontos finais, juntamente com um paralelogramo cuidadosamente escolhido. (No interesse do espaço, não pararei para provar afirmações matemáticas como essa, mas encorajo os leitores a tentar suas próprias provas, porque é um exercício perspicaz.) Aqui está uma ilustração usando três setores (mostrados em rosa). Eles têm raios unitários e seus ângulos são dados nos títulos. O próprio segmento de linha tem comprimento 2, é horizontal e é mostrado em preto:

Dilatações de segmento

Os paralelogramos são encontrados localizando os pontos cor de rosa que estão o mais longe possível do segmento apenas na direção vertical . Isso fornece dois pontos inferiores e dois pontos superiores ao longo de linhas paralelas ao segmento. Nós apenas temos que juntar os quatro pontos em um paralelogramo (mostrado em azul). Observe, à direita, como isso faz sentido, mesmo quando o setor em si é apenas um segmento de linha (e não um polígono verdadeiro): lá, todos os pontos do segmento foram traduzidos em uma direção 171 graus a leste do norte por uma distância que variava de 0 a 1. O conjunto desses pontos de extremidade é o paralelogramo mostrado. Os detalhes desse cálculo aparecem na bufferfunção definida no dilate.edgescódigo abaixo.

Para dilatar uma polilinha , formamos a união das dilatações dos pontos e segmentos que a formam. As duas últimas linhas de dilate.edgesexecutar esse loop.

A dilatação de um polígono requer a inclusão do interior do polígono juntamente com a dilatação de seus limites. (Essa afirmação faz algumas suposições sobre a vizinhança de dilatação. Uma é que todas as vizinhanças contêm o ponto (0,0), o que garante que o polígono seja incluído em sua dilatação. No caso de vizinhanças variáveis, também assume que a dilatação de qualquer interior O ponto do polígono não se estende além da dilatação dos pontos de contorno. É o caso de vizinhanças constantes.)

Vejamos alguns exemplos de como isso funciona, primeiro com um nonagon (escolhido para revelar detalhes) e depois com um círculo (escolhido para corresponder à ilustração na pergunta). Os exemplos continuarão usando os mesmos três bairros, mas encolheram até um raio de 1/3.

Dilatações de um nonágono

Nesta figura, o interior do polígono é cinza, as dilatações pontuais (setores) são rosa e as dilatações das bordas (paralelogramos) são azuis.

Dilatações de um círculo

O "círculo" é realmente apenas 60 gon, mas se aproxima muito bem de um círculo.


atuação

Quando o recurso base é representado por N pontos e a vizinhança de dilatação por M pontos, esse algoritmo requer esforço de O (N M) . Ele precisa ser seguido simplificando a bagunça de vértices e arestas na união, o que pode exigir esforço O (N M log (N M)): isso é algo a ser solicitado ao SIG; não deveríamos ter que programar isso.

O esforço computacional pode ser aprimorado para O (M + N) para recursos de base convexos (porque você pode descobrir como percorrer o novo limite mesclando adequadamente as listas de vértices que descrevem os limites das duas formas originais). Isso também não precisaria de limpeza posterior.

Quando a vizinhança da dilatação muda lentamente de tamanho e / ou orientação à medida que você avança no recurso base, a dilatação da aresta pode ser aproximada do casco convexo da união das dilatações de seus pontos finais. Se as duas vizinhanças de dilatação tiverem pontos M1 e M2, isso pode ser encontrado com esforço O (M1 + M2) usando um algoritmo descrito em Shamos & Preparata, Geometria Computacional . Portanto, deixando K = M1 + M2 + ... + M (N) ser o número total de vértices nas vizinhanças de dilatação de N, podemos calcular a dilatação no tempo de O (K * log (K)).

Por que quereríamos lidar com essa generalização se tudo o que queremos é um buffer simples? Para grandes recursos na Terra, uma vizinhança de dilatação (como um disco) que possui tamanho constante na realidade pode ter tamanho variável no mapa em que esses cálculos são realizados. Assim, obtemos uma maneira de fazer cálculos precisos para o elipsóide , continuando a desfrutar de todas as vantagens da geometria euclidiana.


Código

Os exemplos foram produzidos com este Rprotótipo, que pode ser facilmente transportado para a sua linguagem favorita (Python, C ++, etc.). Na estrutura, é paralela à análise relatada nesta resposta e, portanto, não precisa de explicação separada. Os comentários esclarecem alguns dos detalhes.

(Pode ser interessante observar que os cálculos trigonométricos são usados apenas para criar os recursos de exemplo - que são polígonos regulares - e os setores. Nenhuma parte dos cálculos de dilatação requer 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")
})

O tempo de computação para este exemplo (da última figura), com N = 60 e M = 121 (esquerda), M = 181 (meio) e M = 2 (direita), foi de um quarto de segundo. No entanto, a maior parte disso foi para a exibição. Normalmente, esse Rcódigo processa cerca de N M = 1,5 milhão por segundo (levando apenas 0,002 segundos ou mais para fazer todos os cálculos de exemplo mostrados). No entanto, a aparência do produto M N implica que dilatações de muitas figuras ou figuras complicadas por uma vizinhança detalhada podem levar um tempo considerável, portanto, cuidado! Avalie o momento para problemas menores antes de enfrentar um grande problema. Em tais circunstâncias, pode-se procurar uma solução baseada em varredura (que é muito mais fácil de implementar, exigindo essencialmente apenas um único cálculo de vizinhança).

whuber
fonte
Uau, isso é muito detalhado e fascinante. Eu não esperava menos.
Paul
1

Isso é bastante amplo, mas você pode:

  1. tamponar o polígono original
  2. encontre o ponto de origem dos raios "orientados" a serem criados no limite do polígono (algum tipo de ponto de tangência?)
  3. crie / estenda uma linha a partir desse ponto até uma distância além do buffer usando o ângulo discutido nos comentários da pergunta.
  4. cruze essa linha com o buffer e o polígono original. Provavelmente, isso poderia ser feito ao mesmo tempo que 3) com argumentos adequados para estender.
  5. extrair o novo polígono "buffer orientado" do conjunto resultante de polígonos
Roland
fonte
Eu acredito que o OP significa um "buffer orientado" no sentido de uma dilatação morfológica de cada forma por um setor de um círculo. (Esta descrição dá imediatamente uma solução raster; mas desde shapefiles estão em formato vetorial, um vetor solução seria desejável É complicado de fazer..)
whuber
Espero que o OP esclareça esse ponto. Entrei na minha linha de pensamento com base no gráfico, que nem sempre é o mais seguro. De qualquer forma, embora se possa colocar uma forma irregular de células em relação a um local calculado (eu fiz isso no gridedit ... estou me sentindo velho!), Eu pensaria que uma solução vetorial seria mais limpa / alavancaria melhor as funções vetoriais do Arc . O método geral é provavelmente análogo, independentemente do modelo de dados. Talvez um pouco mais de codificação para o usuário no lado raster.
Roland
Na verdade, não há necessidade de codificação no lado da varredura :-). Isso pode ser feito de várias maneiras, incluindo estatísticas focais com um bairro adequadamente definido. Concordo que uma solução vetorial é preferível aqui: mais limpa e mais precisa. Para conjuntos de dados extremamente grandes ou complexos, ele pode atolar, embora uma solução raster seja rápida, portanto vale sempre a pena saber como fazê-lo nos dois sentidos.
whuber
Estava pensando em estatísticas focais , mas não tinha certeza se o formato + ângulo do OP seria difícil de combinar em um único bairro .
Roland
Somente o setor deve ser descrito pelo bairro, o que é fácil de fazer .
whuber