Entendendo o uso de índices espaciais com o RTree?

13

Estou tendo problemas para entender o uso de índices espaciais com o RTree.

Exemplo: tenho 300 pontos de buffer e preciso conhecer a área de interseção de cada buffer com um shapefile de polígono. O arquivo de forma do polígono possui> 20.000 polígonos. Foi sugerido o uso de índices espaciais para acelerar o processo.

SO ... Se eu criar um índice espacial para o meu shapefile de polígono, ele será "anexado" ao arquivo de alguma forma ou o índice permanecerá sozinho? Ou seja, depois de criá-lo, posso apenas executar minha função de interseção no arquivo polígono e obter resultados mais rápidos? A interseção "verá" que existem índices espaciais e sabe o que fazer? Ou preciso executá-lo no índice e relacionar esses resultados ao meu arquivo de polígono original por meio de FIDs ou algo assim?

A documentação do RTree não está me ajudando muito (provavelmente porque estou apenas aprendendo programação). Eles mostram como criar um índice lendo pontos criados manualmente e consultando-o em relação a outros pontos criados manualmente, que retornam IDs contidos na janela. Faz sentido. Mas eles não explicam como isso se relacionaria com algum arquivo original de onde o índice teria vindo.

Eu estou pensando que deve ser algo como isto:

  1. Puxe bboxes para cada recurso de polígono do meu shapefile de polígono e coloque-os em um índice espacial, fornecendo a eles um ID que seja igual ao ID no shapefile.
  2. Consulte esse índice para obter os IDs que se cruzam.
  3. Em seguida, execute novamente a interseção apenas nos recursos do meu shapefile original que foram identificados consultando meu índice (não tenho certeza de como eu faria essa última parte).

Eu tenho a ideia certa? Estou faltando alguma coisa?


No momento, estou tentando fazer com que esse código funcione em um shapefile de ponto que contém apenas um recurso de ponto e um shapefile de polígono que contém> 20.000 recursos de polígono.

Estou importando os shapefiles usando o Fiona, adicionando o índice espacial usando o RTree e tentando fazer a interseção usando o Shapely.

Meu código de teste é assim:

#point shapefile representing location of desired focal statistic
traps = fiona.open('single_pt_speed_test.shp', 'r') 

#polygon shapefile representing land cover of interest 
gl = MultiPolygon([shape(pol['geometry']) for pol in fiona.open('class3_aa.shp', 'r')]) 

#search area
areaKM2 = 20

#create empty spatial index
idx = index.Index()

#set initial search radius for buffer
areaM2 = areaKM2 * 1000000
r = (math.sqrt(areaM2/math.pi))

#create spatial index from gl
for i, shape in enumerate(gl):
    idx.insert(i, shape.bounds)

#query index for ids that intersect with buffer (will eventually have multiple points)
for point in traps:
        pt_buffer = shape(point['geometry']).buffer(r)
        intersect_ids = pt_buffer.intersection(idx)

Mas continuo recebendo TypeError: o objeto 'Polygon' não pode ser chamado

CSB
fonte
1
Um índice espacial é integral e transparente ao conjunto de dados (contido, não uma única entidade da perspectiva do usuário) O software que executa as interseções está ciente e usará índices espaciais para criar uma lista curta para realizar a interseção real, informando rapidamente o software quais recursos devem ser considerados para uma inspeção mais detalhada e que não estão claramente próximos da interseção. Como você cria um depende do seu software e do seu tipo de dados ... forneça mais informações sobre esses pontos para obter ajuda mais específica. Para um arquivo de forma, é o arquivo .shx.
Michael Stimson
4
.shx não é um índice espacial. É apenas o arquivo de deslocamento de acesso dinâmico do registro de largura variável. .sbn / .sbx é o par de índices espaciais do ArcGIS shapefile, embora a especificação para eles não tenha sido divulgada.
Vince
1
Também .qixé o índice quadtree MapServer / GDAL / OGR / SpatiaLite
Mike T
Sua ideia é perfeita para Spatialite, que não possui um índice espacial real. A maioria dos outros formatos, se eles suportam índices espaciais, o fazem de forma transparente.
precisa saber é o seguinte
2
Você continua recebendo TypeError: 'Polygon' object is not callableseu exemplo de atualização porque substitui a shapefunção que você importou de bem torneado com um objeto Polygon criado com esta linha:for i, shape in enumerate(gl):
user2856 25/16

Respostas:

12

Essa é a essência disso. A árvore R permite fazer uma primeira passagem muito rápida e fornece um conjunto de resultados que terão "falsos positivos" (caixas delimitadoras podem se cruzar quando as geometrias não precisarem). Em seguida, você examina o conjunto de candidatos (buscando-os no shapefile pelo índice) e faz um teste de interseção matematicamente preciso usando, por exemplo, Shapely. Essa é a mesma estratégia empregada em bancos de dados espaciais como o PostGIS.

