Verifique se um ponto se enquadra em um multipolígono com Python

13

Eu tentei vários exemplos de código usando bibliotecas como shapefile, fiona e ogr para tentar verificar se um ponto (x, y) está dentro dos limites de um multipolígono criado com o ArcMap (e, portanto, no formato shapefile). No entanto, nenhum dos exemplos funciona bem com multipolígonos, embora funcione bem com arquivos de forma regulares, de polígono único. Alguns trechos que tentei estão abaixo:

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

O primeiro exemplo funciona bem com um único polígono de cada vez, mas quando insiro um ponto em uma das formas no meu arquivo de forma multipolígono, ele retorna "fora", mesmo que caia dentro de uma das muitas partes.

Para o segundo exemplo, recebo o erro "objeto do tipo 'Geometry' has no len ()", que eu assumo é porque o campo geometry não pode ser lido como uma lista / matriz normal indexada.

Além disso, tentei substituir o ponto real no código de polígono, conforme sugerido aqui, para garantir que não fosse a parte do código que não funcionasse. E enquanto os exemplos desse link funcionam bem com arquivos de forma de polígono simples, não consigo fazer com que meu multipolígono complexo seja testado corretamente.

Portanto, não consigo pensar em outra maneira de testar se um ponto se enquadra em um shapefile de vários polígonos via python ... Talvez haja outras bibliotecas por aí que estejam faltando?

spartmar
fonte
seu segundo exemplo parece estar coagindo multipolígono a polígono? Pode ser apenas verificar o ponto em relação à primeira parte do multipolígono. Tente mover o ponto para partes diferentes e verifique se a verificação é bem-sucedida.
obrl_soil
@obrl_soil Obrigado pela sua sugestão. No entanto, o segundo exemplo nunca funciona devido à mensagem de erro que descrevi acima (o objeto do tipo 'Geometry' não possui len ()) "quer tente MultiPolygon (geometry) ou simplesmente Polygon (geometry). Tentei muitos pontos no . primeiro exemplo e somente aqueles dentro da principal obra polígono esperança este esclarecimento ajuda.
spartmar
Sim, acho que você precisa substituir polygon = Polygon(geometry)por algum tipo de loop de tentativa para onde ele muda, polygon = MultiPolygon(geometry)se esse erro ocorrer.
obrl_soil 27/08/16
O problema no seu primeiro exemplo está no primeiro loop.
Xunilk 27/08/16

Respostas:

24

Os arquivos de forma não têm o tipo MultiPolygon (type = Polygon), mas os suportam de qualquer maneira (todos os anéis são armazenados em um recurso = lista de polígonos, veja Convertendo multipolígonos enormes em polígonos )

O problema

insira a descrição da imagem aqui

Se eu abrir um shapefile MultiPolygon, a geometria será 'Polygon'

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

Solução 1 com 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 o recurso como um MultiPolygon e você pode aplicar a solução apresentada na junção espacial mais eficiente em Python sem 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)

Solução 2 com pyshp (shapefile) e o protocolo geo_interface (como GeoJSON)

Este é um complemento para a resposta do 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)

Solução 3 com ogr e o protocolo geo_interface ( aplicativos 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)

Solução 4 com GeoPandas como na junção espacial mais eficiente em Python sem 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]} 

Os pontos 1,3,5,6 estão dentro dos limites do MultiPolygon

gene
fonte
Segmento ligeiramente antigo aqui, mas como você chama a multi = shape(polys.shapeRecords()[0].shape.__geo_interface__)Solução 2? Não consigo receber uma chamada para um método shape () shapefile.py. Eu até tentei shapefile.Shape(); existe uma classe para isso, mas não funciona.
Pstatix
Além disso, de onde você obtém o within()método?
Pstatix ​​31/08/19
1
de Shapely ( from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon)
gene
Eu recebo este erro usando a Solução 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

O problema no seu primeiro exemplo está neste loop:

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

Acrescenta apenas os últimos pontos de recurso. Eu tentei minha abordagem com este shapefile:

insira a descrição da imagem aqui

Modifiquei seu 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

O código acima foi executado no Python Console do QGIS e o resultado foi:

insira a descrição da imagem aqui

Funciona perfeitamente e agora você pode verificar se um ponto (x, y) se enquadra nos limites de cada recurso.

xunilk
fonte
0

Se você estiver tentando verificar um ponto de latitude e longitude dentro de um polígono, verifique se o objeto de ponto foi criado pelo seguinte:

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

Ponto leva longitude, depois latitude no argumento. Não é a latitude primeiro. Você pode chamar a polygon_object.withinfunção para verificar se o ponto está dentro da forma.

Ahmed
fonte