Skip to content

Commit 760a9d5

Browse files
authored
Merge pull request #17803 from anntzer/contproj
Simplify projection-of-point-on-polyline in contour.py.
2 parents 4723dab + 947db3e commit 760a9d5

File tree

1 file changed

+37
-61
lines changed

1 file changed

+37
-61
lines changed

lib/matplotlib/contour.py

+37-61
Original file line numberDiff line numberDiff line change
@@ -599,31 +599,6 @@ def labels(self, inline, inline_spacing):
599599
paths[:] = additions
600600

601601

602-
def _find_closest_point_on_leg(p1, p2, p0):
603-
"""Find the closest point to p0 on line segment connecting p1 and p2."""
604-
605-
# handle degenerate case
606-
if np.all(p2 == p1):
607-
d = np.sum((p0 - p1)**2)
608-
return d, p1
609-
610-
d21 = p2 - p1
611-
d01 = p0 - p1
612-
613-
# project on to line segment to find closest point
614-
proj = np.dot(d01, d21) / np.dot(d21, d21)
615-
if proj < 0:
616-
proj = 0
617-
if proj > 1:
618-
proj = 1
619-
pc = p1 + proj * d21
620-
621-
# find squared distance
622-
d = np.sum((pc-p0)**2)
623-
624-
return d, pc
625-
626-
627602
def _is_closed_polygon(X):
628603
"""
629604
Return whether first and last object in a sequence are the same. These are
@@ -633,39 +608,40 @@ def _is_closed_polygon(X):
633608
return np.allclose(X[0], X[-1], rtol=1e-10, atol=1e-13)
634609

635610

636-
def _find_closest_point_on_path(lc, point):
611+
def _find_closest_point_on_path(xys, p):
637612
"""
638613
Parameters
639614
----------
640-
lc : coordinates of vertices
641-
point : coordinates of test point
615+
xys : (N, 2) array-like
616+
Coordinates of vertices.
617+
p : (float, float)
618+
Coordinates of point.
619+
620+
Returns
621+
-------
622+
d2min : float
623+
Minimum square distance of *p* to *xys*.
624+
proj : (float, float)
625+
Projection of *p* onto *xys*.
626+
imin : (int, int)
627+
Consecutive indices of vertices of segment in *xys* where *proj* is.
628+
Segments are considered as including their end-points; i.e if the
629+
closest point on the path is a node in *xys* with index *i*, this
630+
returns ``(i-1, i)``. For the special case where *xys* is a single
631+
point, this returns ``(0, 0)``.
642632
"""
643-
644-
# find index of closest vertex for this segment
645-
ds = np.sum((lc - point[None, :])**2, 1)
646-
imin = np.argmin(ds)
647-
648-
dmin = np.inf
649-
xcmin = None
650-
legmin = (None, None)
651-
652-
closed = _is_closed_polygon(lc)
653-
654-
# build list of legs before and after this vertex
655-
legs = []
656-
if imin > 0 or closed:
657-
legs.append(((imin-1) % len(lc), imin))
658-
if imin < len(lc) - 1 or closed:
659-
legs.append((imin, (imin+1) % len(lc)))
660-
661-
for leg in legs:
662-
d, xc = _find_closest_point_on_leg(lc[leg[0]], lc[leg[1]], point)
663-
if d < dmin:
664-
dmin = d
665-
xcmin = xc
666-
legmin = leg
667-
668-
return (dmin, xcmin, legmin)
633+
if len(xys) == 1:
634+
return (((p - xys[0]) ** 2).sum(), xys[0], (0, 0))
635+
dxys = xys[1:] - xys[:-1] # Individual segment vectors.
636+
norms = (dxys ** 2).sum(axis=1)
637+
norms[norms == 0] = 1 # For zero-length segment, replace 0/0 by 0/1.
638+
rel_projs = np.clip( # Project onto each segment in relative 0-1 coords.
639+
((p - xys[:-1]) * dxys).sum(axis=1) / norms,
640+
0, 1)[:, None]
641+
projs = xys[:-1] + rel_projs * dxys # Projs. onto each segment, in (x, y).
642+
d2s = ((projs - p) ** 2).sum(axis=1) # Squared distances.
643+
imin = np.argmin(d2s)
644+
return (d2s[imin], projs[imin], (imin, imin+1))
669645

670646

671647
docstring.interpd.update(contour_set_attributes=r"""
@@ -1341,8 +1317,8 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
13411317
``(x, y)``.
13421318
xmin, ymin : float
13431319
The point in the contour plot that is closest to ``(x, y)``.
1344-
d : float
1345-
The distance from ``(xmin, ymin)`` to ``(x, y)``.
1320+
d2 : float
1321+
The squared distance from ``(xmin, ymin)`` to ``(x, y)``.
13461322
"""
13471323

13481324
# This function uses a method that is probably quite
@@ -1356,7 +1332,7 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
13561332
if indices is None:
13571333
indices = list(range(len(self.levels)))
13581334

1359-
dmin = np.inf
1335+
d2min = np.inf
13601336
conmin = None
13611337
segmin = None
13621338
xmin = None
@@ -1375,16 +1351,16 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
13751351
if pixel:
13761352
lc = trans.transform(lc)
13771353

1378-
d, xc, leg = _find_closest_point_on_path(lc, point)
1379-
if d < dmin:
1380-
dmin = d
1354+
d2, xc, leg = _find_closest_point_on_path(lc, point)
1355+
if d2 < d2min:
1356+
d2min = d2
13811357
conmin = icon
13821358
segmin = segNum
13831359
imin = leg[1]
13841360
xmin = xc[0]
13851361
ymin = xc[1]
13861362

1387-
return (conmin, segmin, imin, xmin, ymin, dmin)
1363+
return (conmin, segmin, imin, xmin, ymin, d2min)
13881364

13891365

13901366
@docstring.dedent_interpd

0 commit comments

Comments
 (0)