Partecipazione spaziale più efficiente in Python senza QGIS, ArcGIS, PostGIS, ecc


31

Sto tentando di fare un join spaziale in modo molto simile all'esempio qui: esiste un'opzione python per "unire gli attributi per posizione"? . Tuttavia, questo approccio sembra davvero inefficiente / lento. Anche eseguirlo con un modesto 250 punti richiede quasi 2 minuti e fallisce completamente su shapefile con> 1.000 punti. C'è un approccio migliore? Mi piacerebbe farlo interamente in Python senza usare ArcGIS, QGIS, ecc.

Sarei anche interessato a sapere se è possibile sommare gli attributi (ovvero la popolazione) di tutti i punti che ricadono all'interno di un poligono e unire quella quantità allo shapefile del poligono.

Ecco il codice che sto cercando di convertire. Viene visualizzato un errore sulla riga 9:

poly['properties']['score'] += point['properties']['score']

che dice:

TypeError: tipi di operando non supportati per + =: 'NoneType' e 'float'.

Se sostituisco "+ =" con "=", funziona bene ma ciò non somma i campi. Ho anche provato a renderli come numeri interi ma anche questo ha esito negativo.

with fiona.open(poly_shp, 'r') as n: 
  with fiona.open(point_shp,'r') as s:
    outSchema = {'geometry': 'Polygon','properties':{'region':'str','score':'float'}}
    with fiona.open (out_shp, 'w', 'ESRI Shapefile', outSchema, crs) as output:
        for point in s:
            for poly in n:
                if shape(point['geometry']).within(shape(poly['geometry'])):  
                    poly['properties']['score']) += point['properties']['score'])
                    output.write({
                        'properties':{
                            'region':poly['properties']['NAME'],
                            'score':poly['properties']['score']},
                        'geometry':poly['geometry']})

Penso che dovresti modificare la tua seconda domanda da qui in modo che questa rimanga focalizzata su ciò che presumo sia la domanda più importante per te. L'altro può essere ricercato / chiesto separatamente.
PolyGeo

Risposte:


37

Fiona restituisce dizionari Python e non è possibile utilizzarli poly['properties']['score']) += point['properties']['score'])con un dizionario.

Esempio di somma degli attributi usando i riferimenti dati da Mike T:

inserisci qui la descrizione dell'immagine

# read the shapefiles 
import fiona
from shapely.geometry import shape
polygons = [pol for pol in fiona.open('poly.shp')]
points = [pt for pt in fiona.open('point.shp')]
# attributes of the polygons
for poly in polygons:
   print poly['properties'] 
OrderedDict([(u'score', 0)])
OrderedDict([(u'score', 0)])
OrderedDict([(u'score', 0)])

# attributes of the points
for pt in points:
    print i['properties']
 OrderedDict([(u'score', 1)]) 
 .... # (same for the 8 points)

Ora, possiamo usare due metodi, con o senza un indice spaziale:

1) senza

# iterate through points 
for i, pt in enumerate(points):
     point = shape(pt['geometry'])
     #iterate through polygons
     for j, poly in enumerate(polygons):
        if point.within(shape(poly['geometry'])):
             # sum of attributes values
             polygons[j]['properties']['score'] = polygons[j]['properties']['score'] + points[i]['properties']['score']

2) con un indice R-tree (puoi usare pyrtree o rtree )

# Create the R-tree index and store the features in it (bounding box)
 from rtree import index
 idx = index.Index()
 for pos, poly in enumerate(polygons):
       idx.insert(pos, shape(poly['geometry']).bounds)

#iterate through points
for i,pt in enumerate(points):
  point = shape(pt['geometry'])
  # iterate through spatial index
  for j in idx.intersection(point.coords[0]):
      if point.within(shape(multi[j]['geometry'])):
            polygons[j]['properties']['score'] = polygons[j]['properties']['score'] + points[i]['properties']['score']

Risultato con le due soluzioni:

for poly in polygons:
   print poly['properties']    
 OrderedDict([(u'score', 2)]) # 2 points in the polygon
 OrderedDict([(u'score', 1)]) # 1 point in the polygon
 OrderedDict([(u'score', 1)]) # 1 point in the polygon

