Bardziej wydajne łączenie przestrzenne w Pythonie bez QGIS, ArcGIS, PostGIS itp


31

Próbuję wykonać sprzężenie przestrzenne podobnie jak tutaj: Czy istnieje opcja Pythona do „łączenia atrybutów według lokalizacji”? . Jednak takie podejście wydaje się naprawdę nieefektywne / powolne. Nawet uruchomienie tego ze skromnymi 250 punktami zajmuje prawie 2 minuty i kończy się niepowodzeniem na plikach kształtu z> 1000 punktów. Czy istnieje lepsze podejście? Chciałbym to zrobić całkowicie w Pythonie bez użycia ArcGIS, QGIS itp.

Chciałbym również wiedzieć, czy możliwe jest SUMOWANIE atrybutów (tj. Populacji) wszystkich punktów, które mieszczą się w wielokącie i łączenie tej ilości z kształtem wielokąta.

Oto kod, który próbuję przekonwertować. W linii 9 pojawia się błąd:

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

który mówi:

TypeError: nieobsługiwane typy operandów dla + =: „NoneType” i „float”.

Jeśli zamieniam „+ =” na „=”, działa dobrze, ale to nie sumuje pól. Próbowałem też tworzyć je jako liczby całkowite, ale to też się nie udaje.

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

Myślę, że powinieneś edytować swoje drugie pytanie stąd, aby pozostało ono skoncentrowane na tym, co uważam za najważniejsze. Drugi można badać / zadawać osobno.
PolyGeo

Odpowiedzi:


37

Fiona zwraca słowniki w języku Python i nie można ich używać poly['properties']['score']) += point['properties']['score'])ze słownikiem.

Przykład sumowania atrybutów z wykorzystaniem odniesień podanych przez Mike'a T:

wprowadź opis zdjęcia tutaj

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

Teraz możemy użyć dwóch metod, z indeksem przestrzennym lub bez:

1) bez

# 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) z indeksem R-drzewa (możesz użyć Pyrtree lub 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']

Wynik z dwoma rozwiązaniami:

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

Jaka jest różnica ?

  • Bez indeksu musisz iterować przez wszystkie geometrie (wielokąty i punkty).
  • Z ograniczającym indeksem przestrzennym ( Spatial Index RTree ), iterujesz tylko przez geometrie, które mają szansę przeciąć się z twoją bieżącą geometrią („filtr”, który może zaoszczędzić znaczną ilość obliczeń i czasu ...)
  • ale Indeks przestrzenny nie jest magiczną różdżką. Gdy trzeba pobrać bardzo dużą część zestawu danych, Indeks przestrzenny nie może przynieść żadnej korzyści w zakresie prędkości.

Po:

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

Aby pójść dalej, spójrz na Korzystanie z indeksowania przestrzennego Rtree z OGR, Shapely, Fiona


15

Dodatkowo - geopandas teraz opcjonalnie obejmuje rtreejako zależność, zobacz repozytorium github

Zamiast więc postępować zgodnie z powyższym (bardzo ładnym) kodem, możesz po prostu zrobić coś takiego:

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

Aby uzyskać tę funkcjonalność snazzy należy zainstalować C-biblioteka libspatialindex pierwszy

EDYCJA: poprawiono import pakietów


Miałem wrażenie, że rtreebył opcjonalny. Czy to nie znaczy, że musisz zainstalować rtreetak dobrze, jak libspatialindexbibliotekę C.
kuanb

minęło trochę czasu, ale myślę, że kiedy to zrobiłem, instalowanie geopand z github dodawało się automatycznie, rtreekiedy pierwszy raz zainstalowałem libspatialindex... zrobili dość duże wydanie, więc jestem pewien, że wszystko się nieco zmieniło
claytonrsh

9

Użyj Rtree jako indeksu, aby wykonać znacznie szybsze połączenia, a następnie Shapely, aby wykonać predykaty przestrzenne, aby ustalić, czy punkt znajduje się w obrębie wielokąta. Prawidłowo wykonane może być szybsze niż większość innych systemów GIS.

Zobacz przykłady tutaj lub tutaj .

W drugiej części pytania dotyczącego „SUM” użyj dictobiektu do gromadzenia populacji, używając klucza wielokąta jako klucza. Chociaż tego typu rzeczy są znacznie ładniejsze w PostGIS.


Dziękuję @ Mike T ... użycie obiektu dict lub PostGIS to świetne sugestie. Nadal jestem trochę zdezorientowany, gdzie mogę włączyć Rtree do mojego kodu (dołączony kod powyżej).
jburrfischer

1

Ta strona pokazuje, jak korzystać z wyszukiwania punkt-w-wielokącie Bounding Box przed droższym zapytaniem przestrzennym Shapely.

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


Dzięki @klewis ... jest jakaś szansa, że ​​możesz pomóc w drugiej części? Aby zsumować atrybuty punktowe (np. Populację), które mieszczą się w wielokątach, próbowałem czegoś podobnego do kodu poniżej, ale spowodowało to błąd. if shape (szkoła [„geometria”]). wewnątrz (kształt (sąsiedztwo [„geometria”])): sąsiedztwo [„właściwości”] [„populacja”] + = szkoły [„właściwości”] [„populacja”]
jburrfischer

Jeśli otworzysz sąsiedztwo w trybie „r”, może to być tylko do odczytu. Czy oba kształty mają populację polową? Która linia # generuje błąd? Powodzenia.
klewis

Jeszcze raz dziękuję @klewis ... Dodałem swój kod powyżej i wyjaśniłem błąd. Poza tym bawiłem się z rtree i nadal jestem trochę zdezorientowany, gdybym dodał to do powyższego kodu. Przepraszam, że przeszkadzam.
jburrfischer

Spróbuj tego, wydaje się, że dodanie None do int powoduje błąd. poly_score = poly ['właściwości'] ['score']) point_score = punkt ['właściwości'] ['score']) jeśli point_score: if poly_score poly ['właściwości'] ['score']) + = point_score else: poli [„właściwości”] [„wynik”]) = point_score
klewis
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.