Coordinates of the closest points of two geometries in Shapely
In case you have a single segment (e.g.: a line, as referring to the title) rather than a list of segments, here is what I did, and with a passing test case. Please consider that some users on this page are looking just for that from looking at the title, coming from a Google search.
Python code:
def sq_shortest_dist_to_point(self, other_point):
dx = self.b.x - self.a.x
dy = self.b.y - self.a.y
dr2 = float(dx ** 2 + dy ** 2)
lerp = ((other_point.x - self.a.x) * dx + (other_point.y - self.a.y) * dy) / dr2
if lerp < 0:
lerp = 0
elif lerp > 1:
lerp = 1
x = lerp * dx + self.a.x
y = lerp * dy + self.a.y
_dx = x - other_point.x
_dy = y - other_point.y
square_dist = _dx ** 2 + _dy ** 2
return square_dist
def shortest_dist_to_point(self, other_point):
return math.sqrt(self.sq_shortest_dist_to_point(other_point))
A test case:
def test_distance_to_other_point(self):
# Parametrize test with multiple cases:
segments_and_point_and_answer = [
[Segment(Point(1.0, 1.0), Point(1.0, 3.0)), Point(2.0, 4.0), math.sqrt(2.0)],
[Segment(Point(1.0, 1.0), Point(1.0, 3.0)), Point(2.0, 3.0), 1.0],
[Segment(Point(0.0, 0.0), Point(0.0, 3.0)), Point(1.0, 1.0), 1.0],
[Segment(Point(1.0, 1.0), Point(3.0, 3.0)), Point(2.0, 2.0), 0.0],
[Segment(Point(-1.0, -1.0), Point(3.0, 3.0)), Point(2.0, 2.0), 0.0],
[Segment(Point(1.0, 1.0), Point(1.0, 3.0)), Point(2.0, 3.0), 1.0],
[Segment(Point(1.0, 1.0), Point(1.0, 3.0)), Point(2.0, 4.0), math.sqrt(2.0)],
[Segment(Point(1.0, 1.0), Point(-3.0, -3.0)), Point(-3.0, -4.0), 1],
[Segment(Point(1.0, 1.0), Point(-3.0, -3.0)), Point(-4.0, -3.0), 1],
[Segment(Point(1.0, 1.0), Point(-3.0, -3.0)), Point(1, 2), 1],
[Segment(Point(1.0, 1.0), Point(-3.0, -3.0)), Point(2, 1), 1],
[Segment(Point(1.0, 1.0), Point(-3.0, -3.0)), Point(-3, -1), math.sqrt(2.0)],
[Segment(Point(1.0, 1.0), Point(-3.0, -3.0)), Point(-1, -3), math.sqrt(2.0)],
[Segment(Point(-1.0, -1.0), Point(3.0, 3.0)), Point(3, 1), math.sqrt(2.0)],
[Segment(Point(-1.0, -1.0), Point(3.0, 3.0)), Point(1, 3), math.sqrt(2.0)],
[Segment(Point(1.0, 1.0), Point(3.0, 3.0)), Point(3, 1), math.sqrt(2.0)],
[Segment(Point(1.0, 1.0), Point(3.0, 3.0)), Point(1, 3), math.sqrt(2.0)]
]
for i, (segment, point, answer) in enumerate(segments_and_point_and_answer):
result = segment.shortest_dist_to_point(point)
self.assertAlmostEqual(result, answer, delta=0.001, msg=str((i, segment, point, answer)))
Note: I assume this function is inside a Segment
class.
In case your line is infinite, don't limit the lerp
from 0 to 1 only, but still at least provide two distinct a
and b
points.
The GIS term you are describing is linear referencing, and Shapely has these methods.
# Length along line that is closest to the point
print(line.project(p))
# Now combine with interpolated point on line
p2 = line.interpolate(line.project(p))
print(p2) # POINT (5 7)
An alternative method is to use nearest_points
:
from shapely.ops import nearest_points
p2 = nearest_points(line, p)[0]
print(p2) # POINT (5 7)
which provides the same answer as the linear referencing technique does, but can determine the nearest pair of points from more complicated geometry inputs, like two polygons.