leaflet.geometryutil.js

  1. // Packaging/modules magic dance.
  2. (function (factory) {
  3. var L;
  4. if (typeof define === 'function' && define.amd) {
  5. // AMD
  6. define(['leaflet'], factory);
  7. } else if (typeof module !== 'undefined') {
  8. // Node/CommonJS
  9. L = require('leaflet');
  10. module.exports = factory(L);
  11. } else {
  12. // Browser globals
  13. if (typeof window.L === 'undefined')
  14. throw 'Leaflet must be loaded first';
  15. factory(window.L);
  16. }
  17. }(function (L) {
  18. "use strict";
  19. L.Polyline._flat = L.LineUtil.isFlat || L.Polyline._flat || function (latlngs) {
  20. // true if it's a flat array of latlngs; false if nested
  21. return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
  22. };
  23. /**
  24. * @fileOverview Leaflet Geometry utilities for distances and linear referencing.
  25. * @name L.GeometryUtil
  26. */
  27. L.GeometryUtil = L.extend(L.GeometryUtil || {}, {
  28. /**
  29. Shortcut function for planar distance between two {L.LatLng} at current zoom.
  30. @tutorial distance-length
  31. @param {L.Map} map Leaflet map to be used for this method
  32. @param {L.LatLng} latlngA geographical point A
  33. @param {L.LatLng} latlngB geographical point B
  34. @returns {Number} planar distance
  35. */
  36. distance: function (map, latlngA, latlngB) {
  37. return map.latLngToLayerPoint(latlngA).distanceTo(map.latLngToLayerPoint(latlngB));
  38. },
  39. /**
  40. Shortcut function for planar distance between a {L.LatLng} and a segment (A-B).
  41. @param {L.Map} map Leaflet map to be used for this method
  42. @param {L.LatLng} latlng - The position to search
  43. @param {L.LatLng} latlngA geographical point A of the segment
  44. @param {L.LatLng} latlngB geographical point B of the segment
  45. @returns {Number} planar distance
  46. */
  47. distanceSegment: function (map, latlng, latlngA, latlngB) {
  48. var p = map.latLngToLayerPoint(latlng),
  49. p1 = map.latLngToLayerPoint(latlngA),
  50. p2 = map.latLngToLayerPoint(latlngB);
  51. return L.LineUtil.pointToSegmentDistance(p, p1, p2);
  52. },
  53. /**
  54. Shortcut function for converting distance to readable distance.
  55. @param {Number} distance distance to be converted
  56. @param {String} unit 'metric' or 'imperial'
  57. @returns {String} in yard or miles
  58. */
  59. readableDistance: function (distance, unit) {
  60. var isMetric = (unit !== 'imperial'),
  61. distanceStr;
  62. if (isMetric) {
  63. // show metres when distance is < 1km, then show km
  64. if (distance > 1000) {
  65. distanceStr = (distance / 1000).toFixed(2) + ' km';
  66. }
  67. else {
  68. distanceStr = Math.ceil(distance) + ' m';
  69. }
  70. }
  71. else {
  72. distance *= 1.09361;
  73. if (distance > 1760) {
  74. distanceStr = (distance / 1760).toFixed(2) + ' miles';
  75. }
  76. else {
  77. distanceStr = Math.ceil(distance) + ' yd';
  78. }
  79. }
  80. return distanceStr;
  81. },
  82. /**
  83. Returns true if the latlng belongs to segment A-B
  84. @param {L.LatLng} latlng - The position to search
  85. @param {L.LatLng} latlngA geographical point A of the segment
  86. @param {L.LatLng} latlngB geographical point B of the segment
  87. @param {?Number} [tolerance=0.2] tolerance to accept if latlng belongs really
  88. @returns {boolean}
  89. */
  90. belongsSegment: function(latlng, latlngA, latlngB, tolerance) {
  91. tolerance = tolerance === undefined ? 0.2 : tolerance;
  92. var hypotenuse = latlngA.distanceTo(latlngB),
  93. delta = latlngA.distanceTo(latlng) + latlng.distanceTo(latlngB) - hypotenuse;
  94. return delta/hypotenuse < tolerance;
  95. },
  96. /**
  97. * Returns total length of line
  98. * @tutorial distance-length
  99. *
  100. * @param {L.Polyline|Array<L.Point>|Array<L.LatLng>} coords Set of coordinates
  101. * @returns {Number} Total length (pixels for Point, meters for LatLng)
  102. */
  103. length: function (coords) {
  104. var accumulated = L.GeometryUtil.accumulatedLengths(coords);
  105. return accumulated.length > 0 ? accumulated[accumulated.length-1] : 0;
  106. },
  107. /**
  108. * Returns a list of accumulated length along a line.
  109. * @param {L.Polyline|Array<L.Point>|Array<L.LatLng>} coords Set of coordinates
  110. * @returns {Array<Number>} Array of accumulated lengths (pixels for Point, meters for LatLng)
  111. */
  112. accumulatedLengths: function (coords) {
  113. if (typeof coords.getLatLngs == 'function') {
  114. coords = coords.getLatLngs();
  115. }
  116. if (coords.length === 0)
  117. return [];
  118. var total = 0,
  119. lengths = [0];
  120. for (var i = 0, n = coords.length - 1; i< n; i++) {
  121. total += coords[i].distanceTo(coords[i+1]);
  122. lengths.push(total);
  123. }
  124. return lengths;
  125. },
  126. /**
  127. Returns the closest point of a {L.LatLng} on the segment (A-B)
  128. @tutorial closest
  129. @param {L.Map} map Leaflet map to be used for this method
  130. @param {L.LatLng} latlng - The position to search
  131. @param {L.LatLng} latlngA geographical point A of the segment
  132. @param {L.LatLng} latlngB geographical point B of the segment
  133. @returns {L.LatLng} Closest geographical point
  134. */
  135. closestOnSegment: function (map, latlng, latlngA, latlngB) {
  136. var maxzoom = map.getMaxZoom();
  137. if (maxzoom === Infinity)
  138. maxzoom = map.getZoom();
  139. var p = map.project(latlng, maxzoom),
  140. p1 = map.project(latlngA, maxzoom),
  141. p2 = map.project(latlngB, maxzoom),
  142. closest = L.LineUtil.closestPointOnSegment(p, p1, p2);
  143. return map.unproject(closest, maxzoom);
  144. },
  145. /**
  146. Returns the closest latlng on layer.
  147. Accept nested arrays
  148. @tutorial closest
  149. @param {L.Map} map Leaflet map to be used for this method
  150. @param {Array<L.LatLng>|Array<Array<L.LatLng>>|L.PolyLine|L.Polygon} layer - Layer that contains the result
  151. @param {L.LatLng} latlng - The position to search
  152. @param {?boolean} [vertices=false] - Whether to restrict to path vertices.
  153. @returns {L.LatLng} Closest geographical point or null if layer param is incorrect
  154. */
  155. closest: function (map, layer, latlng, vertices) {
  156. var latlngs,
  157. mindist = Infinity,
  158. result = null,
  159. i, n, distance, subResult;
  160. if (layer instanceof Array) {
  161. // if layer is Array<Array<T>>
  162. if (layer[0] instanceof Array && typeof layer[0][0] !== 'number') {
  163. // if we have nested arrays, we calc the closest for each array
  164. // recursive
  165. for (i = 0; i < layer.length; i++) {
  166. subResult = L.GeometryUtil.closest(map, layer[i], latlng, vertices);
  167. if (subResult && subResult.distance < mindist) {
  168. mindist = subResult.distance;
  169. result = subResult;
  170. }
  171. }
  172. return result;
  173. } else if (layer[0] instanceof L.LatLng
  174. || typeof layer[0][0] === 'number'
  175. || typeof layer[0].lat === 'number') { // we could have a latlng as [x,y] with x & y numbers or {lat, lng}
  176. layer = L.polyline(layer);
  177. } else {
  178. return result;
  179. }
  180. }
  181. // if we don't have here a Polyline, that means layer is incorrect
  182. // see https://github.com/makinacorpus/Leaflet.GeometryUtil/issues/23
  183. if (! ( layer instanceof L.Polyline ) )
  184. return result;
  185. // deep copy of latlngs
  186. latlngs = JSON.parse(JSON.stringify(layer.getLatLngs().slice(0)));
  187. // add the last segment for L.Polygon
  188. if (layer instanceof L.Polygon) {
  189. // add the last segment for each child that is a nested array
  190. var addLastSegment = function(latlngs) {
  191. if (L.Polyline._flat(latlngs)) {
  192. latlngs.push(latlngs[0]);
  193. } else {
  194. for (var i = 0; i < latlngs.length; i++) {
  195. addLastSegment(latlngs[i]);
  196. }
  197. }
  198. };
  199. addLastSegment(latlngs);
  200. }
  201. // we have a multi polygon / multi polyline / polygon with holes
  202. // use recursive to explore and return the good result
  203. if ( ! L.Polyline._flat(latlngs) ) {
  204. for (i = 0; i < latlngs.length; i++) {
  205. // if we are at the lower level, and if we have a L.Polygon, we add the last segment
  206. subResult = L.GeometryUtil.closest(map, latlngs[i], latlng, vertices);
  207. if (subResult.distance < mindist) {
  208. mindist = subResult.distance;
  209. result = subResult;
  210. }
  211. }
  212. return result;
  213. } else {
  214. // Lookup vertices
  215. if (vertices) {
  216. for(i = 0, n = latlngs.length; i < n; i++) {
  217. var ll = latlngs[i];
  218. distance = L.GeometryUtil.distance(map, latlng, ll);
  219. if (distance < mindist) {
  220. mindist = distance;
  221. result = ll;
  222. result.distance = distance;
  223. }
  224. }
  225. return result;
  226. }
  227. // Keep the closest point of all segments
  228. for (i = 0, n = latlngs.length; i < n-1; i++) {
  229. var latlngA = latlngs[i],
  230. latlngB = latlngs[i+1];
  231. distance = L.GeometryUtil.distanceSegment(map, latlng, latlngA, latlngB);
  232. if (distance <= mindist) {
  233. mindist = distance;
  234. result = L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB);
  235. result.distance = distance;
  236. }
  237. }
  238. return result;
  239. }
  240. },
  241. /**
  242. Returns the closest layer to latlng among a list of layers.
  243. @tutorial closest
  244. @param {L.Map} map Leaflet map to be used for this method
  245. @param {Array<L.ILayer>} layers Set of layers
  246. @param {L.LatLng} latlng - The position to search
  247. @returns {object} ``{layer, latlng, distance}`` or ``null`` if list is empty;
  248. */
  249. closestLayer: function (map, layers, latlng) {
  250. var mindist = Infinity,
  251. result = null,
  252. ll = null,
  253. distance = Infinity;
  254. for (var i = 0, n = layers.length; i < n; i++) {
  255. var layer = layers[i];
  256. if (layer instanceof L.LayerGroup) {
  257. // recursive
  258. var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng);
  259. if (subResult.distance < mindist) {
  260. mindist = subResult.distance;
  261. result = subResult;
  262. }
  263. } else {
  264. // Single dimension, snap on points, else snap on closest
  265. if (typeof layer.getLatLng == 'function') {
  266. ll = layer.getLatLng();
  267. distance = L.GeometryUtil.distance(map, latlng, ll);
  268. }
  269. else {
  270. ll = L.GeometryUtil.closest(map, layer, latlng);
  271. if (ll) distance = ll.distance; // Can return null if layer has no points.
  272. }
  273. if (distance < mindist) {
  274. mindist = distance;
  275. result = {layer: layer, latlng: ll, distance: distance};
  276. }
  277. }
  278. }
  279. return result;
  280. },
  281. /**
  282. Returns the n closest layers to latlng among a list of input layers.
  283. @param {L.Map} map - Leaflet map to be used for this method
  284. @param {Array<L.ILayer>} layers - Set of layers
  285. @param {L.LatLng} latlng - The position to search
  286. @param {?Number} [n=layers.length] - the expected number of output layers.
  287. @returns {Array<object>} an array of objects ``{layer, latlng, distance}`` or ``null`` if the input is invalid (empty list or negative n)
  288. */
  289. nClosestLayers: function (map, layers, latlng, n) {
  290. n = typeof n === 'number' ? n : layers.length;
  291. if (n < 1 || layers.length < 1) {
  292. return null;
  293. }
  294. var results = [];
  295. var distance, ll;
  296. for (var i = 0, m = layers.length; i < m; i++) {
  297. var layer = layers[i];
  298. if (layer instanceof L.LayerGroup) {
  299. // recursive
  300. var subResult = L.GeometryUtil.closestLayer(map, layer.getLayers(), latlng);
  301. results.push(subResult);
  302. } else {
  303. // Single dimension, snap on points, else snap on closest
  304. if (typeof layer.getLatLng == 'function') {
  305. ll = layer.getLatLng();
  306. distance = L.GeometryUtil.distance(map, latlng, ll);
  307. }
  308. else {
  309. ll = L.GeometryUtil.closest(map, layer, latlng);
  310. if (ll) distance = ll.distance; // Can return null if layer has no points.
  311. }
  312. results.push({layer: layer, latlng: ll, distance: distance});
  313. }
  314. }
  315. results.sort(function(a, b) {
  316. return a.distance - b.distance;
  317. });
  318. if (results.length > n) {
  319. return results.slice(0, n);
  320. } else {
  321. return results;
  322. }
  323. },
  324. /**
  325. * Returns all layers within a radius of the given position, in an ascending order of distance.
  326. @param {L.Map} map Leaflet map to be used for this method
  327. @param {Array<ILayer>} layers - A list of layers.
  328. @param {L.LatLng} latlng - The position to search
  329. @param {?Number} [radius=Infinity] - Search radius in pixels
  330. @return {object[]} an array of objects including layer within the radius, closest latlng, and distance
  331. */
  332. layersWithin: function(map, layers, latlng, radius) {
  333. radius = typeof radius == 'number' ? radius : Infinity;
  334. var results = [];
  335. var ll = null;
  336. var distance = 0;
  337. for (var i = 0, n = layers.length; i < n; i++) {
  338. var layer = layers[i];
  339. if (typeof layer.getLatLng == 'function') {
  340. ll = layer.getLatLng();
  341. distance = L.GeometryUtil.distance(map, latlng, ll);
  342. }
  343. else {
  344. ll = L.GeometryUtil.closest(map, layer, latlng);
  345. if (ll) distance = ll.distance; // Can return null if layer has no points.
  346. }
  347. if (ll && distance < radius) {
  348. results.push({layer: layer, latlng: ll, distance: distance});
  349. }
  350. }
  351. var sortedResults = results.sort(function(a, b) {
  352. return a.distance - b.distance;
  353. });
  354. return sortedResults;
  355. },
  356. /**
  357. Returns the closest position from specified {LatLng} among specified layers,
  358. with a maximum tolerance in pixels, providing snapping behaviour.
  359. @tutorial closest
  360. @param {L.Map} map Leaflet map to be used for this method
  361. @param {Array<ILayer>} layers - A list of layers to snap on.
  362. @param {L.LatLng} latlng - The position to snap
  363. @param {?Number} [tolerance=Infinity] - Maximum number of pixels.
  364. @param {?boolean} [withVertices=true] - Snap to layers vertices or segment points (not only vertex)
  365. @returns {object} with snapped {LatLng} and snapped {Layer} or null if tolerance exceeded.
  366. */
  367. closestLayerSnap: function (map, layers, latlng, tolerance, withVertices) {
  368. tolerance = typeof tolerance == 'number' ? tolerance : Infinity;
  369. withVertices = typeof withVertices == 'boolean' ? withVertices : true;
  370. var result = L.GeometryUtil.closestLayer(map, layers, latlng);
  371. if (!result || result.distance > tolerance)
  372. return null;
  373. // If snapped layer is linear, try to snap on vertices (extremities and middle points)
  374. if (withVertices && typeof result.layer.getLatLngs == 'function') {
  375. var closest = L.GeometryUtil.closest(map, result.layer, result.latlng, true);
  376. if (closest.distance < tolerance) {
  377. result.latlng = closest;
  378. result.distance = L.GeometryUtil.distance(map, closest, latlng);
  379. }
  380. }
  381. return result;
  382. },
  383. /**
  384. Returns the Point located on a segment at the specified ratio of the segment length.
  385. @param {L.Point} pA coordinates of point A
  386. @param {L.Point} pB coordinates of point B
  387. @param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive.
  388. @returns {L.Point} the interpolated point.
  389. */
  390. interpolateOnPointSegment: function (pA, pB, ratio) {
  391. return L.point(
  392. (pA.x * (1 - ratio)) + (ratio * pB.x),
  393. (pA.y * (1 - ratio)) + (ratio * pB.y)
  394. );
  395. },
  396. /**
  397. Returns the coordinate of the point located on a line at the specified ratio of the line length.
  398. @param {L.Map} map Leaflet map to be used for this method
  399. @param {Array<L.LatLng>|L.PolyLine} latlngs Set of geographical points
  400. @param {Number} ratio the length ratio, expressed as a decimal between 0 and 1, inclusive
  401. @returns {Object} an object with latLng ({LatLng}) and predecessor ({Number}), the index of the preceding vertex in the Polyline
  402. (-1 if the interpolated point is the first vertex)
  403. */
  404. interpolateOnLine: function (map, latLngs, ratio) {
  405. latLngs = (latLngs instanceof L.Polyline) ? latLngs.getLatLngs() : latLngs;
  406. var n = latLngs.length;
  407. if (n < 2) {
  408. return null;
  409. }
  410. // ensure the ratio is between 0 and 1;
  411. ratio = Math.max(Math.min(ratio, 1), 0);
  412. if (ratio === 0) {
  413. return {
  414. latLng: latLngs[0] instanceof L.LatLng ? latLngs[0] : L.latLng(latLngs[0]),
  415. predecessor: -1
  416. };
  417. }
  418. if (ratio == 1) {
  419. return {
  420. latLng: latLngs[latLngs.length -1] instanceof L.LatLng ? latLngs[latLngs.length -1] : L.latLng(latLngs[latLngs.length -1]),
  421. predecessor: latLngs.length - 2
  422. };
  423. }
  424. // project the LatLngs as Points,
  425. // and compute total planar length of the line at max precision
  426. var maxzoom = map.getMaxZoom();
  427. if (maxzoom === Infinity)
  428. maxzoom = map.getZoom();
  429. var pts = [];
  430. var lineLength = 0;
  431. for(var i = 0; i < n; i++) {
  432. pts[i] = map.project(latLngs[i], maxzoom);
  433. if(i > 0)
  434. lineLength += pts[i-1].distanceTo(pts[i]);
  435. }
  436. var ratioDist = lineLength * ratio;
  437. // follow the line segments [ab], adding lengths,
  438. // until we find the segment where the points should lie on
  439. var cumulativeDistanceToA = 0, cumulativeDistanceToB = 0;
  440. for (var i = 0; cumulativeDistanceToB < ratioDist; i++) {
  441. var pointA = pts[i], pointB = pts[i+1];
  442. cumulativeDistanceToA = cumulativeDistanceToB;
  443. cumulativeDistanceToB += pointA.distanceTo(pointB);
  444. }
  445. if (pointA == undefined && pointB == undefined) { // Happens when line has no length
  446. var pointA = pts[0], pointB = pts[1], i = 1;
  447. }
  448. // compute the ratio relative to the segment [ab]
  449. var segmentRatio = ((cumulativeDistanceToB - cumulativeDistanceToA) !== 0) ? ((ratioDist - cumulativeDistanceToA) / (cumulativeDistanceToB - cumulativeDistanceToA)) : 0;
  450. var interpolatedPoint = L.GeometryUtil.interpolateOnPointSegment(pointA, pointB, segmentRatio);
  451. return {
  452. latLng: map.unproject(interpolatedPoint, maxzoom),
  453. predecessor: i-1
  454. };
  455. },
  456. /**
  457. Returns a float between 0 and 1 representing the location of the
  458. closest point on polyline to the given latlng, as a fraction of total line length.
  459. (opposite of L.GeometryUtil.interpolateOnLine())
  460. @param {L.Map} map Leaflet map to be used for this method
  461. @param {L.PolyLine} polyline Polyline on which the latlng will be search
  462. @param {L.LatLng} latlng The position to search
  463. @returns {Number} Float between 0 and 1
  464. */
  465. locateOnLine: function (map, polyline, latlng) {
  466. var latlngs = polyline.getLatLngs();
  467. if (latlng.equals(latlngs[0]))
  468. return 0.0;
  469. if (latlng.equals(latlngs[latlngs.length-1]))
  470. return 1.0;
  471. var point = L.GeometryUtil.closest(map, polyline, latlng, false),
  472. lengths = L.GeometryUtil.accumulatedLengths(latlngs),
  473. total_length = lengths[lengths.length-1],
  474. portion = 0,
  475. found = false;
  476. for (var i=0, n = latlngs.length-1; i < n; i++) {
  477. var l1 = latlngs[i],
  478. l2 = latlngs[i+1];
  479. portion = lengths[i];
  480. if (L.GeometryUtil.belongsSegment(point, l1, l2, 0.001)) {
  481. portion += l1.distanceTo(point);
  482. found = true;
  483. break;
  484. }
  485. }
  486. if (!found) {
  487. throw "Could not interpolate " + latlng.toString() + " within " + polyline.toString();
  488. }
  489. return portion / total_length;
  490. },
  491. /**
  492. Returns a clone with reversed coordinates.
  493. @param {L.PolyLine} polyline polyline to reverse
  494. @returns {L.PolyLine} polyline reversed
  495. */
  496. reverse: function (polyline) {
  497. return L.polyline(polyline.getLatLngs().slice(0).reverse());
  498. },
  499. /**
  500. Returns a sub-part of the polyline, from start to end.
  501. If start is superior to end, returns extraction from inverted line.
  502. @param {L.Map} map Leaflet map to be used for this method
  503. @param {L.PolyLine} polyline Polyline on which will be extracted the sub-part
  504. @param {Number} start ratio, expressed as a decimal between 0 and 1, inclusive
  505. @param {Number} end ratio, expressed as a decimal between 0 and 1, inclusive
  506. @returns {Array<L.LatLng>} new polyline
  507. */
  508. extract: function (map, polyline, start, end) {
  509. if (start > end) {
  510. return L.GeometryUtil.extract(map, L.GeometryUtil.reverse(polyline), 1.0-start, 1.0-end);
  511. }
  512. // Bound start and end to [0-1]
  513. start = Math.max(Math.min(start, 1), 0);
  514. end = Math.max(Math.min(end, 1), 0);
  515. var latlngs = polyline.getLatLngs(),
  516. startpoint = L.GeometryUtil.interpolateOnLine(map, polyline, start),
  517. endpoint = L.GeometryUtil.interpolateOnLine(map, polyline, end);
  518. // Return single point if start == end
  519. if (start == end) {
  520. var point = L.GeometryUtil.interpolateOnLine(map, polyline, end);
  521. return [point.latLng];
  522. }
  523. // Array.slice() works indexes at 0
  524. if (startpoint.predecessor == -1)
  525. startpoint.predecessor = 0;
  526. if (endpoint.predecessor == -1)
  527. endpoint.predecessor = 0;
  528. var result = latlngs.slice(startpoint.predecessor+1, endpoint.predecessor+1);
  529. result.unshift(startpoint.latLng);
  530. result.push(endpoint.latLng);
  531. return result;
  532. },
  533. /**
  534. Returns true if first polyline ends where other second starts.
  535. @param {L.PolyLine} polyline First polyline
  536. @param {L.PolyLine} other Second polyline
  537. @returns {bool}
  538. */
  539. isBefore: function (polyline, other) {
  540. if (!other) return false;
  541. var lla = polyline.getLatLngs(),
  542. llb = other.getLatLngs();
  543. return (lla[lla.length-1]).equals(llb[0]);
  544. },
  545. /**
  546. Returns true if first polyline starts where second ends.
  547. @param {L.PolyLine} polyline First polyline
  548. @param {L.PolyLine} other Second polyline
  549. @returns {bool}
  550. */
  551. isAfter: function (polyline, other) {
  552. if (!other) return false;
  553. var lla = polyline.getLatLngs(),
  554. llb = other.getLatLngs();
  555. return (lla[0]).equals(llb[llb.length-1]);
  556. },
  557. /**
  558. Returns true if first polyline starts where second ends or start.
  559. @param {L.PolyLine} polyline First polyline
  560. @param {L.PolyLine} other Second polyline
  561. @returns {bool}
  562. */
  563. startsAtExtremity: function (polyline, other) {
  564. if (!other) return false;
  565. var lla = polyline.getLatLngs(),
  566. llb = other.getLatLngs(),
  567. start = lla[0];
  568. return start.equals(llb[0]) || start.equals(llb[llb.length-1]);
  569. },
  570. /**
  571. Returns horizontal angle in degres between two points.
  572. @param {L.Point} a Coordinates of point A
  573. @param {L.Point} b Coordinates of point B
  574. @returns {Number} horizontal angle
  575. */
  576. computeAngle: function(a, b) {
  577. return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI);
  578. },
  579. /**
  580. Returns slope (Ax+B) between two points.
  581. @param {L.Point} a Coordinates of point A
  582. @param {L.Point} b Coordinates of point B
  583. @returns {Object} with ``a`` and ``b`` properties.
  584. */
  585. computeSlope: function(a, b) {
  586. var s = (b.y - a.y) / (b.x - a.x),
  587. o = a.y - (s * a.x);
  588. return {'a': s, 'b': o};
  589. },
  590. /**
  591. Returns LatLng of rotated point around specified LatLng center.
  592. @param {L.LatLng} latlngPoint: point to rotate
  593. @param {double} angleDeg: angle to rotate in degrees
  594. @param {L.LatLng} latlngCenter: center of rotation
  595. @returns {L.LatLng} rotated point
  596. */
  597. rotatePoint: function(map, latlngPoint, angleDeg, latlngCenter) {
  598. var maxzoom = map.getMaxZoom();
  599. if (maxzoom === Infinity)
  600. maxzoom = map.getZoom();
  601. var angleRad = angleDeg*Math.PI/180,
  602. pPoint = map.project(latlngPoint, maxzoom),
  603. pCenter = map.project(latlngCenter, maxzoom),
  604. x2 = Math.cos(angleRad)*(pPoint.x-pCenter.x) - Math.sin(angleRad)*(pPoint.y-pCenter.y) + pCenter.x,
  605. y2 = Math.sin(angleRad)*(pPoint.x-pCenter.x) + Math.cos(angleRad)*(pPoint.y-pCenter.y) + pCenter.y;
  606. return map.unproject(new L.Point(x2,y2), maxzoom);
  607. },
  608. /**
  609. Returns the bearing in degrees clockwise from north (0 degrees)
  610. from the first L.LatLng to the second, at the first LatLng
  611. @param {L.LatLng} latlng1: origin point of the bearing
  612. @param {L.LatLng} latlng2: destination point of the bearing
  613. @returns {float} degrees clockwise from north.
  614. */
  615. bearing: function(latlng1, latlng2) {
  616. var rad = Math.PI / 180,
  617. lat1 = latlng1.lat * rad,
  618. lat2 = latlng2.lat * rad,
  619. lon1 = latlng1.lng * rad,
  620. lon2 = latlng2.lng * rad,
  621. y = Math.sin(lon2 - lon1) * Math.cos(lat2),
  622. x = Math.cos(lat1) * Math.sin(lat2) -
  623. Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
  624. var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360;
  625. return bearing >= 180 ? bearing-360 : bearing;
  626. },
  627. /**
  628. Returns the point that is a distance and heading away from
  629. the given origin point.
  630. @param {L.LatLng} latlng: origin point
  631. @param {float} heading: heading in degrees, clockwise from 0 degrees north.
  632. @param {float} distance: distance in meters
  633. @returns {L.latLng} the destination point.
  634. Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html
  635. for a great reference and examples.
  636. */
  637. destination: function(latlng, heading, distance) {
  638. heading = (heading + 360) % 360;
  639. var rad = Math.PI / 180,
  640. radInv = 180 / Math.PI,
  641. R = 6378137, // approximation of Earth's radius
  642. lon1 = latlng.lng * rad,
  643. lat1 = latlng.lat * rad,
  644. rheading = heading * rad,
  645. sinLat1 = Math.sin(lat1),
  646. cosLat1 = Math.cos(lat1),
  647. cosDistR = Math.cos(distance / R),
  648. sinDistR = Math.sin(distance / R),
  649. lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 *
  650. sinDistR * Math.cos(rheading)),
  651. lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR *
  652. cosLat1, cosDistR - sinLat1 * Math.sin(lat2));
  653. lon2 = lon2 * radInv;
  654. lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2;
  655. return L.latLng([lat2 * radInv, lon2]);
  656. },
  657. /**
  658. Returns the the angle of the given segment and the Equator in degrees,
  659. clockwise from 0 degrees north.
  660. @param {L.Map} map: Leaflet map to be used for this method
  661. @param {L.LatLng} latlngA: geographical point A of the segment
  662. @param {L.LatLng} latlngB: geographical point B of the segment
  663. @returns {Float} the angle in degrees.
  664. */
  665. angle: function(map, latlngA, latlngB) {
  666. var pointA = map.latLngToContainerPoint(latlngA),
  667. pointB = map.latLngToContainerPoint(latlngB),
  668. angleDeg = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI + 90;
  669. angleDeg += angleDeg < 0 ? 360 : 0;
  670. return angleDeg;
  671. },
  672. /**
  673. Returns a point snaps on the segment and heading away from the given origin point a distance.
  674. @param {L.Map} map: Leaflet map to be used for this method
  675. @param {L.LatLng} latlngA: geographical point A of the segment
  676. @param {L.LatLng} latlngB: geographical point B of the segment
  677. @param {float} distance: distance in meters
  678. @returns {L.latLng} the destination point.
  679. */
  680. destinationOnSegment: function(map, latlngA, latlngB, distance) {
  681. var angleDeg = L.GeometryUtil.angle(map, latlngA, latlngB),
  682. latlng = L.GeometryUtil.destination(latlngA, angleDeg, distance);
  683. return L.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB);
  684. },
  685. });
  686. return L.GeometryUtil;
  687. }));