Determinando se o ponto é circundado usando o processamento de varredura

9

Estou tentando melhorar um processo de vetor / python atualmente extremamente complicado para um modelo de risco natural. No momento, temos um longo script que gera distância / linhas de rumo a partir de um determinado ponto para determinar:

  1. o tipo de polígono que cruza (por exemplo: floresta, grama, pântano, etc.)
  2. a distância para esse polígono
  3. quantas dessas linhas cruzam polígonos, para determinar o quão 'cercado' é.

Há muito mais envolvido, mas essa é a essência. Estou tentando encontrar uma maneira de melhorar isso e estou atualmente perplexo na parte 3. A idéia é determinar se um ponto está completamente cercado por polígonos, a cerca de 200mO PointA está cercado, enquanto o PointB está apenas ~ 50% cercado

Portanto, na minha imagem anexada, eu gostaria que o ponto A fosse marcado como de maior risco que o ponto B, pois está completamente cercado pelos meus polígonos. Isso é repetido por cerca de 13 milhões de pontos, portanto não é uma tarefa pequena e eu preferiria ter uma superfície para derivar valores, em vez de executar nosso script. Estou pensando que deve haver uma variedade de ferramentas de hidrologia ou caminho de custo para fazer isso, mas não consigo entender o que estou pensando.

Como eu pude fazer isso?

Loz
fonte
11
Viewshed está à altura da tarefa, mas precisará de ajuda considerável quando aplicado a 13 milhões de pontos! Pense primeiro em como eliminar pontos cujo ambiente é fácil de verificar, como pontos localizados em regiões externas aos polígonos com diâmetros menores que (digamos) 200m: isso pode excluir "A", mas talvez não "B". "B" nunca seria descartado porque o seu ponto de vista (considerando as áreas dos polígonos como locais muito "altos" que bloqueiam a vista) se estende a mais de 200 m da localização de B.
whuber
Um bom ponto @whuber. certamente sou capaz de reduzir o número total de pontos realmente processados ​​por proximidade e, de fato, também por longos longos (estou falando de endereços geocodificados para que os blocos de apartamentos possam ser reduzidos de 50 pontos para 1), mas continuarei procurando em vários milhões de locais. Também posso dividir tudo em blocos sobrepostos, se necessário. Irá investigar o ponto de vista. Obrigado!
Loz 13/01
Outra tela rápida é calcular uma média focal da grade do indicador 0-1 de seus polígonos usando uma vizinhança anular: em qualquer célula cujo valor seja 1, seus polígonos ocupam todo o perímetro no raio, de onde devem estar cercados. Este é um cálculo rápido e pode eliminar a grande maioria dos seus pontos, dependendo de onde eles estão e de quão complicados são seus polígonos. Você também pode acelerar a triagem inicial redimensionando sua grade com uma resolução mais grossa, como 25 a 50 m ou mais.
whuber
Outra etapa de processamento em potencial, ou etapa de pré-processamento, seria passar seus pontos por uma versão rasterizada do seu conjunto de dados, comparando as estatísticas de uma vizinhança em torno dos pontos. Você pode abstrair seu requisito de 'cercado' como uma estatística da vizinhança de pontos ou, se necessário, pode encontrar os pontos 'fáceis' (ou seja, um ponto completamente dentro de uma área de risco) usando uma vizinhança de varredura, analise os pontos "fáceis" de todos os pontos e use a análise vetorial para o restante dos pontos.
DPierce 17/01
uau minha consulta certamente gerou muito interesse! Obrigado a todos que contribuíram com sugestões e comentários. Vou trabalhar do jeito que todos eles responderão, mas todos levarão algum tempo para eu testar. Eu prometo que vou responder eventualmente!
Loz

Respostas:

6

Existe uma solução de caminho de custo, mas você precisará codificá-la. Aqui está o que pode parecer quando aplicado a todos os pontos da imagem na pergunta (um pouco grosseiro para acelerar os cálculos):

Figura 0

As células pretas são partes dos polígonos circundantes. As cores, variando de laranja claro (curto) a azul (longo), mostram a distância máxima (até um máximo de 50 células) que pode ser alcançada pela passagem da linha de visão sem interceptar as células poligonais. (Qualquer célula fora da extensão desta imagem é tratada como parte dos polígonos.)

Vamos discutir uma maneira eficiente de fazer isso usando uma representação rasterizada dos dados. Nesta representação, todas as células poligonais "circundantes" terão, digamos, valores diferentes de zero e qualquer célula que possa ser "vista através" terá um valor zero.

Etapa 1: pré-computando uma estrutura de dados de vizinhança

