Getting point clicked on in a polyline

3,092 views
Skip to first unread message

Yermo Lamers

unread,
Apr 24, 2013, 6:24:07 PM4/24/13
to leafl...@googlegroups.com
I'm crossposting from stackoverflow. 

I'm using the Leaflet mapping API. I would like to determine which point in a polyline has been clicked on. The polyline is one that I have rendered using the Directions services from MapQuest Open.

The relevant code is:

for ( var i = 0; i < latlngs.length; i++ ) {

// the equals method uses a small fudge factor to decide if the two // points are the same.

if ( latlng.equals( latlngs[ i ] )) { return i; } }

.equals() is supposed to compare points with a fudge factor. When I click on the polyline I get an event with lat/lngs like:

'38.83966582989183','-77.0083075761795'

My lat/lngs in the polyline are of less precision as in:

'38.841289','-77.008842'

So as I loop through looking for a match equals() never returns true. Clearly, the click event is firing, so leaflet knows I've clicked the polyline, but there doesn't seem to be a clean way of getting the actual offset of the latlng that was clicked on.

Per this ticket I've tried adjusting the precision to no avail. 

Going into the list of lat/lngs I can clearly see that the select lat/lng is between two points in the polyline.

Reducing the precision returned from the mouse click doesn't seem to work either.

Ideally, I'd like to have some method to get the offset into the latlng set of the polyline closest to where the user clicked. I can do a rough job by hand but would, ideally, like a clean solution.

Any help/pointers would be greatly appreciated.

Yermo Lamers

unread,
Apr 24, 2013, 6:35:35 PM4/24/13
to leafl...@googlegroups.com
Setting L.LatLng.MAX_MARGIN to 1.0E-3 seems to work but it's a very coarse margin that I suspect will fail in some circumstances.

Arnie Shore

unread,
Apr 24, 2013, 6:35:52 PM4/24/13
to leafl...@googlegroups.com
From a lurker: This function might help =>
http://codemonger.tumblr.com/post/10717903865/distance-from-a-point-to-a-line-in-javascript
.

AS

On 4/24/13, Yermo Lamers <yermo....@gmail.com> wrote:
> I'm crossposting from stackoverflow.
>
> I'm using the Leaflet mapping API. I would like to determine which point in
>
> a polyline has been clicked on. The polyline is one that I have rendered
> using the Directions services from MapQuest Open.
>
> The relevant code is:
>
> for ( var i = 0; i < latlngs.length; i++ ) {
>
> // the equals method uses a small fudge factor to decide if the two //
> points are the same.
>
> if ( latlng.equals( latlngs[ i ] )) { return i; } }
>
> .equals() is supposed to compare points with a fudge factor. When I click
> on the polyline I get an event with lat/lngs like:
>
> '38.83966582989183','-77.0083075761795'
>
> My lat/lngs in the polyline are of less precision as in:
>
> '38.841289','-77.008842'
>
> So as I loop through looking for a match equals() never returns true.
> Clearly, the click event is firing, so leaflet knows I've clicked the
> polyline, but there doesn't seem to be a clean way of getting the actual
> offset of the latlng that was clicked on.
>
> Per this ticket <https://github.com/Leaflet/Leaflet/issues/803>I've tried
> adjusting the precision to no avail.
>
> Going into the list of lat/lngs I can clearly see that the select lat/lng
> is between two points in the polyline.
>
> Reducing the precision returned from the mouse click doesn't seem to work
> either.
>
> Ideally, I'd like to have some method to get the offset into the latlng set
>
> of the polyline closest to where the user clicked. I can do a rough job by
> hand but would, ideally, like a clean solution.
>
> Any help/pointers would be greatly appreciated.
>
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "Leaflet" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to leaflet-js+...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>
>

Terry

unread,
Apr 24, 2013, 7:00:52 PM4/24/13
to leafl...@googlegroups.com
I'm trying to do exactly the same thing. At the moment, my method is as follows:

Iterate over the set of latlngs, comparing the angle formed between point n, click location & point n+1. When this angle is (close to) 180 degrees/pi radians, I know I am looking at a straight line - ie the click location lies between the two points. I also compare distance & come up with the best fit that way.

