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