Você primeiro precisa decidir o que significa para uma célula bloquear outra. Uma das regras mais justas que posso encontrar é a seguinte: usando coordenadas integrais para linhas e colunas (e assumindo células quadradas), vamos considerar quais células podem bloquear a célula (i, j) da vista na origem (0,0). Nomeio a célula (i ', j') que está mais próxima do segmento de linha que conecta (i, j) a (0,0) entre todas as células cujas coordenadas diferem de iej por no máximo 1. Porque isso nem sempre Para obter uma solução única (por exemplo, com (i, j) = (1,2) ambos (0,1) e (1,1) funcionarão igualmente bem), são necessários alguns meios para resolver os vínculos. Seria bom para essa resolução de vínculos respeitar as simetrias de vizinhanças circulares em grades: negar coordenadas ou alternar as coordenadas preserva essas vizinhanças. Portanto, podemos decidir quais células bloqueiam (i,

Ilustrando esta regra está o seguinte código de protótipo escrito em R. Esse código retorna uma estrutura de dados que será conveniente para determinar o "ambiente" de células arbitrárias em uma grade.

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

O valor de screen(12)foi usado para produzir essa representação dessa relação de triagem: as setas apontam das células para as que as travam imediatamente. Os tons são proporcionais à distância da origem, que fica no meio desse bairro:

figura 1

Esse cálculo é rápido e precisa ser feito apenas uma vez para um determinado bairro. Por exemplo, ao observar 200 m em uma grade com células de 5 m, o tamanho da vizinhança será 200/5 = 40 unidades.

Etapa 2: aplicando a computação aos pontos selecionados

O restante é direto: para determinar se uma célula localizada em (x, y) (nas coordenadas da linha e da coluna) está "cercada" com relação a essa estrutura de dados da vizinhança, execute o teste recursivamente começando com um deslocamento de (i, j) = (0,0) (a origem do bairro). Se o valor na grade do polígono em (x, y) + (i, j) for diferente de zero, a visibilidade será bloqueada lá. Caso contrário, teremos que considerar todas as compensações que poderiam ter sido bloqueadas no deslocamento (i, j) (que são encontradas no tempo O (1) usando a estrutura de dados retornada por screen). Se não houver nenhum bloqueado, alcançamos o perímetro e concluímos que (x, y) não está cercado; portanto, paramos o cálculo (e não nos importamos em inspecionar quaisquer pontos restantes na vizinhança).

Podemos coletar informações ainda mais úteis, acompanhando a maior distância da linha de visão alcançada durante o algoritmo. Se este for menor que o raio desejado, a célula será cercada; caso contrário, não é.

Aqui está um Rprotótipo desse algoritmo. É mais longo do que parece, porque Rnão suporta nativamente a estrutura de pilha (simples) necessária para implementar a recursão; portanto, uma pilha também precisa ser codificada. O algoritmo real começa cerca de dois terços do caminho e precisa de apenas uma dúzia de linhas. (E metade deles simplesmente lida com a situação na borda da grade, verificando índices fora do intervalo dentro da vizinhança. Isso poderia ser mais eficiente simplesmente expandindo a grade do polígono por klinhas e colunas em torno de seu perímetro, eliminando qualquer é necessário verificar o intervalo do índice com o custo de um pouco mais de RAM para manter a grade de polígonos.)

#
# 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: Exemplo

Neste exemplo, as células poligonais são pretas. As cores fornecem a distância máxima da linha de visão (até 50 células) para células não poligonais, variando de laranja claro para distâncias curtas a azul escuro nas distâncias mais longas. (As células têm uma unidade de largura e altura.) As faixas visivelmente evidentes são criadas pelas pequenas "ilhas" poligonais no meio do "rio": cada uma delas bloqueia uma longa fila de outras células.

Análise do algoritmo

A estrutura da pilha implementa uma pesquisa profunda do gráfico de visibilidade da vizinhança em busca de evidências de que uma célula não está cercada. Onde as células estão longe de qualquer polígono, essa pesquisa exigirá a inspeção de apenas células O (k) para uma vizinhança circular de raio-k. Os piores casos ocorrem quando há um pequeno número de células poligonais dispersas na vizinhança, mas mesmo assim o limite da vizinhança não pode ser alcançado: requer inspeção de quase todas as células de cada vizinhança, que é um O (k ^ 2) Operação.

O comportamento a seguir é típico do que será encontrado. Para pequenos valores de k, a menos que os polígonos preencham a maior parte da grade, a maioria das células não poligonais será obviamente sem volta e o algoritmo será escalado como O (k). Para valores intermediários, a escala começa com O (k ^ 2). À medida que k se torna realmente grande, a maioria das células é cercada e esse fato pode ser determinado bem antes de toda a vizinhança ser inspecionada: o esforço computacional do algoritmo atinge, assim, um limite prático. Esse limite é atingido quando o raio da vizinhança se aproxima do diâmetro das maiores regiões não poligonais conectadas na grade.

Como exemplo, usei a countingopção codificada no protótipo de screenpara retornar o número de operações de pilha usadas em cada chamada. Isso mede o esforço computacional. O gráfico a seguir mostra o número médio de operações da pilha em função do raio da vizinhança. Ele exibe o comportamento previsto.

Figura 3

Podemos usar isso para estimar o cálculo necessário para avaliar 13 milhões de pontos em uma grade. Suponha que uma vizinhança de k = 200/5 = 40 seja usada. Então, algumas centenas de operações de pilha serão necessárias, em média (dependendo da complexidade da grade de polígonos e da localização dos 13 milhões de pontos em relação aos polígonos), o que implica que, em uma linguagem compilada eficiente, no máximo alguns milhares de operações numéricas simples será necessário (adicionar, multiplicar, ler, escrever, compensar etc.). A maioria dos PCs será capaz de avaliar a área circundante de um milhão de pontos nesse ritmo. (ORa implementação é muito, muito mais lenta que isso, porque é pobre nesse tipo de algoritmo, e é por isso que só pode ser considerado um protótipo.) Dessa forma, podemos esperar que uma implementação eficiente em uma linguagem razoavelmente eficiente e apropriada - C ++ e Python vem à mente - poderia concluir a avaliação de 13 milhões de pontos em um minuto ou menos, assumindo que toda a grade de polígonos reside na RAM.

Quando uma grade é muito grande para caber na RAM, esse procedimento pode ser aplicado às partes lado a lado da grade. Eles só precisam se sobrepor por klinhas e colunas; tome os máximos nas sobreposições ao aplicar mosaicos nos resultados.

Outras aplicações

A "busca" de um corpo de água está intimamente relacionada à "envolvência" de seus pontos. De fato, se usarmos um raio de vizinhança igual ou maior que o diâmetro do corpo de água, criaremos uma grade da busca (não direcional) em todos os pontos do corpo de água. Usando um raio de vizinhança menor, obteremos pelo menos um limite inferior para a busca em todos os pontos de busca mais altos, o que em algumas aplicações pode ser bom o suficiente (e pode reduzir substancialmente o esforço computacional). Uma variante desse algoritmo que limita a relação "rastreada por" a direções específicas seria uma maneira de calcular a busca com eficiência nessas direções. Observe que essas variantes exigem a modificação do código para screen; o código para panvisibilitynão muda nada.

whuber
fonte
2

Definitivamente, posso ver como alguém pode querer fazer isso com uma solução raster, mas, mesmo com um número reduzido de pontos, esperaria uma resolução muito grande / alta e, portanto, difícil de processar ou conjunto de grades. Dado isso, gostaria de saber se explorar a topologia em um gdb pode ser mais eficiente. Você pode encontrar todos os vazios internos com algo como:

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')`

então você pode trabalhar com topoErrorsVoidPolysseu padrão normal Intersect_analysis()ou o que quer que seja. Você pode precisar mexer na extração de políticas de interesse topoErrorsVoidPolys. O @whuber tem várias postagens excelentes sobre esse tipo de coisa em outros lugares aqui no gis.stackexchange.com.

Roland
fonte
Essa é uma ideia interessante de pré-processamento. Eu acho que poderia ser prontamente adaptado para incorporar o limite de 200m (por buffer e interseção, etc.) Seu ponto de vista sobre o aumento das grades é certamente uma preocupação real. Não existe uma regra no GIS que diga que a solução de um problema deve ser puramente baseada em raster ou vetor (embora haja um princípio que diga que você deve ter um bom motivo para converter de uma representação para outra no meio) de uma análise; aqui, como você sugere, pode haver benefícios substanciais ao fazer exatamente isso).
whuber
0

