Como acessar com eficiência os recursos retornados pelo QgsSpatialIndex?

9

O PyQGIS Cookbook explica como configurar o índice espacial, mas explica apenas metade de seu uso:

criar índice espacial - o código a seguir cria um índice vazio

index = QgsSpatialIndex()

adicionar recursos ao índice - o índice pega o objeto QgsFeature e o adiciona à estrutura de dados interna. Você pode criar o objeto manualmente ou usar um da chamada anterior para o nextFeature () do provedor

index.insertFeature(feat)

depois que o índice espacial for preenchido com alguns valores, você poderá fazer algumas consultas

# returns array of feature IDs of five nearest features
nearest = index.nearestNeighbor(QgsPoint(25.4, 12.7), 5)

Qual é a etapa mais eficiente para obter os recursos reais pertencentes aos IDs de recursos retornados?

underdark
fonte

Respostas:

12
    # assume a list of feature ids returned from index and a QgsVectorLayer 'lyr'
    fids = [1, 2, 4]
    request = QgsFeatureRequest()
    request.setFilterFids(fids)

    features = lyr.getFeatures(request)
    # can now iterate and do fun stuff:
    for feature in features:
        print feature.id(), feature

    1 <qgis._core.QgsFeature object at 0x000000000E987510>
    2 <qgis._core.QgsFeature object at 0x000000000E987400>
    4 <qgis._core.QgsFeature object at 0x000000000E987510>
gsherman
fonte
Obrigado! Snorfalorpagus mencionou que setFilterFids seria consideravelmente mais lento que a solução que ele postou. Você confirma isso?
Subterrâneo
Eu não o usei em grandes conjuntos de resultados, portanto não posso confirmar.
gsherman
11
Confirmo e, no meu caso, rtree é ainda mais rápido que QgsSpatialIndex () (para construção de gráficos planares a partir de camadas muito grandes de polilinhas, a transposição do módulo PlanarGraph com Shapely no PyQGIS. Mas a solução com Fiona, Shapely e rtree ainda é a mais rápido)
gene
11
Acredito que a questão é obter os recursos reais dos IDs de recursos retornados , em vez da velocidade de vários métodos de indexação.
gsherman
7

Em uma postagem de blog sobre o assunto , Nathan Woodrow fornece o seguinte código:

layer = qgis.utils.iface.activeLayer()

# Select all features along with their attributes
allAttrs = layer.pendingAllAttributesList()
layer.select(allAttrs)
# Get all the features to start
allfeatures = {feature.id(): feature for (feature) in layer}

def noindex():
    for feature in allfeatures.values():
        for f in allfeatures.values():
            touches = f.geometry().touches(feature.geometry())
            # It doesn't matter if we don't return anything it's just an example

def withindex():
    # Build the spatial index for faster lookup.
    index = QgsSpatialIndex()
    map(index.insertFeature, allfeatures.values())

    # Loop each feature in the layer again and get only the features that are going to touch.
    for feature in allfeatures.values():
        ids = index.intersects(feature.geometry().boundingBox())
        for id in ids:
            f = allfeatures[id]
            touches = f.geometry().touches(feature.geometry())
            # It doesn't matter if we don't return anything it's just an example

import timeit
print "With Index: %s seconds " % timeit.timeit(withindex,number=1)
print "Without Index: %s seconds " % timeit.timeit(noindex,number=1)

Isso cria um dicionário que permite procurar rapidamente um QgsFeature usando seu FID.

Descobri que para camadas muito grandes isso não é especialmente prático, pois requer muita memória. No entanto, a alternativa (acesso aleatório do recurso desejado) layer.getFeatures(QgsFeatureRequest().setFilterFid(fid))parece comparativamente muito lenta. Não sei por que isso ocorre, pois a chamada equivalente usando as ligações SWIG OGR layer.GetFeature(fid)parece muito mais rápida que isso.

Snorfalorpagus
fonte
11
Usar um dicionário foi muito mais rápido que layer.getFeatures(QgsFeatureRequest().setFilterFid(fid)). Eu estava trabalhando em uma camada com recursos de 140k, e o tempo total para as pesquisas de 140k passou de muitos minutos a segundos.
Håvard Tveite 23/03
5

Para comparações, olhada espacial mais eficiente juntar-se em Python sem QGIS, ArcGIS, PostGIS, etc . A solução apresentada utiliza os módulos Python Fiona , Shapely e rtree (Spatial Index).

Com PyQGIS e o mesmo exemplo, duas camadas pointe polygon:

insira a descrição da imagem aqui

1) Sem um índice espacial:

polygons = [feature for feature in polygon.getFeatures()]
points = [feature for feature in point.getFeatures()]
for pt in points: 
    point = pt.geometry()
    for pl  in polygons:
        poly = pl.geometry()
        if poly.contains(point):
            print point.asPoint(), poly.asPolygon()
(184127,122472) [[(183372,123361), (184078,123130), (184516,122631),   (184516,122265), (183676,122144), (183067,122570), (183128,123105), (183372,123361)]]
(183457,122850) [[(183372,123361), (184078,123130), (184516,122631), (184516,122265), (183676,122144), (183067,122570), (183128,123105), (183372,123361)]]
(184723,124043) [[(184200,124737), (185368,124372), (185466,124055), (185515,123714), (184955,123580), (184675,123471), (184139,123787), (184200,124737)]]
(182179,124067) [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]

2) Com o índice espacial RQ -Tree PyQGIS:

# build the spatial index with all the polygons and not only a bounding box
index = QgsSpatialIndex()
for poly in polygons:
     index.insertFeature(poly)

# intersections with the index 
# indices of the index for the intersections
for pt in points:
    point = pt.geometry()
    for id in index.intersects(point.boundingBox()):
    print id
0
0
1
2

O que esses índices significam?

for i, pt in enumerate(points):
     point = pt.geometry()
     for id in index.intersects(point.boundingBox()):
        print "Point ", i, points[i].geometry().asPoint(), "is in Polygon ", id, polygons[id].geometry().asPolygon()
Point  1 (184127,122472) is in Polygon  0 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]
Point  2 (183457,122850) is in Polygon  0 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]
Point  4 (184723,124043) is in Polygon  1 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]
Point  6 (182179,124067) is in Polygon  2 [[(182520,125175), (183348,124286), (182605,123714), (182252,123544), (181753,123799), (181740,124627), (182520,125175)]]

As mesmas conclusões que na junção espacial mais eficiente no Python sem QGIS, ArcGIS, PostGIS, etc :

  • Sem e índice, você deve percorrer todas as geometrias (polígonos e pontos).
  • Com um índice espacial delimitador (QgsSpatialIndex ()), você itera apenas pelas geometrias que têm a chance de se cruzar com a geometria atual ('filtro', que pode economizar uma quantidade considerável de cálculos e tempo ...).
  • Você também pode usar outros módulos Python de índice espacial ( rtree , Pyrtree ou Quadtree ) com PyQGIS como em Usando um índice espacial QGIS para acelerar seu código (com QgsSpatialIndex () e rtree )
  • mas um índice espacial não é uma varinha mágica. Quando uma parte muito grande do conjunto de dados precisa ser recuperada, um Índice Espacial não pode oferecer nenhum benefício de velocidade.

Outro exemplo no GIS se: Como encontrar a linha mais próxima de um ponto no QGIS? [duplicado]

gene
fonte
Obrigado por todas as explicações adicionais. Basicamente, sua solução usa uma lista em vez de um ditado como o Snorfalorpagus. Portanto, há realmente parece haver layer.getFeatures função ([ids]) ...
Subterrâneo
O objetivo desta explicação é puramente geométrico e é muito fácil adicionar uma layer.getFeatures ([ids]) funciona como na junção espacial mais eficiente no Python sem QGIS, ArcGIS, PostGIS, etc
gene
0

Aparentemente, o único método para obter um bom desempenho é evitar ou agrupar chamadas para layer.getFeatures (), mesmo que o filtro seja tão simples quanto um fid.

Agora, aqui está a armadilha: chamar getFeatures é caro. Se você chamá-lo em uma camada vetorial, o QGIS precisará configurar uma nova conexão com o armazenamento de dados (o provedor da camada), criar alguma consulta para retornar dados e analisar cada resultado conforme retornado pelo provedor. Isso pode ser lento, especialmente se você estiver trabalhando com algum tipo de camada remota, como uma tabela PostGIS em uma conexão VPN.

fonte: http://nyalldawson.net/2016/10/speeding-up-your-pyqgis-scripts/

evod
fonte