sgillies
fonte
1
Bom trocadilho (GiST)! O GiST é normalmente descrito como uma variante da B-Tree, mas o Postgresql possui uma implementação do GiST da R-Tree. Embora o wiki não seja necessariamente a melhor referência para citar, ele possui um bom diagrama para explicar as pesquisas em caixas delimitadoras.
MappaGnosis
Pode valer a pena aprender uma maneira manual de usar o índice da árvore R, como nas etapas 2 e 3. Este blog sobre o OGC GeoPackage, que também suporta a árvore R, como tabelas de banco de dados separadas, mostra algumas capturas de tela e SQL openjump.blogspot.fi / 2014/02 /… .
user30184
9

Você quase conseguiu, mas cometeu um pequeno erro. Você precisa usar o intersectionmétodo no índice espacial, em vez de passar o índice para o intersectionmétodo no ponto de buffer. Depois de encontrar uma lista de recursos em que as caixas delimitadoras se sobrepõem, é necessário verificar se o ponto em buffer realmente cruza as geometrias.

import fiona
from shapely.geometry import mapping
import rtree
import math

areaM2 = areaKM2 * 1000000
r = (math.sqrt(areaM2/math.pi))

# open both layers
with fiona.open('single_pt_speed_test.shp', 'r') as layer_pnt:
    with fiona.open('class3_aa.shp', 'r') as layer_land:

        # create an empty spatial index object
        index = rtree.index.Index()

        # populate the spatial index
        for fid, feature in layer_land.items():
            geometry = shape(feature['geometry'])
            idx.insert(fid, geometry.bounds)

        for feature in layer_pnt:
            # buffer the point
            geometry = shape(feature['geometry'])
            geometry_buffered = geometry.buffer(r)

            # get list of fids where bounding boxes intersect
            fids = [int(i) for i in index.intersection(geometry_buffered.bounds)]

            # access the features that those fids reference
            for fid in fids:
                feature_land = layer_land[fid]
                geometry_land = shape(feature_land['geometry'])

                # check the geometries intersect, not just their bboxs
                if geometry.intersects(geometry_land):
                    print('Found an intersection!')  # do something useful here

Se você estiver interessado em encontrar pontos a uma distância mínima da sua classe de terra, use o distancemétodo (troque a seção apropriada da anterior).

for feature in layer_pnt:
    geometry = shape(feature['geometry'])

    # expand bounds by r in all directions
    bounds = [a+b*r for a,b in zip(geometry.bounds, [-1, -1, 1, 1])]

    # get list of fids where bounding boxes intersect
    fids = [int(i) for i in index.intersection(geometry_buffered.bounds)]

    for fid in fids:
        feature_land = layer_land[fid]
        geometry_land = shape(feature_land['geometry'])

        # check the geometries are within r metres
        if geometry.distance(geometry_land) <= r:
            print('Found a match!')

Se estiver demorando muito tempo para criar seu índice espacial e você o fizer mais do que algumas vezes, tente serializar o índice em um arquivo. A documentação descreve como fazer isso: http://toblerity.org/rtree/tutorial.html#serializing-your-index-to-a-file

Você também pode observar o carregamento em massa das caixas delimitadoras na rtree usando um gerador, como este:

def gen(collection):
    for fid, feature in collection.items():
        geometry = shape(feature['geometry'])
        yield((fid, geometry.bounds, None))
index = rtree.index.Index(gen(layer_land))
Snorfalorpagus
fonte
2

Sim, essa é a ideia. Aqui está um trecho deste tutorial sobre o uso de um índice espacial r-tree em Python , usando formas bem torneadas, Fiona e geopandas:

Uma árvore r representa objetos individuais e suas caixas delimitadoras (o "r" é para "retângulo") como o nível mais baixo do índice espacial. Em seguida, agrega objetos próximos e os representa com sua caixa delimitadora agregada no próximo nível mais alto do índice. Em níveis ainda mais altos, a árvore r agrega caixas delimitadoras e as representa por sua caixa delimitadora, iterativamente, até que tudo seja aninhado em uma caixa delimitadora de nível superior. Para pesquisar, a r-tree pega uma caixa de consulta e, começando no nível superior, vê quais (se houver) caixas delimitadoras a interceptam. Em seguida, ele expande cada caixa delimitadora de interseção e vê quais caixas delimitadoras filho dentro dela interceptam a caixa de consulta. Isso ocorre recursivamente até que todas as caixas de interseção sejam pesquisadas até o nível mais baixo e retorna os objetos correspondentes do nível mais baixo.

eos
fonte