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.