不幸的是,Shapely 不提供立即从MultiPolygon 对象中提取所有点的功能。相反,您必须首先使用iterate over individual polygons of a MultiPolygon,然后使用extract individual points of each Polygon。
人们可以想出不同的方法来解决这个问题。例如,如果您知道所有多边形都没有孔,您可以简单地执行以下操作:
points = []
for polygon in multipolygon:
points.extend(polygon.exterior.coords[:-1])
注意[:-1],它可以防止复制第一个顶点。如果您想要更简洁的语法并且不关心每个多边形有一个重复点,则可以将其删除。
这也可以使用带有两个循环的列表理解写在一行中:
points = [point for polygon in multipolygon for point in polygon.exterior.coords[:-1]]
或在itertools.chain.from_iterable的帮助下:
from itertools import chain
points = list(chain.from_iterable(polygon.exterior.coords[:-1] for polygon in multipolygon))
一般来说,当多边形可以包含孔洞时,例如,我们可以编写以下函数来从内环中提取坐标:
def to_coords(multipolygon):
for polygon in multipolygon:
yield from polygon.exterior.coords[:-1]
yield from chain.from_iterable(interior.coords[:-1] for interior in polygon.interiors)
使用示例:
mp = MultiPolygon([Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(2, 0), (3, 0), (3, 1), (2, 1)],
holes=[[(2.25, 0.25), (2.75, 0.25), (2.75, 0.75), (2.25, 0.75)]])])
points = list(to_coords(mp))
# [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0),
# (2.0, 0.0), (3.0, 0.0), (3.0, 1.0), (2.0, 1.0),
# (2.25, 0.25), (2.75, 0.25), (2.75, 0.75), (2.25, 0.75)]
我们可以更进一步,将其推广到任何输入几何体(Python ≥3.7):
from functools import singledispatch
from itertools import chain
from typing import (List,
Tuple,
TypeVar)
from shapely.geometry import (GeometryCollection,
LinearRing,
LineString,
Point,
Polygon)
from shapely.geometry.base import (BaseGeometry,
BaseMultipartGeometry)
Geometry = TypeVar('Geometry', bound=BaseGeometry)
@singledispatch
def to_coords(geometry: Geometry) -> List[Tuple[float, float]]:
"""Returns a list of unique vertices of a given geometry object."""
raise NotImplementedError(f"Unsupported Geometry {type(geometry)}")
@to_coords.register
def _(geometry: Point):
return [(geometry.x, geometry.y)]
@to_coords.register
def _(geometry: LineString):
return list(geometry.coords)
@to_coords.register
def _(geometry: LinearRing):
return list(geometry.coords[:-1])
@to_coords.register
def _(geometry: BaseMultipartGeometry):
return list(set(chain.from_iterable(map(to_coords, geometry))))
@to_coords.register
def _(geometry: Polygon):
return to_coords(GeometryCollection([geometry.exterior, *geometry.interiors]))
使用示例:
from shapely.geometry import (MultiLineString,
MultiPoint,
MultiPolygon)
geometry_objects = [Point(0, 0),
LineString([(0, 0), (1, 1)]),
LinearRing([(0, 0), (1, 0), (1, 1)]),
Polygon([(0, 0), (1, 0), (1, 1), (0, 1)],
holes=[[(0.25, 0.25), (0.75, 0.25), (0.75, 0.75), (0.25, 0.75)]]),
MultiPoint([(0, 0), (1, 1)]),
MultiLineString([LineString([(0, 0), (1, 1)]), LineString([(2, 0), (3, 1)])]),
MultiPolygon([Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
Polygon([(2, 0), (3, 0), (3, 1), (2, 1)],
holes=[[(2.25, 0.25), (2.75, 0.25), (2.75, 0.75), (2.25, 0.75)]])]),
GeometryCollection([Point(0, 0), LineString([(0, 0), (1, 1)])])]
for geometry in geometry_objects:
print(f"For {geometry.wkt}\nwe got:\n"
f"{to_coords(geometry)}\n")
输出:
For POINT (0 0)
we got:
[(0.0, 0.0)]
For LINESTRING (0 0, 1 1)
we got:
[(0.0, 0.0), (1.0, 1.0)]
For LINEARRING (0 0, 1 0, 1 1, 0 0)
we got:
[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]
For POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0), (0.25 0.25, 0.75 0.25, 0.75 0.75, 0.25 0.75, 0.25 0.25))
we got:
[(0.0, 1.0), (0.0, 0.0), (0.25, 0.25), (0.75, 0.25), (0.75, 0.75), (0.25, 0.75), (1.0, 0.0), (1.0, 1.0)]
For MULTIPOINT (0 0, 1 1)
we got:
[(0.0, 0.0), (1.0, 1.0)]
For MULTILINESTRING ((0 0, 1 1), (2 0, 3 1))
we got:
[(2.0, 0.0), (0.0, 0.0), (3.0, 1.0), (1.0, 1.0)]
For MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 0, 3 0, 3 1, 2 1, 2 0), (2.25 0.25, 2.75 0.25, 2.75 0.75, 2.25 0.75, 2.25 0.25)))
we got:
[(0.0, 1.0), (0.0, 0.0), (3.0, 0.0), (3.0, 1.0), (2.0, 1.0), (2.0, 0.0), (2.25, 0.25), (2.75, 0.25), (2.75, 0.75), (2.25, 0.75), (1.0, 0.0), (1.0, 1.0)]
For GEOMETRYCOLLECTION (POINT (0 0), LINESTRING (0 0, 1 1))
we got:
[(0.0, 0.0), (1.0, 1.0)]