Compruebe si un punto cae dentro de un multipolígono con Python

13

He intentado varios ejemplos de código utilizando bibliotecas como shapefile, fiona y ogr para intentar verificar si un punto (x, y) se encuentra dentro de los límites de un multipolígono creado con ArcMap (y, por lo tanto, en formato shapefile). Sin embargo, ninguno de los ejemplos funciona bien con multipolígonos, aunque funcionan bien con archivos de forma de polígonos individuales regulares. Algunos fragmentos que probé están a continuación:

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

El primer ejemplo funciona bien con un solo polígono a la vez, pero cuando ingreso un punto dentro de una de las formas en mi archivo de formas multipolígono, regresa "fuera" a pesar de que cae dentro de una de las muchas partes.

Para el segundo ejemplo, recibo un error "el objeto de tipo 'Geometry' no tiene len ()", lo que supongo es porque el campo de geometría no puede leerse como una lista / matriz indexada normal.

También intenté reemplazar el punto real en el código de polígono como se sugirió aquí para asegurarme de que no era esa parte del código que no funcionaba. Y aunque los ejemplos de ese enlace funcionan bien con archivos de forma de polígonos simples, no puedo hacer que mi multipolígono complejo se pruebe correctamente.

Así que no puedo pensar en otra forma de probar si un punto cae dentro de un archivo de forma multipolígono a través de Python ... ¿Tal vez hay otras bibliotecas que me faltan?

spartmar
fuente
¿Parece que su segundo ejemplo podría obligar a multipolígono a polígono? Es posible que solo esté verificando el punto contra la primera parte del multipolígono. Intente mover el punto a diferentes partes y vea si la verificación alguna vez tiene éxito.
obrl_soil
@obrl_soil Gracias por su sugerencia. Sin embargo, el segundo ejemplo nunca funciona debido al mensaje de error que describí anteriormente (el objeto de tipo 'Geometry' no tiene len ()) "si intento MultiPolygon (geometría) o simplemente Polygon (geometría). He intentado muchos puntos en el . primer ejemplo, y sólo aquellos dentro de la obra principal polígono esperanza esta aclaración ayuda.
spartmar
Sí, creo que debe reemplazarse polygon = Polygon(geometry)con algún tipo de bucle de prueba donde cambia polygon = MultiPolygon(geometry)si se produce ese error.
obrl_soil
El problema en su primer ejemplo está en el primer bucle.
xunilk

Respuestas:

24

Los archivos de forma no tienen tipo MultiPolygon (type = Polygon), pero los admiten de todos modos (todos los anillos se almacenan en una característica = lista de polígonos, mira Convertir gran polígono en polígonos )

El problema

ingrese la descripción de la imagen aquí

Si abro un archivo de forma MultiPolygon, la geometría es 'Polygon'

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

Solución 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 función como un MultiPolygon y puede aplicar la solución presentada en la unión espacial más eficiente en Python sin QGIS, ArcGIS, PostGIS, etc. (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)

Solución 2 con pyshp (shapefile) y el protocolo geo_interface (como GeoJSON)

Este es un suplemento a la respuesta de 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)

Solución 3 con ogr y el protocolo geo_interface ( aplicaciones 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)

Solución 4 con GeoPandas como en combinación espacial más eficiente en Python sin QGIS, ArcGIS, PostGIS, etc. (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]} 

Los puntos 1,3,5,6 caen dentro de los límites del MultiPolygon

gene
fuente
Hilo un poco viejo aquí, pero ¿cómo se llama multi = shape(polys.shapeRecords()[0].shape.__geo_interface__)en la Solución 2? No puedo recibir una llamada a un método shape () desde shapefile.py. Incluso lo he intentado shapefile.Shape(); hay una clase para eso pero no funciona.
pstatix
Además, ¿de dónde obtienes el within()método?
pstatix
1
from Shapely ( from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon)
gen
Recibo este error usando la Solución 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

El problema en su primer ejemplo está en este bucle:

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

Solo agrega los últimos puntos de características. Probé mi enfoque con este shapefile:

ingrese la descripción de la imagen aquí

Modifiqué tu código para:

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

El código anterior se ejecutó en la Consola Python de QGIS y el resultado fue:

ingrese la descripción de la imagen aquí

Funciona perfectamente y ahora, puede verificar si un punto (x, y) cae dentro de los límites de cada entidad.

xunilk
fuente
0

Si está tratando de verificar un punto de latitud y longitud dentro de un polígono, asegúrese de tener un objeto de punto creado de la siguiente manera:

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

El punto toma longitud, luego latitud en el argumento. No la latitud primero. Puede llamar a la polygon_object.withinfunción para verificar si el punto está dentro de la forma.

Ahmed
fuente