Проверьте, попадает ли точка в мультиполигон с помощью Python

13

Я попробовал несколько примеров кода с использованием библиотек, таких как shapefile, fiona и ogr, чтобы попытаться проверить, попадает ли точка (x, y) в границы мультиполигона, созданного с помощью ArcMap (и, следовательно, в формате shapefile). Однако ни один из примеров не работает хорошо с мультиполигонами, хотя они хорошо работают с обычными, однополигональными шейп-файлами. Ниже приведены некоторые фрагменты, которые я попробовал:

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

Первый пример отлично работает с одним полигоном за раз, но когда я ввожу точку в пределах одной из фигур в моем многогранном шейп-файле, он возвращает «out», даже если он попадает в одну из множества частей.

Во втором примере я получаю сообщение об ошибке «объект типа« Геометрия »не имеет len ()», который, как я полагаю, связан с тем, что поле геометрии не может быть прочитано как обычный индексированный список / массив.

Я также попытался заменить фактическую точку в коде многоугольника, как предложено здесь, чтобы убедиться, что это не та часть кода, которая не работает. И хотя примеры этой ссылки хорошо работают с простыми шейп-файлами полигонов, я не могу заставить мой сложный мультиполигон правильно тестировать.

Поэтому я не могу придумать какой-либо другой способ проверить, попадает ли точка в шейп-файл многоугольника через python ... Может быть, есть другие библиотеки, которые мне не хватает?

spartmar
источник
Ваш второй пример выглядит так, как если бы он приводил мультиполигон к полигону? Это может быть только проверка точки относительно первой части мультиполигона. Попробуйте переместить точку в разные части и посмотреть, удастся ли когда-нибудь проверить.
obrl_soil
@obrl_soil Спасибо за ваше предложение. Тем не менее, второй пример никогда не работает из-за сообщения об ошибке, которое я описал выше (объект типа «Геометрия» не имеет len ()) «пытаюсь ли я использовать MultiPolygon (геометрия) или просто Polygon (геометрия). Я пробовал много точек в первый пример и только те, которые находятся в основной работе многоугольника. Надеюсь, это разъяснение поможет.
spartmar
Да, я думаю, вам нужно заменить polygon = Polygon(geometry)какой-то цикл try, где он переключается, polygon = MultiPolygon(geometry)если возникает эта ошибка.
obrl_soil
Проблема в вашем первом примере заключается в первом цикле.
xunilk

Ответы:

24

Шейп-файлы не имеют типа MultiPolygon (type = Polygon), но они все равно поддерживают их (все кольца хранятся в одном элементе = список полигонов, посмотрите на Преобразование огромного мультиполигона в полигоны )

Проблема

введите описание изображения здесь

Если я открою шейп-файл MultiPolygon, геометрия будет «Полигон»

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

Решение 1 с Фионой

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 интерпретирует эту функцию как MultiPolygon, и вы можете применить решение, представленное в разделе « Более эффективное пространственное соединение» в Python, без QGIS, ArcGIS, PostGIS и т. Д. (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)

Решение 2 с pyshp (shapefile) и протоколом geo_interface (как GeoJSON)

Это дополнение к ответу 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)

Решение 3 с ogr и протоколом geo_interface ( приложения 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)

Решение 4 с GeoPandas, как в более эффективном пространственном соединении в Python без QGIS, ArcGIS, PostGIS и т. Д. (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]} 

Точки 1,3,5,6 попадают в границы мультиполигона

ген
источник
Немного старая тема здесь, но как вы называете multi = shape(polys.shapeRecords()[0].shape.__geo_interface__)Решение 2? Я не могу получить вызов метода shape () из shapefile.py. Я даже пытался shapefile.Shape(); есть класс для него, но он не работает.
pstatix
Кроме того, откуда вы берете within()метод?
pstatix
1
из Shapely ( from shapely.geometry import shape,mapping, Point, Polygon, MultiPolygon)
ген
Я получаю эту ошибку, используя Решение 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'
Аарон Брамсон
6

Проблема в вашем первом примере заключается в следующем цикле:

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

Добавляет только последние характерные точки. Я опробовал свой подход с этим шейп-файлом:

введите описание изображения здесь

Я изменил ваш код:

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

Вышеприведенный код был запущен на консоли Python QGIS, и в результате было получено:

введите описание изображения здесь

Он работает отлично, и теперь вы можете проверить, попадает ли точка (x, y) в границы каждого объекта.

xunilk
источник
0

Если вы пытаетесь проверить широту, долготу точки внутри многоугольника, убедитесь, что у вас есть точечный объект, созданный с помощью следующего:

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

Точка принимает долготу, затем широту в аргументе. Сначала не широта. Вы можете вызвать polygon_object.withinфункцию, чтобы проверить, находится ли точка внутри фигуры.

Ahmed
источник