Confine dragging of Google Maps V3 Marker to Polyl

2020-01-30 15:58发布

问题:

I've created a Google Map and have drawn a polyline on it. I've then added a marker to the start of the polyine (same coords as the starting coords of the polyline).

What I'd like to be able to do, is grab and drag the marker but have it "stick" to the polyline such that you can only drag it along the polyline and not away or to the side of it.

Is it possible to confine a draggable marker to a path in GM V3? If not, can anyone think how this might be done? There's the possibility of snapping the marker to the nearest point on the path when the user drops it, but I'd prefer a smoother "drag along the path" effect.

Happy to have ArcGis suggestions too. Have not provided code as this is more of an in theory question.

Let me know if I need to explain further.

Thanks in advance

回答1:

Ok so I have managed to solve this. It's not exactly elegant, and I'm sure it could be improved on but here's the general concept I've come up with:

I create an array of latlng points from a GPX file, but they only log points every 20s or so. That's not enough granularity for my purposes so what I did is I padded the array of points with about 10 points (on a linear line) between each pair of points logged by the gpx:

$.each(array_of_points_to_pad, function(key, pt) {
    var current_point = pt;//The current point
    var next_point = array_of_points_to_pad[key + 1];//The point immediately after the current point

    //Check that we're not on the last point 
    if (typeof next_point !== 'undefined') {
        //Get a 10th of the difference in latitude between current and next points
        var lat_incr = (next_point.lat() - current_point.lat()) / 10;

        //Get a 10th of the difference in longitude between current and next points
        var lng_incr = (next_point.lng() - current_point.lng()) / 10;

        //Add the current point to a new padded_points array
        padded_points.push(current_point);

        //Now add 10 additional points at lat_incr & lng_incr intervals between current and next points (in the new padded_points array)
        for (var i = 1; i <= 10; i++) {
            var new_pt = new google.maps.LatLng(current_point.lat() + (i * lat_incr), current_point.lng() + (i * lng_incr));
            padded_points.push(new_pt);
        }
    }
});

Now that I have a more refined array of points, I use this to plot a polyline. The padded polyline will look no different to a polyline drawn without the padding, as all of the additional points lie on a linear "as-the-crow-flies" line between the existing points.

var line = new google.maps.Polyline({
    path: polyline_path_points_padded,
    strokeColor: '#ff0000',
    strokeOpacity: 1.0,
    strokeWeight: 2
});
line.setMap(map);

Now I add a draggable marker at the start of the line:

var latLng = new google.maps.LatLng(startlat,startlng);
var marker = new google.maps.Marker({
  position: latLng,
  map: map,
  draggable:true
});

All that's left to do is control the drag and dragend events of this marker:

google.maps.event.addDomListener(marker,'dragend',function(e){
    marker.setPosition(find_closest_point_on_path(e.latLng,padded_points));
});

google.maps.event.addDomListener(marker,'drag',function(e){
    marker.setPosition(find_closest_point_on_path(e.latLng,padded_points));
});

Here we simply send the latLng of the marker to a function find_closest_point_on_path() whilst dragging and when the marker is dropped. We send the padded array of points as a path to search.

The function looks like this:

function find_closest_point_on_path(marker_pt,path_pts){
    distances = new Array();
    distance_keys = new Array();
    $.each(path_pts,function(key, path_pt){
        var R = 6371; // km
        var dLat = (path_pt.lat()-marker_pt.lat()).toRad();
        var dLon = (path_pt.lng()-marker_pt.lng()).toRad();
        var lat1 = marker_pt.lat().toRad();
        var lat2 = path_pt.lat().toRad();

        var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); 
        var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
        var d = R * c;
        //Store the key of the point on the path that matches this distance
        distance_keys[d] = key; 

    });
            //Return the latLng pt on the path for the second closest point 
    return path_pts[distance_keys[_.min(distances)]+1];
}

What this function does (with the help of a degrees to radians function) is it finds the distance between the markers position and all of the points on the line. Then it finds the closest point to the marker and returns the coordinates for the very next closest point after that one. This way, when you drag or drop the marker it "snaps" to the next point (rather than getting stuck in one spot).

Working JS Fiddle Below:

http://jsfiddle.net/Z5GwW/4/

Have not cross-browser tested. Working in Chrome latest version.



回答2:

Thank you for this solution.

To get it working without dependencies, I had to modify a couple of lines:

First, check the toRad() function exists:

if (typeof(Number.prototype.toRad) === "undefined") {
	  Number.prototype.toRad = function() {
	    return this * Math.PI / 180;
	  }
	}

And also, remove _. dependency by replacing the return code:

return path_pts[distance_keys[ Math.min(...distances) ]+1];

and finally, include distances[] before distancekeys[]

distances[key] = d;
distance_keys[d] = key;