Controlla se un punto rientra in un multipoligono con Python


13

Ho provato diversi esempi di codice usando librerie come shapefile, fiona e ogr per tentare di verificare se un punto (x, y) rientra nei limiti di un multipoligono creato con ArcMap (e quindi in formato shapefile). Tuttavia, nessuno degli esempi funziona bene con i multipoligoni, sebbene funzionino bene con i normali shapefile a poligono singolo. Di seguito sono riportati alcuni frammenti che ho provato:

# First example using shapefile and shapely:
from shapely.geometry import Polygon, Point, MultiPolygon
import shapefile

polygon = shapefile.Reader('shapefile.shp') 
polygon = polygon.shapes()  
shpfilePoints = []
for shape in polygon:
    shpfilePoints = shape.points 
polygon = shpfilePoints 
poly = Polygon(poly)

point = Point(x, y)
# point in polygon test
if polygon.contains(point):
    print 'inside'
else:
    print 'OUT'


# Second example using ogr and shapely:
from shapely.geometry import Polygon, Point, MultiPolygon
from osgeo import ogr, gdal

driver = ogr.GetDriverByName('ESRI Shapefile')
dataset = driver.Open("shapefile.shp", 0)

layer = dataset.GetLayer()
for index in xrange(layer.GetFeatureCount()):
    feature = layer.GetFeature(index)
    geometry = feature.GetGeometryRef()

polygon = Polygon(geometry)
print 'polygon points =', polygon  # this prints 'multipoint' + all the points fine

point = Point(x, y)
# point in polygon test
if polygon.contains(point):
    print 'inside'
else:
    print 'OUT'

Il primo esempio funziona bene con un singolo poligono alla volta, ma quando inserisco un punto all'interno di una delle forme nel mio file di forma multipoligono restituisce "out" anche se rientra in una delle molte parti.

Perché per il secondo esempio ricevo un errore "oggetto di tipo 'Geometria' non ha len ()" che presumo sia perché il campo della geometria non può essere letto come un normale elenco / array indicizzato.

Ho anche provato a sostituire il punto effettivo nel codice poligonale come suggerito qui per assicurarmi che non facesse parte del codice che non funzionava. E mentre gli esempi di quel link funzionano bene con semplici shapefile poligonali, non riesco a far testare correttamente il mio multipoligono complesso.

Quindi non riesco a pensare ad altro modo per verificare se un punto rientra in uno shapefile multipoligono tramite Python ... Forse ci sono altre librerie là fuori che mi mancano?


il tuo secondo esempio sembra che potrebbe forzare il multipolygon in poligono? Potrebbe solo controllare il punto rispetto alla prima parte del multipoligono. Prova a spostare il punto in parti diverse e vedi se il controllo ha mai avuto successo.
obrl_soil,