It works for the most part, but it's not perfect - there are still a few edge cases (ie lines very close together with vertices spaced far apart) where I sometimes end up with the wrong decision.

Terry

unread,
Apr 24, 2013, 7:39:24 PM4/24/13
to leafl...@googlegroups.com
Actually, I've re-factored my code a bit and it's now performing acceptably for my purposes.

I am comparing point pairs based on the angle as described in my previous post, and iterating over my set of latlngs. Once I have the best fit angle, I vote on the distance between AB & AC to work out whether A or C is closest to the user's click (B).

I am not using the set of latlngs provided by the polyline itself (though this shouldn't make any difference), as what I care about is actually another attribute belonging to the point in question - the array I am iterating over contains the same values as the polyline's latlngs.

Code as follows, let me know if you come up with any improvements.

that.historicalTrack.on('click', function(e) {

var point = null;
var angle = null;
var delta = null;
var minDelta = 10;
var numPoints = that.historicalTrackPoints.length;

var pointA = null;
var pointC = null;
var pointB = [e.latlng.lat, e.latlng.lng]
 
var distanceA = null;
var distanceC = null;
 
// Compare all points in the set
for(var i=0;i<numPoints-1;i++) {

pointA = [parseFloat(that.historicalTrackPoints[i].latitude), parseFloat(that.historicalTrackPoints[i].longitude)];
pointC = [parseFloat(that.historicalTrackPoints[i+1].latitude), parseFloat(that.historicalTrackPoints[i+1].longitude)];

// Angle between A--B--C
// Delta describes variance from PI radians
angle = vectorAngle(pointA, pointB, pointC);
if (angle < 0) angle = -angle;
delta = Math.PI - angle;
if (delta < 0) delta = -delta;
 
// A smaller delta should mean a better match... right?
if (delta < minDelta) {
minDelta = delta;
distanceA = e.latlng.distanceTo(L.latLng(pointA));
distanceC = e.latlng.distanceTo(L.latLng(pointC));
 
// Vote on the two points in the pair
if (distanceA < distanceC) {
point = that.historicalTrackPoints[i];
} else {
point = that.historicalTrackPoints[i+1];
}
}
}
var trackPopup = new L.Popup().setLatLng([point.latitude, point.longitude]).setContent(point.id);
trackPopup.addTo(map);
});

On Thursday, April 25, 2013 7:54:07 AM UTC+9:30, Yermo Lamers wrote:

Yermo Lamers

unread,
Apr 24, 2013, 8:27:53 PM4/24/13
to leafl...@googlegroups.com

I've come up with an alternate solution that is probably not coture of seems to work. I notice that there is a _originalPoints array in the polyline. There is also layerPoint member in the polyline event that is a Point object. 

I'm not a fan of using undocumented APIs but looping through the points finding the closest point to the clicked point, using the L.LineUtil.pointToSegmentDistance seems to work as in:

        var point = e.layerPoint;

var points = polyline._originalPoints;

for ( var i = 0; i < points.length; i++ )
{

if ( i < points.length - 2 )
{

if ( min_distance > L.LineUtil.pointToSegmentDistance( point, points[i], points[ i + 1] ) )
{
min_distance = L.LineUtil.pointToSegmentDistance( point, points[i], points[ i + 1] );
min_offset = i;

diego soto

unread,
Jul 24, 2013, 2:11:15 PM7/24/13
to leafl...@googlegroups.com

Stavros Doropoulos

unread,
Mar 23, 2014, 3:10:58 PM3/23/14
to leafl...@googlegroups.com
Hello, does anyone know if there is a better/more elegant way of doing this? I keep getting lots of false results. The other way is to split my polyline into hundreds of smaller ones but that has a huge performance issue.
My end result is to "highlight" the current path (between 2 real points) and apply a label.
Thank you!

Andrew Gregory

unread,
Mar 27, 2018, 4:47:52 PM3/27/18
to Leaflet
Hope you're well Terry.
Reply all
Reply to author
Forward
0 new messages