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