简体   繁体   中英

Find coordinate of the closest point on polygon in Shapely

Say I have the following Polygon and Point:

>>> poly = Polygon([(0, 0), (2, 8), (14, 10), (6, 1)])
>>> point = Point(12, 4)

在此处输入图片说明

I can calculate the point's distance to the polygon...

>>> dist = point.distance(poly)
>>> print(dist)
2.49136439561

...but I would like to know the coordinate of the point on the polygon border where that shortest distance measures to.

My initial approach is to buffer the point by its distance to the polygon, and find the point at which that circle is tangent to the polygon:

>>> buff = point.buffer(dist) 

在此处输入图片说明 However, I'm not sure how to calculate that point. The two polygon's don't intersect so list(poly.intersection(buff)) will not give me that point.

Am I on the right track with this? Is there a more straightforward method?

While the answer of eguaio does the job, there is a more natural way to get the closest point using shapely.ops.nearest_points function:

from shapely.geometry import Point, Polygon
from shapely.ops import nearest_points

poly = Polygon([(0, 0), (2, 8), (14, 10), (6, 1)])
point = Point(12, 4)
# The points are returned in the same order as the input geometries:
p1, p2 = nearest_points(poly, point)
print(p1.wkt)
# POINT (10.13793103448276 5.655172413793103)

The result is the same as in the other answer:

from shapely.geometry import LinearRing
pol_ext = LinearRing(poly.exterior.coords)
d = pol_ext.project(point)
p = pol_ext.interpolate(d)
print(p.wkt)
# POINT (10.13793103448276 5.655172413793103)
print(p.equals(p1))
# True

Please, do not up-vote this answer, the correct answer is @Georgy 's answer below.

My answer for reference:

There is an easy way to do this relying on Shapely functions. First, you need to get the exterior ring of the polygon and project the point to the ring. It is mandatory to get the exterior as a LinearRing since polygons do not have the projection function. Opposed to intuition, this gives a distance, the distance from the first point of the ring to the point in the ring closest to the given point. Then, you just use that distance to get the point with the interpolate function. See the code below.

from shapely.geometry import Polygon, Point, LinearRing

poly = Polygon([(0, 0), (2,8), (14, 10), (6, 1)])
point = Point(12, 4)

pol_ext = LinearRing(poly.exterior.coords)
d = pol_ext.project(point)
p = pol_ext.interpolate(d)
closest_point_coords = list(p.coords)[0]

It is important to mention that this method only works if you know the point is outside the exterior of the polygon. If the point is inside one of its interior rings, you need to adapt the code for that situation.

If the polygon doesn't have interior rings, the code will work even for points inside the polygon. That is because we are in fact working with the exterior ring as a line string, and ignoring whether the line string comes from a polygon or not.

It is easy to extend this code to the general case of computing the distance of any point (inside or outside of the polygon) to the closest point in the polygon boundary. You only need to compute the closest point (and distance) from the point to all line rings: the exterior ring, and each interior ring of the polygon. Then, you just keep the minimum of those.

在此处输入图片说明

There are two cases two consider: (1) the closest point lies on an edge and (2) the closest point is a vertex. Case (2) is easy to check - just take the distance to each vertex and find the minimum. Case (1) involves a little more math but still isn't too bad. You need to do two things for case (1): (a) find where the normal from the point to the edge intersects the edge, and (b) verify that it lies within the line segment (as opposed to extending past one of the ends). If it's not on the line segment, ignore it (one of the vertices will be the closest point on that edge).

I liked the idea of intersecting the polygon poly with a circle buff centered at the point point , just as you wrote in your question. I would suggest: poly.boundary.intersection(buff.boundary)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM