El índice espacial RTree no da como resultado un cálculo de intersección más rápido

9

Tengo un código que estoy usando para determinar qué Polígono Shapely / MultiPolygons se cruzan con una serie de Shapely LineStrings. A través de las respuestas a esta pregunta, el código ha pasado de esto:

import fiona
from shapely.geometry import LineString, Polygon, MultiPolygon, shape

# Open each layer
poly_layer = fiona.open('polygon_layer.shp')
line_layer = fiona.open('line_layer.shp')

# Convert to lists of shapely geometries
the_lines = [shape(line['geometry']) for line in line_layer]
the_polygons = [(poly['properties']['GEOID'], shape(poly['geometry'])) for poly in poly_layer]

# Check for Polygons/MultiPolygons that the LineString intersects with
covered_polygons = {}
for poly_id, poly in the_polygons:
    for line in the_lines:
        if poly.intersects(line):
            covered_polygons[poly_id] = covered_polygons.get(poly_id, 0) + 1

donde se verifica cada posible intersección, para esto:

import fiona
from shapely.geometry import LineString, Polygon, MultiPolygon, shape
import rtree

# Open each layer
poly_layer = fiona.open('polygon_layer.shp')
line_layer = fiona.open('line_layer.shp')

# Convert to lists of shapely geometries
the_lines = [shape(line['geometry']) for line in line_layer]
the_polygons = [(poly['properties']['GEOID'], shape(poly['geometry'])) for poly in poly_layer]

# Create spatial index
spatial_index = rtree.index.Index()
for idx, poly_tuple in enumerate(the_polygons):
    _, poly = poly_tuple
    spatial_index.insert(idx, poly.bounds)

# Check for Polygons/MultiPolygons that the LineString intersects with
covered_polygons = {}
for line in the_lines:
    for idx in list(spatial_index.intersection(line.bounds)):
        if the_polygons[idx][1].intersects(line):
            covered_polygons[idx] = covered_polygons.get(idx, 0) + 1

donde el índice espacial se usa para reducir el número de verificaciones de intersección.

Con los archivos de forma que tengo (aproximadamente 4000 polígonos y 4 líneas), el código original realiza 12936 .intersection()comprobaciones y tarda aproximadamente 114 segundos en ejecutarse. La segunda pieza de código que usa el índice espacial realiza solo 1816 .intersection()comprobaciones, pero también tarda aproximadamente 114 segundos en ejecutarse.

El código para construir el índice espacial solo tarda de 1 a 2 segundos en ejecutarse, por lo que las comprobaciones de 1816 en la segunda parte del código tardan casi la misma cantidad de tiempo en realizarse que las comprobaciones de 12936 en el código original (desde la carga de shapefiles y la conversión a geometrías Shapely es lo mismo en ambas partes del código).

No veo ninguna razón por la que el índice espacial haga que la .intersects()verificación tarde más, por lo que no sé por qué sucede esto.

Solo puedo pensar que estoy usando el índice espacial RTree incorrectamente. Pensamientos?

derNincompoop
fuente

Respuestas:

6

Mi respuesta se basa esencialmente en otra respuesta de @gene aquí:

Unión espacial más eficiente en Python sin QGIS, ArcGIS, PostGIS, etc.

Propuso la misma solución usando dos métodos diferentes, con o sin un índice espacial.

Él (correctamente) declaró:

Cuál es la diferencia ?

  • Sin el índice, debe recorrer en iteración todas las geometrías (polígonos y puntos).
  • Con un índice espacial delimitador ( Spatial Index RTree ), itera solo a través de las geometrías que tienen la oportunidad de cruzarse con su geometría actual ('filtro' que puede ahorrar una cantidad considerable de cálculos y tiempo ...)
  • pero un índice espacial no es una varita mágica. Cuando se debe recuperar una gran parte del conjunto de datos, un índice espacial no puede proporcionar ningún beneficio de velocidad.

Estas oraciones se explican por sí mismas, pero preferí citar a @gene en lugar de proponer las mismas conclusiones que las mías (por lo tanto, ¡todos los créditos van a su brillante trabajo!).

Para una mejor comprensión del índice espacial Rtree, puede recuperar información útil siguiendo estos enlaces:

Otra gran introducción sobre el uso de índices espaciales puede ser este artículo de @Nathan Woodrow .

mgri
fuente
Entiendo que el índice espacial funcionará mejor cuando pueda reducir las geometrías de interés a la menor cantidad posible. Es por eso que comparé el número de geometrías de interés al usar el método ingenuo (12936) con el número de geometrías al usar el índice espacial (1816). El intersects()método lleva más tiempo cuando se usa el índice espacial (consulte la comparación de tiempo anterior), por lo que no estoy seguro de si estoy usando el índice espacial incorrectamente. Al leer la documentación y las publicaciones vinculadas, creo que sí, pero esperaba que alguien pudiera señalar si no fuera así.
derNincompoop
Su declaración final fue: "Solo puedo pensar que estoy usando el índice espacial Rtree incorrectamente", así que pensé que estaba confundido sobre el uso del índice espacial y subrayé su significado en mi respuesta (que no estaba fuera de lugar) tema). Está intentando realizar algún tipo de análisis estadístico, pero el número de geometrías e intentos involucrados no debería ser suficiente para una mejor comprensión del problema. Este comportamiento puede depender del número de geometrías involucradas (un número muy pequeño para apreciar el poder del índice espacial) o de su máquina.
mgri
4