Se você realmente quer ficar mais raster ... Eu faria algo como esse pseudo-código (não se estremece só porque é óbvio que sou um retrocesso de AML!: P)

  1. pontos de rasterização ("pts_g") e polys ("polys_g" (
  2. voids = regiongroup (con (isnull (polys_g), 1))
  3. pode ser necessário fazer algo para refinar os vazios para eliminar polígonos externos indesejados / área de universo aberto
  4. pts_surrounded = con (vazios, pts_g)

Apenas inventando isso, pode precisar de refinamento.

Roland
fonte
Sua solução não faz referência à distância limite (de, digamos, 200m); portanto, parece não responder corretamente à pergunta.
whuber
você está certo. Isso se aplica à minha outra resposta também. Suponho que alguém possa usá-lo Expand(), mas nesse momento acho que a resposta do @radouxju seria funcionalmente equivalente e provavelmente mais rápida. (nada contra o viewhed, apenas não use muito).
Roland
estava tentando editar quando o tempo acabou. deseja expandir o modo Expand()de dizer fazer isso pts_ge usar apenas Con()para interceptar polys_g.
Roland
0

Se você usar um valor de distância limite (aqui você fala sobre 200 m), a melhor solução é usar a análise vetorial:

1) crie um buffer de 200 m em torno de cada ponto (em preto na ilustração)

2) use a ferramenta de interseção (análise) entre o buffer e os polígonos (em azul na ilustração). Ficará melhor se você fizer isso entre os limites dos polígonos circundantes e o buffer, mas o resultado final será o mesmo.

