--- Revision None +++ Revision 646333356535 @@ -0,0 +1,325 @@ +L.GeomUtils = (function() { + var self; + return self = { + + // Calculate if a point p is between a and b + isBetween: function(x, a, b, epsilon) { + epsilon = epsilon || 0.5; + var d = x.distanceTo(a) + x.distanceTo(b) - a.distanceTo(b); + return d < epsilon; + }, + + // Use LatLng + getPercentageDistanceFromPolyline: function(ll, polyline) { + // Will test every point, considering a point is in a segment with an error of 2 meters + return self.getPercentageDistance(ll, polyline.getLatLngs(), 5 /* in meters */, true); + }, + + // May be used for performance issue but you will loose precision + getPercentageDistanceFromPolylineAsPoints: function(point, polyline) { + return self.getPercentageDistance(point, polyline._parts[0], 5, true); + }, + + // You may pass latlng or point to this function + getPercentageDistance: function(x, xs, epsilon, only_first, recurse) { + var xs_len = 0.0 + , distance_found = false + , closest_idx = null + , distance = Number.MAX_VALUE; + + for (var i = 0; i < xs.length - 1; i++) { + var x1 = xs[i], x2 = xs[i+1]; + + // We iterate on each segment of the path + if (!distance_found || !only_first) { + if (self.isBetween(x, x1, x2, epsilon)) { + distance_found = true; + xdistance = xs_len + x.distanceTo(x1); + + if (only_first || xdistance < distance) { + distance = xdistance; + closest_idx = i; + } + } + } + + xs_len += x1.distanceTo(x2); + } + + if (!distance_found) { + if (!recurse) { + console.warn('Could not find ' + x + ' in ' + xs); + return null; + } + // Try with closest point. + var seg = L.GeomUtils.closestSegment(x, xs) + , p = L.LineUtil.closestPointOnSegment(x, seg[0], seg[1]); + return L.GeomUtils.getPercentageDistance(p, xs, epsilon, only_first, true); + } + var percent = Math.round((distance / xs_len)*10000)/10000; + return { 'distance': percent, 'closest': closest_idx }; + }, + + getLatLngFromPos: function(map, polyline, pos_list, equal_delta) { + equal_delta === equal_delta === undefined ? 2 /*in meters*/ : equal_delta; + + // Safety check : should be ordered and 0.0 <= X <=1.0! + $.each(pos_list, function(i, pos) { + var prev_pos = pos[i - 1]; + var sorted = prev_pos === undefined ? true : pos > prev_pos; + if (! (pos >= 0 && pos <= 1 && sorted)) { + throw 'Wrong value: ' + pos_list; + } + }); + + // Polyline related + var polyline_lls = polyline.getLatLngs(); + var d_len = self.getDistances(polyline_lls) + , polyline_len = d_len.length + , polyline_distances = d_len.distances; + + // Simple situation... simple solution. + if (pos_list.length == 1) { + if (pos_list[0] == 0.0) return [self.cloneLatLng(polyline_lls[0])]; + if (pos_list[0] == 1.0) return [self.cloneLatLng(polyline_lls[polyline_lls.length-1])]; + } + + var ds = $.map(pos_list, function(pos) { return polyline_len * pos; }); + + var res = []; + var i; + + var current_distance = ds.shift() + , current_geom = []; + + // If pos is 0.0, take first latlng + if (current_distance == 0.0) { + res.push(self.cloneLatLng(polyline_distances[0].x1)); + current_distance = ds.shift() + } + + for (i = 0; i < polyline_distances.length; i++) { + var dist = polyline_distances[i]; + var new_acc = dist.acc + dist.distance; + + var delta = Math.abs(current_distance - new_acc) + var distance_equal = delta < equal_delta; + + if (distance_equal || current_distance < new_acc) { + if (distance_equal) { + // Same point + res.push(self.cloneLatLng(dist.x2)); + } else {  + // current_distance < new_acc + // New point + + var dist_from_point = current_distance - dist.acc; + var ratio_dist = dist_from_point / dist.distance; + var ll = self.getPointOnLine(map, ratio_dist, dist.x1, dist.x2); + + res.push(ll); + } + + if (ds.length == 0) break; + current_distance = ds.shift() + } + } + + if (res.length < 1) console.warn("Could not get LatLng from position " + pos_list); + if (window.DEBUG) { + console.log("Invert getLatLngFromPos("+ pos_list[0] + ") : " + + JSON.stringify(self.getPercentageDistanceFromPolyline(res[0], polyline))); + } + return res; + }, + + cloneLatLng: function(latlng) { + return new L.LatLng(latlng.lat, latlng.lng); + }, + + getPointOnLine: function(map, ratio_dist, ll1, ll2) { + if (ratio_dist == 0.0) return ll1; + if (ratio_dist == 1.0) return ll2; + var zoom = map.getMaxZoom() + , p1 = map.project(ll1, zoom) + , p2 = map.project(ll2, zoom) + , d = p1.distanceTo(p2); + + var x_new = p1.x + (p2.x - p1.x) * ratio_dist + , y_new = p1.y + (p2.y - p1.y) * ratio_dist + , ll_new = map.unproject(new L.Point(x_new, y_new), zoom); + console.assert(!ll_new.equals(ll1) && !ll_new.equals(ll2), ratio_dist + ' got extremity (margin is ' + L.LatLng.MAX_MARGIN + ')'); + return ll_new; + }, + + getGradient: function(x1, y1, x2, y2) { + var a = (y2 - y1) / (x2 - x1); + var b = y1 - (a * x1); + return {'a': a, 'b': b}; + }, + + getDistances: function(xs) { + var xs_len = 0.0, d, distances = []; + + for (var i = 0; i < xs.length - 1; i++) { + var x1 = xs[i], x2 = xs[i+1]; + d = x1.distanceTo(x2); + + // acc: so far (without distance) + distances.push({ + 'i1': i, 'i2': i+1, + 'x1': x1, 'x2': x2, + 'acc': xs_len, 'distance': d + }); + + xs_len += d + } + return {'length': xs_len, 'distances': distances}; + }, + + // Calculate length (works for either points or latlngs) + length: function(xs) { + var xs_len = 0; + for (var i = 0; i < xs.length - 1; i++) { + xs_len += xs[i].distanceTo(xs[i+1]); + } + return xs_len; + }, + + distance: function (map, latlng1, latlng2) { + return map.latLngToLayerPoint(latlng1).distanceTo(map.latLngToLayerPoint(latlng2)); + }, + + distanceSegment: function (map, latlng, latlngA, latlngB) { + var p = map.latLngToLayerPoint(latlng), + p1 = map.latLngToLayerPoint(latlngA), + p2 = map.latLngToLayerPoint(latlngB); + return L.LineUtil.pointToSegmentDistance(p, p1, p2); + }, + + latlngOnSegment: function (map, latlng, latlngA, latlngB) { + var maxzoom = map.getMaxZoom(); + var p = map.project(latlng, maxzoom), + p1 = map.project(latlngA, maxzoom), + p2 = map.project(latlngB, maxzoom); + closest = L.LineUtil.closestPointOnSegment(p, p1, p2); + return map.unproject(closest, maxzoom); + }, + + closestSegment: function (p, points) { + var mindist = Number.MAX_VALUE + , idx = 0; + for (var i=0; i1000) console.warn("Snap list is very big : " + n + " objects!"); + + // Iterate the whole snaplist + for (var i = 0; i < n ; i++) { + var object = snaplist[i], + ll = null, + distance = Number.MAX_VALUE; + if (object.getLatLng) { + // Single dimension, snap on points + ll = object.getLatLng(); + distance = self.distance(map, marker.getLatLng(), ll); + } + else { + ll = L.GeomUtils.closestOnLine(map, marker.getLatLng(), object); + distance = L.GeomUtils.distance(map, marker.getLatLng(), ll); + } + // Keep the closest point of all objects + if (distance < snap_distance && distance < mindist) { + mindist = distance; + chosen = object; + point = ll; + } + } + // Try to snap on line points (extremities and middle points) + if (chosen && chosen.getLatLngs) { + var mindist = snap_distance, + linepoint = null; + for (var i=0; i