@obrl_soil Grazie per il tuo suggerimento. Tuttavia, il secondo esempio non funziona mai a causa del messaggio di errore che ho descritto sopra (l'oggetto del tipo 'Geometry' non ha len ()) "se provo MultiPolygon (geometria) o semplicemente Polygon (geometria). Ho provato molti punti nel primo esempio e solo quelli all'interno del poligono principale. Spero che questo chiarimento sia di aiuto
spartmar,

Sì, penso che devi sostituirlo polygon = Polygon(geometry)con una sorta di ciclo di prova in cui passa polygon = MultiPolygon(geometry)se si verifica quell'errore.
obrl_soil,

Il problema nel tuo primo esempio è nel primo ciclo.
xunilk,

Risposte:


24

Gli Shapefile non hanno alcun tipo di multipoligono (tipo = poligono), ma li supportano comunque (tutti gli anelli sono memorizzati in una caratteristica = elenco di poligoni, guarda Conversione di un enorme multipoligono in poligoni )

Il problema

inserisci qui la descrizione dell'immagine

Se apro un file di forma MultiPolygon, la geometria è "Poligono"

multipolys = fiona.open("multipol.shp")
multipolys.schema
{'geometry': 'Polygon', 'properties': OrderedDict([(u'id', 'int:10')])}
len(multipolys)
1

Soluzione 1 con Fiona

import fiona
from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon
multipol = fiona.open("multipol.shp")
multi= multipol.next() # only one feature in the shapefile
print multi
{'geometry': {'type': 'MultiPolygon', 'coordinates': [[[(-0.5275288092189501, 0.5569782330345711), (-0.11779769526248396, 0.29065300896286816), (-0.25608194622279135, 0.01920614596670933), (-0.709346991037132, -0.08834827144686286), (-0.8629961587708066, 0.18309859154929575), (-0.734955185659411, 0.39820742637644047), (-0.5275288092189501, 0.5569782330345711)]], [[(0.19974391805377723, 0.060179257362355965), (0.5480153649167734, 0.1293213828425096), (0.729833546734955, 0.03969270166453265), (0.8143405889884763, -0.13956466069142115), (0.701664532650448, -0.38540332906530095), (0.4763124199743918, -0.5006402048655569), (0.26888604353393086, -0.4238156209987196), (0.18950064020486557, -0.2291933418693981), (0.19974391805377723, 0.060179257362355965)]], [[(-0.3764404609475033, -0.295774647887324), (-0.11523687580025621, -0.3597951344430217), (-0.033290653008962945, -0.5800256081946222), (-0.11523687580025621, -0.7413572343149808), (-0.3072983354673495, -0.8591549295774648), (-0.58898847631242, -0.6927016645326505), (-0.6555697823303457, -0.4750320102432779), (-0.3764404609475033, -0.295774647887324)]]]}, 'type': 'Feature', 'id': '0', 'properties': OrderedDict([(u'id', 1)])}

Fiona interpreta la funzione come un multipoligono ed è possibile applicare la soluzione presentata in Join spaziale più efficiente in Python senza QGIS, ArcGIS, PostGIS, ecc. (1)

points= ([pt for pt  in fiona.open("points.shp")])
for i, pt in enumerate(points):
    point = shape(pt['geometry'])
    if point.within(shape(multi['geometry'])):
         print i, shape(points[i]['geometry'])
1 POINT (-0.58898847631242 0.17797695262484)
3 POINT (0.4993597951344431 -0.06017925736235585)
5 POINT (-0.3764404609475033 -0.4750320102432779)
6 POINT (-0.3098591549295775 -0.6312419974391805)

Soluzione 2 con pyshp (shapefile) e il protocollo geo_interface (come GeoJSON)

Questo è un supplemento alla risposta di xulnik.

import shapefile
pts = shapefile.Reader("points.shp")
polys = shapefile.Reader("multipol.shp")
points = [pt.shape.__geo_interface__ for pt in pts.shapeRecords()]
multi = shape(polys.shapeRecords()[0].shape.__geo_interface__) # 1 polygon
print multi
MULTIPOLYGON (((-0.5275288092189501 0.5569782330345711, -0.117797695262484 0.2906530089628682, -0.2560819462227913 0.01920614596670933, -0.7093469910371319 -0.08834827144686286, -0.8629961587708066 0.1830985915492958, -0.734955185659411 0.3982074263764405, -0.5275288092189501 0.5569782330345711)), ((0.1997439180537772 0.06017925736235596, 0.5480153649167734 0.1293213828425096, 0.729833546734955 0.03969270166453265, 0.8143405889884763 -0.1395646606914211, 0.701664532650448 -0.3854033290653009, 0.4763124199743918 -0.5006402048655569, 0.2688860435339309 -0.4238156209987196, 0.1895006402048656 -0.2291933418693981, 0.1997439180537772 0.06017925736235596)), ((-0.3764404609475033 -0.295774647887324, -0.1152368758002562 -0.3597951344430217, -0.03329065300896294 -0.5800256081946222, -0.1152368758002562 -0.7413572343149808, -0.3072983354673495 -0.8591549295774648, -0.58898847631242 -0.6927016645326505, -0.6555697823303457 -0.4750320102432779, -0.3764404609475033 -0.295774647887324)))
for i, pt in enumerate(points):
    point = shape(pt)
    if point.within(multi): 
        print i, shape(points[i])
1 POINT (-0.58898847631242 0.17797695262484)
3 POINT (0.4993597951344431 -0.06017925736235585)
5 POINT (-0.3764404609475033 -0.4750320102432779)
6 POINT (-0.3098591549295775 -0.6312419974391805)

Soluzione 3 con ogr e il protocollo geo_interface ( applicazioni Python Geo_interface )

from osgeo import ogr
import json
def records(file):  
    # generator 
    reader = ogr.Open(file)
    layer = reader.GetLayer(0)
    for i in range(layer.GetFeatureCount()):
        feature = layer.GetFeature(i)
        yield json.loads(feature.ExportToJson())

points  = [pt for pt in records("point_multi_contains.shp")]
multipol = records("multipol.shp")
multi = multipol.next() # 1 feature
for i, pt in enumerate(points):
     point = shape(pt['geometry'])
     if point.within(shape(multi['geometry'])):
          print i, shape(points[i]['geometry'])

1 POINT (-0.58898847631242 0.17797695262484)
3 POINT (0.499359795134443 -0.060179257362356)
5 POINT (-0.376440460947503 -0.475032010243278)
6 POINT (-0.309859154929577 -0.631241997439181)

Soluzione 4 con GeoPandas come in Unioni spaziali più efficienti in Python senza QGIS, ArcGIS, PostGIS, ecc. (2)

import geopandas
point = geopandas.GeoDataFrame.from_file('points.shp') 
poly  = geopandas.GeoDataFrame.from_file('multipol.shp')
from geopandas.tools import sjoin
pointInPolys = sjoin(point, poly, how='left')
grouped = pointInPolys.groupby('index_right')
list(grouped)
[(0.0,      geometry                               id_left  index_right id_right  

1  POINT (-0.58898847631242 0.17797695262484)       None      0.0        1.0 
3  POINT (0.4993597951344431 -0.06017925736235585)  None      0.0        1.0
5  POINT (-0.3764404609475033 -0.4750320102432779)  None      0.0        1.0 
6  POINT (-0.3098591549295775 -0.6312419974391805)  None      0.0        1.0 ]
print grouped.groups
{0.0: [1, 3, 5, 6]} 

I punti 1,3,5,6 rientrano nei limiti del multipoligono


Discussione leggermente vecchia qui, ma come si chiama multi = shape(polys.shapeRecords()[0].shape.__geo_interface__)nella soluzione 2? Non riesco a ottenere una chiamata a un metodo shape () da shapefile.py. Ci ho persino provato shapefile.Shape(); c'è una classe per esso ma non funziona.
pstatix,

Inoltre, da dove prendi il within()metodo?
pstatix,

1
da Shapely ( from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon)
gene

Ottengo questo errore usando la Soluzione 4:File "C:\WinPython\python-3.6.5.amd64\lib\site-packages\geopandas\tools\sjoin.py", line 43, in sjoin if left_df.crs != right_df.crs: AttributeError: 'MultiPolygon' object has no attribute 'crs'
Aaron Bramson

6

Il problema nel tuo primo esempio è in questo ciclo:

...
shpfilePoints = []
for shape in polygon:
    shpfilePoints = shape.points
...

Aggiunge solo gli ultimi punti funzione. Ho provato il mio approccio con questo shapefile:

inserisci qui la descrizione dell'immagine

Ho modificato il tuo codice in:

from shapely.geometry import Polygon, Point, MultiPolygon
import shapefile 

path = '/home/zeito/pyqgis_data/polygon8.shp'

polygon = shapefile.Reader(path) 

polygon = polygon.shapes() 

shpfilePoints = [ shape.points for shape in polygon ]

print shpfilePoints

polygons = shpfilePoints

for polygon in polygons:
    poly = Polygon(polygon)
    print poly

Il codice sopra è stato eseguito nella console Python di QGIS e il risultato è stato:

inserisci qui la descrizione dell'immagine

Funziona perfettamente e ora puoi verificare se un punto (x, y) rientra nei limiti di ciascuna funzione.


0

Se stai cercando di controllare un punto di latitudine e longitudine all'interno di un poligono, assicurati di avere un oggetto punto creato da quanto segue:

from shapely.geometry.point import Point
Point(LONGITUDE, LATITUDE)
..
poly.within(point) # Returns true if the point within the 

Il punto richiede longitudine, quindi latitudine nell'argomento. Prima non la latitudine. È possibile chiamare la polygon_object.withinfunzione per verificare se il punto è all'interno della forma.

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.