3) use o recurso para polígono (gerenciamento) para criar polígonos onde seus pontos estão completamente cercados (em vermelho na ilustração)

4) selecione camadas por localização (gerenciamento) ou junção espacial (análise) para identificar os pontos que estão circundados. O uso da junção espacial permite que você tenha informações sobre o polígono de incorporação (área do polígono, estatísticas zonais ...) que podem ser úteis para processamento adicional.

Alternativas 2b) Dependendo de suas necessidades, você pode selecionar por localização os polígonos ao redor a uma distância de 200 m, e então identificar alguns tipos de "compartimento", mas não tão estritamente quanto em 2).

insira a descrição da imagem aqui

Considerando o "caso do labirinto", isso pode ajudar: avaliar quanto tempo é necessário "escapar" do local.

  • Você já pode excluir da análise os pontos que estão totalmente incluídos ou totalmente gratuitos

  • então você converte seus obstáculos em uma varredura e define os valores para NoData onde você possui um polígono e para o tamanho da célula em metros onde você não possui (isso aumentará o custo).

  • terceiro, você pode calcular a distância de custo usando o recém-gerado custo raster

  • finalmente, você usa estatísticas zonais como tabela com base nos limites do buffer convertidos em raster (formando um espaço anular). Se você puder escapar em todas as direções, o mínimo deve ser aproximadamente 200 (dependendo do tamanho da célula da sua análise). Mas se você estiver em um labirinto, o máximo será maior que 200. Portanto, o máximo das estatísticas zonais menos 200 será um valor contínuo, indicando o quão difícil é "escapar".

radouxju
fonte
Por favor, esclareça sua definição de "cercado". A descrição na pergunta sugere que um ponto deve ser considerado "cercado" quando alguma parte do polígono estiver visível em todas as direções ao redor desse ponto (a uma distância de 200 m). Como você testa isso na etapa (3) exatamente? (Não é fácil usando uma análise do vetor!)
whuber
Adicionei uma pequena ilustração, é mais fácil explicar assim. Se o buffer não cruzar um polígono em todas as direções, o loop não será fechado. E se o loop não estiver próximo, isso não fará um polígono.
precisa saber é o seguinte
Não sei ao certo o que você quer dizer com "loop" ou "fechado". Observe que um ponto pode ser "circundado" mesmo quando nenhum círculo de raio r (menos de 200 m) ao redor dele estiver totalmente contido no polígono. Pense em um labirinto: o polígono é tudo, exceto os corredores do labirinto. Pode-se escapar do labirinto começando em qualquer ponto dentro dele, mas a maioria dos pontos será "cercada" no sentido de que o exterior do labirinto não será visível a partir deles.
whuber
do meu entendimento, meio cercado em algum lugar do qual você não pode escapar. Na ilustração, você pode escapar de B, mas não de A. Por outro lado, B pareceria cercado se você usar o viewhed (bem, talvez não a 200 m, pois não há barra de escala na imagem, mas você veja os limites do polígono ao olhar em todas as direções). Eu acho que precisamos de mais detalhes de @Loz
radouxju
Isso não seria uma pergunta difícil se "não puder escapar" fosse o critério a ser verificado: basta agrupar por região o complemento do polígono, manter apenas o componente externo exclusivo e verificar a inclusão nele. Penso que uma leitura atenta da questão - especialmente suas referências a olhar para todos os possíveis rumos - esclarece o sentido em que "cercado" se destina, embora eu concorde que seja afirmado vagamente.
whuber