UNPKG

26.7 kBJavaScriptView Raw
1/**
2* A small library to provide some basic geo functions like distance calculation,
3* conversion of decimal coordinates to sexagesimal and vice versa, etc.
4* WGS 84 (World Geodetic System 1984)
5*
6* @author Manuel Bieh
7* @url http://www.manuelbieh.com/
8* @version 1.2.3
9* @license LGPL
10**/
11
12/*global geolib:true require:true module:true window:true*/
13(function (window, undefined) {
14
15 var radius = 6378137; // Earth radius
16 var sexagesimalPattern = /^([0-9]{1,3})°\s*([0-9]{1,3})'\s*(([0-9]{1,3}(\.([0-9]{1,2}))?)"\s*)?([NEOSW]?)$/;
17
18 var geolib = {
19
20 decimal: {},
21
22 sexagesimal: {},
23
24 distance: 0,
25
26 /**
27 * Get the key names for a geo point.
28 *
29 * @param object Point position {latitude: 123, longitude: 123, elevation: 123}
30 * @return object {
31 * longitude: 'lng|long|longitude',
32 * latitude: 'lat|latitude',
33 * elevation: 'alt|altitude|elev|elevation'
34 * }
35 */
36 getKeys: function(point) {
37
38 var latitude = point.hasOwnProperty('lat') ? 'lat' : 'latitude';
39
40 var longitude = (point.hasOwnProperty('lng') ? 'lng' : false) ||
41 (point.hasOwnProperty('long') ? 'long' : false) ||
42 'longitude';
43
44 var elevation = (point.hasOwnProperty('alt') ? 'alt' : false) ||
45 (point.hasOwnProperty('altitude') ? 'altitude' : false) ||
46 (point.hasOwnProperty('elev') ? 'elev' : false) ||
47 'elevation';
48
49 return {
50 latitude: latitude,
51 longitude: longitude,
52 elevation: elevation
53 };
54
55 },
56
57 /**
58 * Calculates geodetic distance between two points specified by latitude/longitude using
59 * Vincenty inverse formula for ellipsoids
60 * Vincenty Inverse Solution of Geodesics on the Ellipsoid (c) Chris Veness 2002-2010
61 * (Licensed under CC BY 3.0)
62 *
63 * @param object Start position {latitude: 123, longitude: 123}
64 * @param object End position {latitude: 123, longitude: 123}
65 * @param integer Accuracy (in meters)
66 * @return integer Distance (in meters)
67 */
68
69 getDistance: function(start, end, accuracy) {
70
71 var keys = geolib.getKeys(start);
72 var latitude = keys.latitude;
73 var longitude = keys.longitude;
74 var elevation = keys.elevation;
75
76 accuracy = Math.floor(accuracy) || 1;
77
78 var coord1 = {}, coord2 = {};
79 coord1[latitude] = geolib.useDecimal(start[latitude]);
80 coord1[longitude] = geolib.useDecimal(start[longitude]);
81
82 coord2[latitude] = geolib.useDecimal(end[latitude]);
83 coord2[longitude] = geolib.useDecimal(end[longitude]);
84
85 var a = 6378137, b = 6356752.314245, f = 1/298.257223563; // WGS-84 ellipsoid params
86 var L = (coord2[longitude]-coord1[longitude]).toRad();
87
88 var cosSigma, sigma, sinAlpha, cosSqAlpha, cos2SigmaM, sinSigma;
89
90 var U1 = Math.atan((1-f) * Math.tan(parseFloat(coord1[latitude]).toRad()));
91 var U2 = Math.atan((1-f) * Math.tan(parseFloat(coord2[latitude]).toRad()));
92 var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
93 var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
94
95 var lambda = L, lambdaP, iterLimit = 100;
96 do {
97 var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
98 sinSigma = (
99 Math.sqrt(
100 (
101 cosU2 * sinLambda
102 ) * (
103 cosU2 * sinLambda
104 ) + (
105 cosU1 * sinU2 - sinU1 * cosU2 * cosLambda
106 ) * (
107 cosU1 * sinU2 - sinU1 * cosU2 * cosLambda
108 )
109 )
110 );
111 if (sinSigma === 0) {
112 return geolib.distance = 0; // co-incident points
113 }
114
115 cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
116 sigma = Math.atan2(sinSigma, cosSigma);
117 sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
118 cosSqAlpha = 1 - sinAlpha * sinAlpha;
119 cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
120
121 if (isNaN(cos2SigmaM)) {
122 cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6)
123 }
124 var C = (
125 f / 16 * cosSqAlpha * (
126 4 + f * (
127 4 - 3 * cosSqAlpha
128 )
129 )
130 );
131 lambdaP = lambda;
132 lambda = (
133 L + (
134 1 - C
135 ) * f * sinAlpha * (
136 sigma + C * sinSigma * (
137 cos2SigmaM + C * cosSigma * (
138 -1 + 2 * cos2SigmaM * cos2SigmaM
139 )
140 )
141 )
142 );
143
144 } while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0);
145
146 if (iterLimit === 0) {
147 return NaN; // formula failed to converge
148 }
149
150 var uSq = (
151 cosSqAlpha * (
152 a * a - b * b
153 ) / (
154 b*b
155 )
156 );
157
158 var A = (
159 1 + uSq / 16384 * (
160 4096 + uSq * (
161 -768 + uSq * (
162 320 - 175 * uSq
163 )
164 )
165 )
166 );
167
168 var B = (
169 uSq / 1024 * (
170 256 + uSq * (
171 -128 + uSq * (
172 74-47 * uSq
173 )
174 )
175 )
176 );
177
178 var deltaSigma = (
179 B * sinSigma * (
180 cos2SigmaM + B / 4 * (
181 cosSigma * (
182 -1 + 2 * cos2SigmaM * cos2SigmaM
183 ) -B / 6 * cos2SigmaM * (
184 -3 + 4 * sinSigma * sinSigma
185 ) * (
186 -3 + 4 * cos2SigmaM * cos2SigmaM
187 )
188 )
189 )
190 );
191
192 var distance = b * A * (sigma - deltaSigma);
193
194 distance = distance.toFixed(3); // round to 1mm precision
195
196 if (start.hasOwnProperty(elevation) && end.hasOwnProperty(elevation)) {
197 var climb = Math.abs(start[elevation] - end[elevation]);
198 distance = Math.sqrt(distance*distance + climb*climb);
199 }
200
201 return geolib.distance = Math.floor(Math.round(distance/accuracy)*accuracy);
202
203 /*
204 // note: to return initial/final bearings in addition to distance, use something like:
205 var fwdAz = Math.atan2(cosU2*sinLambda, cosU1*sinU2-sinU1*cosU2*cosLambda);
206 var revAz = Math.atan2(cosU1*sinLambda, -sinU1*cosU2+cosU1*sinU2*cosLambda);
207
208 return { distance: s, initialBearing: fwdAz.toDeg(), finalBearing: revAz.toDeg() };
209 */
210
211 },
212
213
214 /**
215 * Calculates the distance between two spots.
216 * This method is more simple but also more inaccurate
217 *
218 * @param object Start position {latitude: 123, longitude: 123}
219 * @param object End position {latitude: 123, longitude: 123}
220 * @param integer Accuracy (in meters)
221 * @return integer Distance (in meters)
222 */
223 getDistanceSimple: function(start, end, accuracy) {
224
225 var keys = geolib.getKeys(start);
226 var latitude = keys.latitude;
227 var longitude = keys.longitude;
228
229 accuracy = Math.floor(accuracy) || 1;
230
231 var coord1 = {}, coord2 = {};
232 coord1[latitude] = parseFloat(geolib.useDecimal(start[latitude])).toRad();
233 coord1[longitude] = parseFloat(geolib.useDecimal(start[longitude])).toRad();
234
235 coord2[latitude] = parseFloat(geolib.useDecimal(end[latitude])).toRad();
236 coord2[longitude] = parseFloat(geolib.useDecimal(end[longitude])).toRad();
237
238 var distance =
239 Math.round(
240 Math.acos(
241 Math.sin(
242 coord2[latitude]
243 ) *
244 Math.sin(
245 coord1[latitude]
246 ) +
247 Math.cos(
248 coord2[latitude]
249 ) *
250 Math.cos(
251 coord1[latitude]
252 ) *
253 Math.cos(
254 coord1[longitude] - coord2[longitude]
255 )
256 ) * radius
257 );
258
259 return geolib.distance = Math.floor(Math.round(distance/accuracy)*accuracy);
260
261 },
262
263
264 /**
265 * Calculates the center of a collection of geo coordinates
266 *
267 * @param array Collection of coords [{latitude: 51.510, longitude: 7.1321}, {latitude: 49.1238, longitude: "8° 30' W"}, ...]
268 * @return object {latitude: centerLat, longitude: centerLng, distance: diagonalDistance}
269 */
270 getCenter: function(coords) {
271
272 if (!coords.length) {
273 return false;
274 }
275
276 var keys = geolib.getKeys(coords[0]);
277 var latitude = keys.latitude;
278 var longitude = keys.longitude;
279
280 var max = function( array ){
281 return Math.max.apply( Math, array );
282 };
283
284 var min = function( array ){
285 return Math.min.apply( Math, array );
286 };
287
288 var lat, lng, splitCoords = {lat: [], lng: []};
289
290 for(var coord in coords) {
291 splitCoords.lat.push(geolib.useDecimal(coords[coord][latitude]));
292 splitCoords.lng.push(geolib.useDecimal(coords[coord][longitude]));
293 }
294
295 var minLat = min(splitCoords.lat);
296 var minLng = min(splitCoords.lng);
297 var maxLat = max(splitCoords.lat);
298 var maxLng = max(splitCoords.lng);
299
300 lat = ((minLat + maxLat)/2).toFixed(6);
301 lng = ((minLng + maxLng)/2).toFixed(6);
302
303 // distance from the deepest left to the highest right point (diagonal distance)
304 var distance = geolib.convertUnit('km', geolib.getDistance({lat:minLat, lng:minLng}, {lat:maxLat, lng:maxLng}));
305
306 return {"latitude": lat, "longitude": lng, "distance": distance};
307
308 },
309
310 /**
311 * Gets the max and min, latitude, longitude, and elevation (if provided).
312 * @param array array with coords e.g. [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...]
313 * @return object {maxLat: maxLat,
314 * minLat: minLat
315 * maxLng: maxLng,
316 * minLng: minLng,
317 * maxElev: maxElev,
318 * minElev: minElev}
319 */
320 getBounds: function(coords) {
321 if (!coords.length) {
322 return false;
323 }
324
325 var keys = geolib.getKeys(coords[0]);
326 var latitude = keys.latitude;
327 var longitude = keys.longitude;
328 var elevation = keys.elevation;
329
330 var useElevation = coords[0].hasOwnProperty(elevation);
331 var stats = {
332 maxLat: 0,
333 minLat: Infinity,
334 maxLng: 0,
335 minLng: Infinity
336 };
337
338 if (useElevation) {
339 stats.maxElev = 0;
340 stats.minElev = Infinity;
341 }
342
343 for (var i = 0, l = coords.length; i < l; ++i) {
344 stats.maxLat = Math.max(coords[i][latitude], stats.maxLat);
345 stats.minLat = Math.min(coords[i][latitude], stats.minLat);
346 stats.maxLng = Math.max(coords[i][longitude], stats.maxLng);
347 stats.minLng = Math.min(coords[i][longitude], stats.minLng);
348 if (useElevation) {
349 stats.maxElev = Math.max(coords[i][elevation], stats.maxElev);
350 stats.minElev = Math.min(coords[i][elevation], stats.minElev);
351 }
352 }
353 return stats;
354 },
355
356 /**
357 * Checks whether a point is inside of a polygon or not.
358 * Note that the polygon coords must be in correct order!
359 *
360 * @param object coordinate to check e.g. {latitude: 51.5023, longitude: 7.3815}
361 * @param array array with coords e.g. [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...]
362 * @return bool true if the coordinate is inside the given polygon
363 */
364 isPointInside: function(latlng, coords) {
365
366 var keys = geolib.getKeys(latlng);
367 var latitude = keys.latitude;
368 var longitude = keys.longitude;
369
370 for(var c = false, i = -1, l = coords.length, j = l - 1; ++i < l; j = i) {
371
372 if(
373 (
374 (coords[i][longitude] <= latlng[longitude] && latlng[longitude] < coords[j][longitude]) ||
375 (coords[j][longitude] <= latlng[longitude] && latlng[longitude] < coords[i][longitude])
376 ) &&
377 (
378 latlng[latitude] < (coords[j][latitude] - coords[i][latitude]) *
379 (latlng[longitude] - coords[i][longitude]) /
380 (coords[j][longitude] - coords[i][longitude]) +
381 coords[i][latitude]
382 )
383 ) {
384 c = !c;
385 }
386
387 }
388
389 return c;
390
391 },
392
393
394 /**
395 * Checks whether a point is inside of a circle or not.
396 *
397 * @param object coordinate to check (e.g. {latitude: 51.5023, longitude: 7.3815})
398 * @param object coordinate of the circle's center (e.g. {latitude: 51.4812, longitude: 7.4025})
399 * @param integer maximum radius in meters
400 * @return bool true if the coordinate is inside the given radius
401 */
402 isPointInCircle: function(latlng, center, radius) {
403
404 return geolib.getDistance(latlng, center) < radius;
405
406 },
407
408
409 /**
410 * Gets rhumb line bearing of two points. Find out about the difference between rhumb line and
411 * great circle bearing on Wikipedia. It's quite complicated. Rhumb line should be fine in most cases:
412 *
413 * http://en.wikipedia.org/wiki/Rhumb_line#General_and_mathematical_description
414 *
415 * Function heavily based on Doug Vanderweide's great PHP version (licensed under GPL 3.0)
416 * http://www.dougv.com/2009/07/13/calculating-the-bearing-and-compass-rose-direction-between-two-latitude-longitude-coordinates-in-php/
417 *
418 * @param object origin coordinate (e.g. {latitude: 51.5023, longitude: 7.3815})
419 * @param object destination coordinate
420 * @return integer calculated bearing
421 */
422 getRhumbLineBearing: function(originLL, destLL) {
423
424 var keys = geolib.getKeys(originLL);
425 var latitude = keys.latitude;
426 var longitude = keys.longitude;
427
428 // difference of longitude coords
429 var diffLon = geolib.useDecimal(destLL[longitude]).toRad() - geolib.useDecimal(originLL[longitude]).toRad();
430
431 // difference latitude coords phi
432 var diffPhi = Math.log(Math.tan(geolib.useDecimal(destLL[latitude]).toRad() / 2 + Math.PI / 4) / Math.tan(geolib.useDecimal(originLL[latitude]).toRad() / 2 + Math.PI / 4));
433
434 // recalculate diffLon if it is greater than pi
435 if(Math.abs(diffLon) > Math.PI) {
436 if(diffLon > 0) {
437 diffLon = (2 * Math.PI - diffLon) * -1;
438 }
439 else {
440 diffLon = 2 * Math.PI + diffLon;
441 }
442 }
443
444 //return the angle, normalized
445 return (Math.atan2(diffLon, diffPhi).toDeg() + 360) % 360;
446
447 },
448
449
450 /**
451 * Gets great circle bearing of two points. See description of getRhumbLineBearing for more information
452 *
453 * @param object origin coordinate (e.g. {latitude: 51.5023, longitude: 7.3815})
454 * @param object destination coordinate
455 * @return integer calculated bearing
456 */
457 getBearing: function(originLL, destLL) {
458
459 var keys = geolib.getKeys(originLL);
460 var latitude = keys.latitude;
461 var longitude = keys.longitude;
462
463 destLL[latitude] = geolib.useDecimal(destLL[latitude]);
464 destLL[longitude] = geolib.useDecimal(destLL[longitude]);
465 originLL[latitude] = geolib.useDecimal(originLL[latitude]);
466 originLL[longitude] = geolib.useDecimal(originLL[longitude]);
467
468 var bearing = (
469 (
470 Math.atan2(
471 Math.sin(
472 destLL[longitude].toRad() -
473 originLL[longitude].toRad()
474 ) *
475 Math.cos(
476 destLL[latitude].toRad()
477 ),
478 Math.cos(
479 originLL[latitude].toRad()
480 ) *
481 Math.sin(
482 destLL[latitude].toRad()
483 ) -
484 Math.sin(
485 originLL[latitude].toRad()
486 ) *
487 Math.cos(
488 destLL[latitude].toRad()
489 ) *
490 Math.cos(
491 destLL[longitude].toRad() - originLL[longitude].toRad()
492 )
493 )
494 ).toDeg() + 360
495 ) % 360;
496
497 return bearing;
498
499 },
500
501
502 /**
503 * Gets the compass direction from an origin coordinate to a destination coordinate.
504 *
505 * @param object origin coordinate (e.g. {latitude: 51.5023, longitude: 7.3815})
506 * @param object destination coordinate
507 * @param string Bearing mode. Can be either circle or rhumbline
508 * @return object Returns an object with a rough (NESW) and an exact direction (NNE, NE, ENE, E, ESE, etc).
509 */
510 getCompassDirection: function(originLL, destLL, bearingMode) {
511
512 var direction, bearing;
513 if(bearingMode == 'circle') { // use great circle bearing
514 bearing = geolib.getBearing(originLL, destLL);
515 } else { // default is rhumb line bearing
516 bearing = geolib.getRhumbLineBearing(originLL, destLL);
517 }
518
519 switch(Math.round(bearing/22.5)) {
520 case 1:
521 direction = {exact: "NNE", rough: "N"};
522 break;
523 case 2:
524 direction = {exact: "NE", rough: "N"};
525 break;
526 case 3:
527 direction = {exact: "ENE", rough: "E"};
528 break;
529 case 4:
530 direction = {exact: "E", rough: "E"};
531 break;
532 case 5:
533 direction = {exact: "ESE", rough: "E"};
534 break;
535 case 6:
536 direction = {exact: "SE", rough: "E"};
537 break;
538 case 7:
539 direction = {exact: "SSE", rough: "S"};
540 break;
541 case 8:
542 direction = {exact: "S", rough: "S"};
543 break;
544 case 9:
545 direction = {exact: "SSW", rough: "S"};
546 break;
547 case 10:
548 direction = {exact: "SW", rough: "S"};
549 break;
550 case 11:
551 direction = {exact: "WSW", rough: "W"};
552 break;
553 case 12:
554 direction = {exact: "W", rough: "W"};
555 break;
556 case 13:
557 direction = {exact: "WNW", rough: "W"};
558 break;
559 case 14:
560 direction = {exact: "NW", rough: "W"};
561 break;
562 case 15:
563 direction = {exact: "NNW", rough: "N"};
564 break;
565 default:
566 direction = {exact: "N", rough: "N"};
567 }
568
569 return direction;
570
571 },
572
573
574 /**
575 * Sorts an array of coords by distance from a reference coordinate
576 *
577 * @param object reference coordinate e.g. {latitude: 51.5023, longitude: 7.3815}
578 * @param mixed array or object with coords [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...]
579 * @return array ordered array
580 */
581 orderByDistance: function(latlng, coords) {
582
583 var keys = geolib.getKeys(latlng);
584 var latitude = keys.latitude;
585 var longitude = keys.longitude;
586
587 var coordsArray = [];
588 for(var coord in coords) {
589 var d = geolib.getDistance(latlng, coords[coord]);
590 coordsArray.push({key: coord, latitude: coords[coord][latitude], longitude: coords[coord][longitude], distance: d});
591 }
592
593 return coordsArray.sort(function(a, b) { return a.distance - b.distance; });
594
595 },
596
597
598 /**
599 * Finds the nearest coordinate to a reference coordinate
600 *
601 * @param object reference coordinate e.g. {latitude: 51.5023, longitude: 7.3815}
602 * @param mixed array or object with coords [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...]
603 * @return array ordered array
604 */
605 findNearest: function(latlng, coords, offset) {
606
607 offset = offset || 0;
608 var ordered = geolib.orderByDistance(latlng, coords);
609 return ordered[offset];
610
611 },
612
613
614 /**
615 * Calculates the length of a given path
616 *
617 * @param mixed array or object with coords [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...]
618 * @return integer length of the path (in meters)
619 */
620 getPathLength: function(coords) {
621
622 var dist = 0, last;
623 for (var i = 0, l = coords.length; i < l; ++i) {
624 if(last) {
625 dist += geolib.getDistance(coords[i], last);
626 }
627 last = coords[i];
628 }
629
630 return dist;
631
632 },
633
634 /**
635 * Converts a distance from meters to km, mm, cm, mi, ft, in or yd
636 *
637 * @param string Format to be converted in
638 * @param float Distance in meters
639 * @param float Decimal places for rounding (default: 4)
640 * @return float Converted distance
641 */
642 convertUnit: function(unit, distance, round) {
643
644 if(distance === 0 || typeof distance == 'undefined') {
645
646 if(geolib.distance === 0) {
647 // throw 'No distance given.';
648 return 0;
649 } else {
650 distance = geolib.distance;
651 }
652
653 }
654
655 unit = unit || 'm';
656 round = (null == round ? 4 : round);
657
658 switch(unit) {
659
660 case 'm': // Meter
661 return geolib.round(distance, round);
662 case 'km': // Kilometer
663 return geolib.round(distance / 1000, round);
664 case 'cm': // Centimeter
665 return geolib.round(distance * 100, round);
666 case 'mm': // Millimeter
667 return geolib.round(distance * 1000, round);
668 case 'mi': // Miles
669 return geolib.round(distance * (1 / 1609.344), round);
670 case 'sm': // Seamiles
671 return geolib.round(distance * (1 / 1852.216), round);
672 case 'ft': // Feet
673 return geolib.round(distance * (100 / 30.48), round);
674 case 'in': // Inch
675 return geolib.round(distance * 100 / 2.54, round);
676 case 'yd': // Yards
677 return geolib.round(distance * (1 / 0.9144), round);
678 }
679
680 return distance;
681
682 },
683
684
685 /**
686 * Checks if a value is in decimal format or, if neccessary, converts to decimal
687 *
688 * @param mixed Value to be checked/converted
689 * @return float Coordinate in decimal format
690 */
691 useDecimal: function(value) {
692
693 value = value.toString().replace(/\s*/, '');
694
695 // looks silly but works as expected
696 // checks if value is in decimal format
697 if (!isNaN(parseFloat(value)) && parseFloat(value).toString() == value) {
698 return parseFloat(value);
699 // checks if it's sexagesimal format (HHH° MM' SS" (NESW))
700 } else if(geolib.isSexagesimal(value) === true) {
701 return parseFloat(geolib.sexagesimal2decimal(value));
702 } else {
703 throw 'Unknown format.';
704 }
705
706 },
707
708
709 /**
710 * Converts a decimal coordinate value to sexagesimal format
711 *
712 * @param float decimal
713 * @return string Sexagesimal value (XX° YY' ZZ")
714 */
715 decimal2sexagesimal: function(dec) {
716
717 if (dec in geolib.sexagesimal) {
718 return geolib.sexagesimal[dec];
719 }
720
721 var tmp = dec.toString().split('.');
722
723 var deg = Math.abs(tmp[0]);
724 var min = ('0.' + tmp[1])*60;
725 var sec = min.toString().split('.');
726
727 min = Math.floor(min);
728 sec = (('0.' + sec[1]) * 60).toFixed(2);
729
730 geolib.sexagesimal[dec] = (deg + '° ' + min + "' " + sec + '"');
731
732 return geolib.sexagesimal[dec];
733
734 },
735
736
737 /**
738 * Converts a sexagesimal coordinate to decimal format
739 *
740 * @param float Sexagesimal coordinate
741 * @return string Decimal value (XX.XXXXXXXX)
742 */
743 sexagesimal2decimal: function(sexagesimal) {
744
745 if (sexagesimal in geolib.decimal) {
746 return geolib.decimal[sexagesimal];
747 }
748
749 var regEx = new RegExp(sexagesimalPattern);
750 var data = regEx.exec(sexagesimal);
751 var min = 0, sec = 0;
752
753 if(data) {
754 min = parseFloat(data[2]/60);
755 sec = parseFloat(data[4]/3600) || 0;
756 }
757
758 var dec = ((parseFloat(data[1]) + min + sec)).toFixed(8);
759 // South and West are negative decimals
760 dec = (data[7] == 'S' || data[7] == 'W') ? dec * -1 : dec;
761
762 geolib.decimal[sexagesimal] = dec;
763
764 return dec;
765
766 },
767
768
769 /**
770 * Checks if a value is in sexagesimal format
771 *
772 * @param string Value to be checked
773 * @return bool True if in sexagesimal format
774 */
775 isSexagesimal: function(value) {
776
777 return sexagesimalPattern.test(value);
778
779 },
780
781 round: function(value, n) {
782 var decPlace = Math.pow(10, n);
783 return Math.round(value * decPlace)/decPlace;
784 }
785
786 };
787
788 if (typeof(Number.prototype.toRad) === "undefined") {
789 Number.prototype.toRad = function() {
790 return this * Math.PI / 180;
791 };
792 }
793
794 if (typeof(Number.prototype.toDeg) === "undefined") {
795 Number.prototype.toDeg = function() {
796 return this * 180 / Math.PI;
797 };
798 }
799
800 // we're in a browser
801 window.geolib = geolib;
802 if (typeof module != 'undefined') {
803 module.exports = geolib;
804 }
805
806}(this));
807(function(global) {
808
809 var geolib = global.geolib;
810
811 /* Optional elevation addon requires Googlemaps API JS */
812
813 /*global google:true geolib:true require:true module:true elevationResult*/
814 /**
815 * @param Array Collection of coords [{latitude: 51.510, longitude: 7.1321}, {latitude: 49.1238, longitude: "8° 30' W"}, ...]
816 *
817 * @return Array [{lat:#lat, lng:#lng, elev:#elev},....]}
818 */
819 geolib.getElevation = function() {
820 if (typeof window.navigator !== 'undefined') {
821 geolib.getElevationClient.apply(this, arguments);
822 } else {
823 geolib.getElevationServer.apply(this, arguments);
824 }
825 };
826
827 geolib.getElevationClient = function(coords, cb) {
828
829 if (!window.google) {
830 throw new Error("Google maps api not loaded");
831 }
832
833 if (coords.length === 0) {
834 return cb(null, null);
835 }
836
837 if (coords.length === 1) {
838 return cb(new Error("getElevation requires at least 2 points."));
839 }
840
841 var path = [];
842 var keys = geolib.getKeys(coords[0]);
843 var latitude = keys.latitude;
844 var longitude = keys.longitude;
845
846 for(var i = 0; i < coords.length; i++) {
847 path.push(new google.maps.LatLng(
848 geolib.useDecimal(coords[i][latitude]),
849 geolib.useDecimal(coords[i][longitude])
850 ));
851 }
852
853 var positionalRequest = {
854 'path': path,
855 'samples': path.length
856 };
857 var elevationService = new google.maps.ElevationService();
858 elevationService.getElevationAlongPath(positionalRequest,function (results, status) {
859 geolib.elevationHandler(results, status, coords, keys, cb);
860 });
861
862 };
863
864 geolib.getElevationServer = function(coords, cb) {
865
866 if (coords.length === 0) {
867 return cb(null, null);
868 }
869
870 if (coords.length === 1) {
871 return cb(new Error("getElevation requires at least 2 points."));
872 }
873
874 var gm = require('googlemaps');
875 var path = [];
876 var keys = geolib.getKeys(coords[0]);
877 //coords[0]
878 var latitude = keys.latitude;
879 var longitude = keys.longitude;
880
881 for(var i = 0; i < coords.length; i++) {
882 path.push(geolib.useDecimal(coords[i][latitude]) + ',' +
883 geolib.useDecimal(coords[i][longitude]));
884 }
885
886 gm.elevationFromPath(path.join('|'), path.length, function(err, results) {
887 geolib.elevationHandler(results.results, results.status, coords, keys, cb);
888 });
889
890 },
891
892 geolib.elevationHandler = function(results, status, coords, keys, cb){
893 var latsLngsElevs = [];
894 var latitude = keys.latitude;
895 var longitude = keys.longitude;
896 if (status == "OK" ) {
897 for (var i = 0; i < results.length; i++) {
898 latsLngsElevs.push({
899 "lat":coords[i][latitude],
900 "lng":coords[i][longitude],
901 "elev":results[i].elevation
902 });
903 }
904 cb(null, latsLngsElevs);
905 } else {
906 cb(new Error("Could not get elevation using Google's API"), elevationResult.status);
907 }
908 };
909
910 /**
911 * @param Array [{lat:#lat, lng:#lng, elev:#elev},....]}
912 *
913 * @return Number % grade
914 */
915 geolib.getGrade = function(coords){
916 var keys = geolib.getKeys(coords[0]);
917 var elevation = keys.elevation;
918 var rise = Math.abs(coords[coords.length-1][elevation] - coords[0][elevation]);
919 var run = geolib.getPathLength(coords);
920 return Math.floor((rise/run)*100);
921 };
922
923 /**
924 * @param Array [{lat:#lat, lng:#lng, elev:#elev},....]}
925 *
926 * @return Object {gain:#gain, loss:#loss}
927 */
928 geolib.getTotalElevationGainAndLoss = function(coords){
929 var keys = geolib.getKeys(coords[0]);
930 var elevation = keys.elevation;
931 var gain = 0;
932 var loss = 0;
933 for(var i = 0; i < coords.length - 1; i++){
934 var deltaElev = coords[i][elevation] - coords[i + 1][elevation];
935 if (deltaElev > 0) {
936 loss += deltaElev;
937 } else {
938 gain += Math.abs(deltaElev);
939 }
940 }
941 return {
942 "gain": gain,
943 "loss": loss
944 };
945 };
946
947})(this);
\No newline at end of file