Qual è la differenza ?

  • Senza l'indice, è necessario scorrere tutte le geometrie (poligoni e punti).
  • Con un indice spaziale limite ( Indice spaziale RTree ), si esegue l' iterazione solo attraverso le geometrie che hanno la possibilità di intersecarsi con la geometria corrente ("filtro" che può risparmiare una notevole quantità di calcoli e tempo ...)
  • ma un indice spaziale non è una bacchetta magica. Quando è necessario recuperare gran parte del set di dati, un indice spaziale non può offrire alcun vantaggio in termini di velocità.

Dopo:

schema = fiona.open('poly.shp').schema
with fiona.open ('output.shp', 'w', 'ESRI Shapefile', schema) as output:
    for poly in polygons:
        output.write(poly)

Per andare oltre, guarda Uso dell'indicizzazione spaziale Rtree con OGR, Shapely, Fiona


15

Inoltre - geopandas ora include facoltativamente rtreecome dipendenza, vedere il repository github

Quindi, invece di seguire tutto il (molto bello) codice sopra, potresti semplicemente fare qualcosa del tipo:

import geopandas
from geopandas.tools import sjoin
point = geopandas.GeoDataFrame.from_file('point.shp') # or geojson etc
poly = geopandas.GeoDataFrame.from_file('poly.shp')
pointInPolys = sjoin(point, poly, how='left')
pointSumByPoly = pointInPolys.groupby('PolyGroupByField')['fields', 'in', 'grouped', 'output'].agg(['sum'])

Per ottenere questa funzionalità snazzy assicurarsi di installare la libreria C libspatialindex primo

EDIT: importazioni di pacchetti corretti


Avevo l'impressione che rtreefosse facoltativo. Ciò non significa che è necessario installare rtreeoltre alla libspatialindexlibreria C?
kuanb,

è passato un po 'di tempo ma penso che quando ho fatto l'installazione di Geopandas da Github sia stata aggiunta automaticamente rtreequando l'avevo installata per la prima volta libspatialindex... hanno fatto un rilascio abbastanza importante da allora, quindi sono sicuro che le cose sono cambiate un po'
ClaytonRsh,

9

Usa Rtree come indice per eseguire i join molto più veloci, quindi Shapely per eseguire i predicati spaziali per determinare se un punto si trova effettivamente all'interno di un poligono. Se fatto correttamente, questo può essere più veloce della maggior parte degli altri GIS.

Vedi esempi qui o qui .

La seconda parte della tua domanda riguardante 'SOMMA', usa un dictoggetto per accumulare popolazioni usando un ID poligono come chiave. Tuttavia, questo tipo di cose è fatto molto più bene con PostGIS.


Grazie @Mike T ... utilizzando l'oggetto dict o PostGIS sono ottimi suggerimenti. Sono ancora un po 'confuso dove posso incorporare Rtree nel mio codice, tuttavia (codice incluso sopra).
jburrfischer,

1

Questa pagina web mostra come utilizzare una ricerca point-in-poligono di Bounding Box prima della più costosa query spaziale all'interno di Shapely.

http://rexdouglass.com/fast-spatial-joins-in-python-with-a-spatial-index/


Grazie @klewis ... hai qualche possibilità di aiutarti con la seconda parte? Per riassumere gli attributi dei punti (es. Popolazione) che ricadono nei poligoni, ho provato qualcosa di simile al codice qui sotto, ma ha generato un errore. if shape (scuola ['geometria']). entro (forma (vicinato ['geometria'])): neighbourhood ['proprietà'] ['popolazione'] + = scuole ['proprietà'] ['popolazione']
jburrfischer

Se apri il vicinato in modalità "r", potrebbe essere di sola lettura. Entrambi gli shapefile hanno popolazione di campo? Quale riga # sta generando l'errore? In bocca al lupo.
Klewis,

Grazie ancora @klewis ... Ho aggiunto il mio codice sopra e spiegato l'errore. Inoltre, ho giocato con rtree e sono ancora un po 'confuso su dove lo aggiungerei nel codice sopra. Mi dispiace essere un tale fastidio.
jburrfischer,

Prova questo, sembra che l'aggiunta di None a un int stia causando l'errore. poly_score = poly ['properties'] ['score']) point_score = point ['properties'] ['score']) if point_score: if poly_score poly ['properties'] ['score']) + = point_score altro: poly ['properties'] ['score']) = point_score
klewis
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.