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.