Vérifiez si un point se trouve dans un multipolygone avec Python

13

J'ai essayé plusieurs exemples de code utilisant des bibliothèques telles que shapefile, fiona et ogr pour tenter de vérifier si un point (x, y) se situe dans les limites d'un multipolygone créé avec ArcMap (et donc au format shapefile). Cependant, aucun des exemples ne fonctionne bien avec les multipolygones, bien qu'ils fonctionnent bien avec les fichiers de formes polygonaux réguliers. Quelques extraits que j'ai essayés sont ci-dessous:

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

Le premier exemple fonctionne correctement avec un seul polygone à la fois, mais lorsque j'entre un point dans l'une des formes de mon fichier de formes multipolygone, il renvoie "out" même s'il tombe à l'intérieur d'une des nombreuses parties.

Pour le deuxième exemple, j'obtiens une erreur "objet de type 'Geometry' n'a pas de len ()" qui je suppose est parce que le champ de géométrie ne peut pas être lu comme une liste / un tableau indexé normal.

J'ai également essayé de remplacer le point réel dans le code du polygone comme suggéré ici pour m'assurer que ce n'était pas la partie du code qui ne fonctionnait pas. Et bien que les exemples de ce lien fonctionnent correctement avec des fichiers de formes polygonales simples, je ne peux pas faire tester correctement mon multipolygone complexe.

Je ne peux donc pas penser à un autre moyen de tester si un point se trouve dans un fichier de formes multipolygone via python ... Peut-être qu'il y a d'autres bibliothèques là-bas qui me manquent?

spartmar
la source
votre deuxième exemple ressemble à une contrainte de multipolygone en polygone? Il s'agit peut-être uniquement de vérifier le point par rapport à la première partie du multipolygone. Essayez de déplacer le point vers différentes parties et voyez si la vérification réussit jamais.
obrl_soil
@obrl_soil Merci pour votre suggestion. Cependant, le deuxième exemple ne fonctionne jamais à cause du message d'erreur que j'ai décrit ci-dessus (l'objet de type 'Geometry' n'a pas de len ()) "que j'essaie MultiPolygon (géométrie) ou simplement Polygon (géométrie). J'ai essayé de nombreux points dans le premier exemple et seulement ceux dans le polygone principal. J'espère que cette clarification aide.
spartmar
Oui, je pense que vous devez remplacer polygon = Polygon(geometry)par une sorte de boucle d'essai où elle passe polygon = MultiPolygon(geometry)si cette erreur se produit.
obrl_soil
Le problème dans votre premier exemple est dans la première boucle.
xunilk

Réponses:

24

Les fichiers de formes n'ont pas de type MultiPolygon (type = Polygon), mais ils les prennent quand même en charge (tous les anneaux sont stockés dans une seule entité = liste de polygones, voir Conversion d'énormes multipolygones en polygones )

Le problème

entrez la description de l'image ici

Si j'ouvre un fichier de formes MultiPolygon, la géométrie est 'Polygon'

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

Solution 1 avec 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 interprète la fonctionnalité comme un MultiPolygon et vous pouvez appliquer la solution présentée dans Jointure spatiale plus efficace en Python sans 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)

Solution 2 avec pyshp (shapefile) et le protocole geo_interface (de type GeoJSON)

Ceci est un complément à la réponse 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)

Solution 3 avec ogr et le protocole geo_interface ( applications 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)

Solution 4 avec GeoPandas comme dans la jointure spatiale plus efficace en Python sans 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]} 

Les points 1,3,5,6 se situent dans les limites du MultiPolygon

gène
la source
Un peu de fil ici, mais comment appeler la multi = shape(polys.shapeRecords()[0].shape.__geo_interface__)solution 2? Je ne parviens pas à appeler une méthode shape () à partir de shapefile.py. J'ai même essayé shapefile.Shape(); il y a une classe pour ça mais ça ne marche pas.
pstatix
De plus, d'où obtenez-vous la within()méthode?
pstatix
1
de Shapely ( from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon)
gène
J'obtiens cette erreur en utilisant la solution 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 le
6

Le problème dans votre premier exemple est dans cette boucle:

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

Il ajoute uniquement les derniers points de fonctionnalité. J'ai essayé mon approche avec ce fichier de formes:

entrez la description de l'image ici

J'ai modifié votre code pour:

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

Le code ci-dessus a été exécuté sur la console Python de QGIS et le résultat était:

entrez la description de l'image ici

Cela fonctionne parfaitement et maintenant, vous pouvez vérifier si un point (x, y) se situe dans les limites de chaque entité.

xunilk
la source
0

Si vous essayez de vérifier un point de latitude et de longitude dans un polygone, assurez-vous que vous avez un objet point créé par ce qui suit:

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

Le point prend la longitude, puis la latitude dans l'argument. Pas la latitude d'abord. Vous pouvez appeler la polygon_object.withinfonction pour vérifier si le point se trouve dans la forme.

Ahmed
la source