Solo para agregar a mgri respuesta.

Es importante comprender qué es un índice espacial ( ¿Cómo implementar correctamente un cuadro de límites para Shapely y Fiona? ). Con mi ejemplo en Cómo determinar eficientemente cuál de los miles de polígonos se cruzan con una cadena lineal

ingrese la descripción de la imagen aquí

Puedes crear un índice espacial con los polígonos

idx = index.Index()
for feat in poly_layer:
    geom = shape(feat['geometry'])
    id = int(feat['id'])
    idx.insert(id, geom.bounds,feat)

Límites del índice espacial (límites de polígonos en verde)

ingrese la descripción de la imagen aquí

O con los LineStrings

  idx = index.Index()
  for feat in line_layer:
      geom = shape(feat['geometry'])
      id = int(feat['id'])
      idx.insert(id, geom.bounds,feat)

Límites del índice espacial (LineString encuadernado en rojo)

ingrese la descripción de la imagen aquí

Ahora, itera solo a través de las geometrías que tienen la posibilidad de cruzarse con su geometría actual (en amarillo)

ingrese la descripción de la imagen aquí

Utilizo aquí el índice espacial LineStrings (los resultados son los mismos pero con su ejemplo de 4000 polígonos y 4 líneas ...).

for feat1 in poly_layer:
    geom1 = shape(feat1['geometry'])
    for id in idx.intersection(geom1.bounds):
        feat2 = line_layer[id]
        geom2 = shape(feat2['geometry'])
        if geom2.intersects(geom1):
            print 'Polygon {} intersects line {}'.format(feat1['id'], feat2['id'])

  Polygon 2 intersects line 0
  Polygon 3 intersects line 0
  Polygon 6 intersects line 0
  Polygon 9 intersects line 0

También puedes usar un generador ( ejemplo.py )

def build_ind():
     with fiona.open('polygon_layer.shp') as shapes:
         for s in shapes:
             geom = shape(s['geometry'])
             id = int(s['id'])
             yield (id, geom.bounds, s)

 p= index.Property()
 tree = index.Index(build_ind(), properties=p)
 # first line of line_layer
 first = shape(line_layer.next()['geometry'])
 # intersection of first with polygons
 tuple(tree.intersection(first.bounds))
 (6, 2, 3, 9)

Puede examinar el script GeoPandas sjoin.py para comprender el uso de Rtree .

Hay muchas soluciones pero no olvides que

  • El índice espacial no es una varita mágica ...
gene
fuente
Cuando uso el método ingenuo (donde realizo una prueba de intersección entre cada combinación de Polygon y LineString) termino realizando 12936 pruebas de este tipo. Cuando uso el índice espacial, solo tengo que realizar 1816 pruebas. Creo que esto significa que el índice espacial proporciona valor en este caso de uso. Sin embargo, cuando sincronizo el código, realizar las pruebas de 1816 TOMA TANTO como realizar las pruebas 12936. ¿No debería ser más rápido el código con el índice espacial ya que se realizan más de 11000 pruebas menos?
derNincompoop
Así que investigué esto y descubrí que las ~ 11000 pruebas realizadas solo por el código ingenuo tardan menos de 1 segundo en realizarse, mientras que las pruebas de 1816 realizadas por ambos conjuntos de códigos tardan 112 segundos en realizarse. Ahora entiendo lo que quiere decir con 'Índice espacial no es una varita mágica', aunque redujo la cantidad de pruebas necesarias, las que se necesitaron fueron las que más contribuyeron al momento.
derNincompoop
2

Editar: para aclarar esta respuesta, creí incorrectamente que todas las pruebas de intersección tomaron aproximadamente la misma cantidad de tiempo. Este no es el caso. La razón por la que no obtuve la velocidad que esperaba al usar un índice espacial es que la selección de las pruebas de intersección son las que tomaron más tiempo en primer lugar.

Como ya han dicho gene y mgri, un índice espacial no es una varita mágica. Aunque el índice espacial redujo el número de pruebas de intersección que debían realizarse desde 12936 hasta solo 1816, las pruebas de 1816 son la prueba que, en primer lugar, tomó la gran mayoría del tiempo para calcular.

El índice espacial se está utilizando correctamente, pero la suposición de que cada prueba de intersección toma aproximadamente la misma cantidad de tiempo es lo incorrecto. El tiempo requerido por la prueba de intersección puede variar mucho (0.05 segundos versus 0.000007 segundos).

derNincompoop
fuente
1
No se puede tener en cuenta cómo el índice espacial influye en la velocidad de la intersección posterior porque solo pertenece a la complejidad de las geometrías involucradas. Para su caso, si las geometrías "A" y "B" se intersectan en 0.05 segundos, aún se intersecarán en 0.05 segundos incluso si previamente utilizó un índice espacial (esto es obviamente una declaración teórica, porque creo que sabe que el procesamiento de ¡cualquier cosa dentro de un procesador está relacionada con muchos otros factores!).
mgri