/*! geolib 2.0.7 by Manuel Bieh
* Library to provide geo functions like distance calculation,
* conversion of decimal coordinates to sexagesimal and vice versa, etc.
* WGS 84 (World Geodetic System 1984)
* 
* @author Manuel Bieh
* @url http://www.manuelbieh.com/
* @version 2.0.7
* @license MIT 
**/;(function(global, undefined) {

	"use strict";

	function Geolib() {}

	// Setting readonly defaults
	var geolib = Object.create(Geolib.prototype, {
		version: {
			value: "2.0.7"
		},
		radius: {
			value: 6378137
		},
		minLat: {
			value: -90
		},
		maxLat: {
			value: 90
		},
		minLon: {
			value: -180
		},
		maxLon: {
			value: 180
		},
		sexagesimalPattern: {
			value: /^([0-9]{1,3})°\s*([0-9]{1,3}(?:\.(?:[0-9]{1,2}))?)'\s*(([0-9]{1,3}(\.([0-9]{1,2}))?)"\s*)?([NEOSW]?)$/
		},
		measures: {
			value: Object.create(Object.prototype, {
				"m" : {value: 1},
				"km": {value: 0.001},
				"cm": {value: 100},
				"mm": {value: 1000},
				"mi": {value: (1 / 1609.344)},
				"sm": {value: (1 / 1852.216)},
				"ft": {value: (100 / 30.48)},
				"in": {value: (100 / 2.54)},
				"yd": {value: (1 / 0.9144)}
			})
		},
		prototype: {
			value: Geolib.prototype
		},
		extend: {
			value: function(methods, overwrite) {
				for(var prop in methods) {
					if(typeof geolib.prototype[prop] === 'undefined' || overwrite === true) {
						geolib.prototype[prop] = methods[prop];
					}
				}
			}
		}
	});

	if (typeof(Number.prototype.toRad) === "undefined") {
		Number.prototype.toRad = function() {
			return this * Math.PI / 180;
		};
	}

	if (typeof(Number.prototype.toDeg) === "undefined") {
		Number.prototype.toDeg = function() {
			return this * 180 / Math.PI;
		};
	}

	// Here comes the magic
	geolib.extend({

		decimal: {},

		sexagesimal: {},

		distance: null,

		getKeys: function(point) {

			// GeoJSON Array [longitude, latitude(, elevation)]
			if(Object.prototype.toString.call(point) == '[object Array]') {

				return {
					longitude: point.length >= 1 ? 0 : undefined,
					latitude: point.length >= 2 ? 1 : undefined,
					elevation: point.length >= 3 ? 2 : undefined
				};

			}

			var getKey = function(possibleValues) {

				var key;

				possibleValues.every(function(val) {
					// TODO: check if point is an object
					if(typeof point != 'object') {
						return true;
					}
					return point.hasOwnProperty(val) ? (function() { key = val; return false; }()) : true;
				});

				return key;

			};

			var longitude = getKey(['lng', 'lon', 'longitude']);
			var latitude = getKey(['lat', 'latitude']);
			var elevation = getKey(['alt', 'altitude', 'elevation', 'elev']);

			// return undefined if not at least one valid property was found
			if(typeof latitude == 'undefined' && 
				typeof longitude == 'undefined' && 
				typeof elevation == 'undefined') {
				return undefined;
			}

			return {
				latitude: latitude,
				longitude: longitude,
				elevation: elevation
			};

		},

		// returns latitude of a given point, converted to decimal
		// set raw to true to avoid conversion
		getLat: function(point, raw) {
			return raw === true ? point[this.getKeys(point).latitude] : this.useDecimal(point[this.getKeys(point).latitude]);
		},

		// Alias for getLat
		latitude: function(point) {
			return this.getLat.call(this, point);
		},

		// returns longitude of a given point, converted to decimal
		// set raw to true to avoid conversion
		getLon: function(point, raw) {
			return raw === true ? point[this.getKeys(point).longitude] : this.useDecimal(point[this.getKeys(point).longitude]);
		},

		// Alias for getLon
		longitude: function(point) {
			return this.getLon.call(this, point);
		},

		getElev: function(point) {
			return point[this.getKeys(point).elevation];
		},

		// Alias for getElev
		elevation: function(point) {
			return this.getElev.call(this, point);
		},

		coords: function(point, raw) {

			var retval = {
				latitude: raw === true ? point[this.getKeys(point).latitude] : this.useDecimal(point[this.getKeys(point).latitude]),
				longitude: raw === true ? point[this.getKeys(point).longitude] : this.useDecimal(point[this.getKeys(point).longitude])
			};

			var elev = point[this.getKeys(point).elevation];

			if(typeof elev !== 'undefined') {
				retval['elevation'] = elev;
			}

			return retval;

		},

		// checks if a variable contains a valid latlong object
		validate: function(point) {

			var keys = this.getKeys(point);

			if(typeof keys === 'undefined' || typeof keys.latitude === 'undefined' || keys.longitude === 'undefined') {
				return false;
			}

			var lat = point[keys.latitude];
			var lng = point[keys.longitude];

			if(typeof lat === 'undefined' || !this.isDecimal(lat) && !this.isSexagesimal(lat)) {
				return false;
			}

			if(typeof lng === 'undefined' || !this.isDecimal(lng) && !this.isSexagesimal(lng)) {
				return false;
			}

			lat = this.useDecimal(lat);
			lng = this.useDecimal(lng);

			if(lat < this.minLat || lat > this.maxLat || lng < this.minLon || lng > this.maxLon) {
				return false;
			}

			return true;

		},

		/**
		* Calculates geodetic distance between two points specified by latitude/longitude using 
		* Vincenty inverse formula for ellipsoids
		* Vincenty Inverse Solution of Geodesics on the Ellipsoid (c) Chris Veness 2002-2010
		* (Licensed under CC BY 3.0)
		*
		* @param    object    Start position {latitude: 123, longitude: 123}
		* @param    object    End position {latitude: 123, longitude: 123}
		* @param    integer   Accuracy (in meters)
		* @return   integer   Distance (in meters)
		*/
		getDistance: function(start, end, accuracy) {

			accuracy = Math.floor(accuracy) || 1;

			var s = this.coords(start);
			var e = this.coords(end);

			var a = 6378137, b = 6356752.314245,  f = 1/298.257223563;  // WGS-84 ellipsoid params
			var L = (e['longitude']-s['longitude']).toRad();

			var cosSigma, sigma, sinAlpha, cosSqAlpha, cos2SigmaM, sinSigma;

			var U1 = Math.atan((1-f) * Math.tan(parseFloat(s['latitude']).toRad()));
			var U2 = Math.atan((1-f) * Math.tan(parseFloat(e['latitude']).toRad()));
			var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
			var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);

			var lambda = L, lambdaP, iterLimit = 100;
			do {
				var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
				sinSigma = (
					Math.sqrt(
						(
							cosU2 * sinLambda
						) * (
							cosU2 * sinLambda
						) + (
							cosU1 * sinU2 - sinU1 * cosU2 * cosLambda
						) * (
							cosU1 * sinU2 - sinU1 * cosU2 * cosLambda
						)
					)
				);
				if (sinSigma === 0) {
					return geolib.distance = 0;  // co-incident points
				}

				cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
				sigma = Math.atan2(sinSigma, cosSigma);
				sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
				cosSqAlpha = 1 - sinAlpha * sinAlpha;
				cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;

				if (isNaN(cos2SigmaM)) {
					cos2SigmaM = 0;  // equatorial line: cosSqAlpha=0 (§6)
				}
				var C = (
					f / 16 * cosSqAlpha * (
						4 + f * (
							4 - 3 * cosSqAlpha
						)
					)
				);
				lambdaP = lambda;
				lambda = (
					L + (
						1 - C
					) * f * sinAlpha * (
						sigma + C * sinSigma * (
							cos2SigmaM + C * cosSigma * (
								-1 + 2 * cos2SigmaM * cos2SigmaM
							)
						)
					)
				);

			} while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0);

			if (iterLimit === 0) {
				return NaN;  // formula failed to converge
			}

			var uSq = (
				cosSqAlpha * (
					a * a - b * b
				) / (
					b*b
				)
			);

			var A = (
				1 + uSq / 16384 * (
					4096 + uSq * (
						-768 + uSq * (
							320 - 175 * uSq
						)
					)
				)
			);

			var B = (
				uSq / 1024 * (
					256 + uSq * (
						-128 + uSq * (
							74-47 * uSq
						)
					)
				)
			);

			var deltaSigma = (
				B * sinSigma * (
					cos2SigmaM + B / 4 * (
						cosSigma * (
							-1 + 2 * cos2SigmaM * cos2SigmaM
						) -B / 6 * cos2SigmaM * (
							-3 + 4 * sinSigma * sinSigma
						) * (
							-3 + 4 * cos2SigmaM * cos2SigmaM
						)
					)
				)
			);

			var distance = b * A * (sigma - deltaSigma);

			distance = distance.toFixed(3); // round to 1mm precision

			//if (start.hasOwnProperty(elevation) && end.hasOwnProperty(elevation)) {
			if (typeof this.elevation(start) !== 'undefined' && typeof this.elevation(end) !== 'undefined') {
				var climb = Math.abs(this.elevation(start) - this.elevation(end));
				distance = Math.sqrt(distance * distance + climb * climb);
			}

			return this.distance = Math.floor(
				Math.round(distance / accuracy) * accuracy
			);

			/*
			// note: to return initial/final bearings in addition to distance, use something like:
			var fwdAz = Math.atan2(cosU2*sinLambda,  cosU1*sinU2-sinU1*cosU2*cosLambda);
			var revAz = Math.atan2(cosU1*sinLambda, -sinU1*cosU2+cosU1*sinU2*cosLambda);

			return { distance: s, initialBearing: fwdAz.toDeg(), finalBearing: revAz.toDeg() };
			*/

		},


		/**
		* Calculates the distance between two spots. 
		* This method is more simple but also far more inaccurate
		*
		* @param    object    Start position {latitude: 123, longitude: 123}
		* @param    object    End position {latitude: 123, longitude: 123}
		* @param    integer   Accuracy (in meters)
		* @return   integer   Distance (in meters)
		*/
		getDistanceSimple: function(start, end, accuracy) {

			accuracy = Math.floor(accuracy) || 1;

			var distance = 
				Math.round(
					Math.acos(
						Math.sin(
							this.latitude(end).toRad()
						) * 
						Math.sin(
							this.latitude(start).toRad()
						) + 
						Math.cos(
							this.latitude(end).toRad()
						) * 
						Math.cos(
							this.latitude(start).toRad()
						) * 
						Math.cos(
							this.longitude(start).toRad() - this.longitude(end).toRad()
						) 
					) * this.radius
				);

			return geolib.distance = Math.floor(Math.round(distance/accuracy)*accuracy);

		},


		/**
		* Calculates the center of a collection of geo coordinates
		*
		* @param		array		Collection of coords [{latitude: 51.510, longitude: 7.1321}, {latitude: 49.1238, longitude: "8° 30' W"}, ...]
		* @return		object		{latitude: centerLat, longitude: centerLng, distance: diagonalDistance}
		*/
		getCenter: function(coords) {

			if (!coords.length) {
				return false;
			}

			var max = function( array ){
				return Math.max.apply( Math, array );
			};

			var min = function( array ){
				return Math.min.apply( Math, array );
			};

			var	latitude;
			var longitude;
			var splitCoords = {latitude: [], longitude: []};

			for(var coord in coords) {

				splitCoords.latitude.push(
					this.latitude(coords[coord])
				);

				splitCoords.longitude.push(
					this.longitude(coords[coord])
				);

			}

			var minLat = min(splitCoords.latitude);
			var minLon = min(splitCoords.longitude);
			var maxLat = max(splitCoords.latitude);
			var maxLon = max(splitCoords.longitude);

			latitude = ((minLat + maxLat)/2).toFixed(6);
			longitude = ((minLon + maxLon)/2).toFixed(6);

			// distance from the deepest left to the highest right point (diagonal distance)
			var distance = this.convertUnit('km', this.getDistance({latitude: minLat, longitude: minLon}, {latitude: maxLat, longitude: maxLon}));

			return {
				latitude: latitude, 
				longitude: longitude, 
				distance: distance
			};

		},



		/**
		* Gets the max and min, latitude, longitude, and elevation (if provided).
		* @param		array		array with coords e.g. [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...] 
		* @return	object		{maxLat: maxLat,
		*                     minLat: minLat		
		*                     maxLng: maxLng,
		*                     minLng: minLng,
		*                     maxElev: maxElev,
		*                     minElev: minElev}
		*/
		getBounds: function(coords) {

			if (!coords.length) {
				return false;
			}

			var useElevation = this.elevation(coords[0]);

			var stats = {
				maxLat: -Infinity,
				minLat: Infinity,
				maxLng: -Infinity,
				minLng: Infinity
			};

			if (typeof useElevation != 'undefined') {
				stats.maxElev = 0;
				stats.minElev = Infinity;
			}

			for (var i = 0, l = coords.length; i < l; ++i) {

				stats.maxLat = Math.max(this.latitude(coords[i]), stats.maxLat);
				stats.minLat = Math.min(this.latitude(coords[i]), stats.minLat);
				stats.maxLng = Math.max(this.longitude(coords[i]), stats.maxLng);
				stats.minLng = Math.min(this.longitude(coords[i]), stats.minLng);

				if (useElevation) {
					stats.maxElev = Math.max(this.elevation(coords[i]), stats.maxElev);
					stats.minElev = Math.min(this.elevation(coords[i]), stats.minElev);
				}

			}

			return stats;

		},


		/**
		* Computes the bounding coordinates of all points on the surface
		* of the earth less than or equal to the specified great circle
		* distance.
		*
		* @param object Point position {latitude: 123, longitude: 123}
		* @param number Distance (in meters).
		* @return array Collection of two points defining the SW and NE corners.
		*/
		getBoundsOfDistance: function(point, distance) {

			var latitude = this.latitude(point);
			var longitude = this.longitude(point);

			var radLat = latitude.toRad();
			var radLon = longitude.toRad();

			var radDist = distance / this.radius;
			var minLat = radLat - radDist;
			var maxLat = radLat + radDist;

			var MAX_LAT_RAD = this.maxLat.toRad();
			var MIN_LAT_RAD = this.minLat.toRad();
			var MAX_LON_RAD = this.maxLon.toRad();
			var MIN_LON_RAD = this.minLon.toRad();

			var minLon;
			var maxLon;

			if (minLat > MIN_LAT_RAD && maxLat < MAX_LAT_RAD) {

				var deltaLon = Math.asin(Math.sin(radDist) / Math.cos(radLat));
				minLon = radLon - deltaLon;

				if (minLon < MIN_LON_RAD) {
					minLon += 2 * Math.PI;
				}

				maxLon = radLon + deltaLon;

				if (maxLon > MAX_LON_RAD) {
					maxLon -= 2 * Math.PI;
				}

			} else {
				// A pole is within the distance.
				minLat = Math.max(minLat, MIN_LAT_RAD);
				maxLat = Math.min(maxLat, MAX_LAT_RAD);
				minLon = MIN_LON_RAD;
				maxLon = MAX_LON_RAD;
			}

			return [
				// Southwest
				{
					latitude: minLat.toDeg(), 
					longitude: minLon.toDeg()
				},
				// Northeast
				{
					latitude: maxLat.toDeg(), 
					longitude: maxLon.toDeg()
				}
			];

		},


		/**
		* Checks whether a point is inside of a polygon or not.
		* Note that the polygon coords must be in correct order!
		*
		* @param		object		coordinate to check e.g. {latitude: 51.5023, longitude: 7.3815}
		* @param		array		array with coords e.g. [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...] 
		* @return		bool		true if the coordinate is inside the given polygon
		*/
		isPointInside: function(latlng, coords) {

			for(var c = false, i = -1, l = coords.length, j = l - 1; ++i < l; j = i) {

				if(
					(
						(this.longitude(coords[i]) <= this.longitude(latlng) && this.longitude(latlng) < this.longitude(coords[j])) ||
						(this.longitude(coords[j]) <= this.longitude(latlng) && this.longitude(latlng) < this.longitude(coords[i]))
					) && 
					(
						this.latitude(latlng) < (this.latitude(coords[j]) - this.latitude(coords[i])) * 
						(this.longitude(latlng) - this.longitude(coords[i])) / 
						(this.longitude(coords[j]) - this.longitude(coords[i])) + 
						this.latitude(coords[i])
					)
				) {
					c = !c;
				}

			}

			return c;

		},


		/**
		* Shortcut for geolib.isPointInside()
		*/
		isInside: function() {
			return this.isPointInside.apply(this, arguments);
		},


		/**
		* Checks whether a point is inside of a circle or not.
		*
		* @param		object		coordinate to check (e.g. {latitude: 51.5023, longitude: 7.3815})
		* @param		object		coordinate of the circle's center (e.g. {latitude: 51.4812, longitude: 7.4025})
		* @param		integer		maximum radius in meters 
		* @return		bool		true if the coordinate is within the given radius
		*/
		isPointInCircle: function(latlng, center, radius) {
			return this.getDistance(latlng, center) < radius;
		},


		/**
		* Shortcut for geolib.isPointInCircle()
		*/
		withinRadius: function() {
			return this.isPointInCircle.apply(this, arguments);
		},


		/**
		* Gets rhumb line bearing of two points. Find out about the difference between rhumb line and 
		* great circle bearing on Wikipedia. It's quite complicated. Rhumb line should be fine in most cases:
		*
		* http://en.wikipedia.org/wiki/Rhumb_line#General_and_mathematical_description
		* 
		* Function heavily based on Doug Vanderweide's great PHP version (licensed under GPL 3.0)
		* http://www.dougv.com/2009/07/13/calculating-the-bearing-and-compass-rose-direction-between-two-latitude-longitude-coordinates-in-php/
		*
		* @param		object		origin coordinate (e.g. {latitude: 51.5023, longitude: 7.3815})
		* @param		object		destination coordinate
		* @return		integer		calculated bearing
		*/
		getRhumbLineBearing: function(originLL, destLL) {

			// difference of longitude coords
			var diffLon = this.longitude(destLL).toRad() - this.longitude(originLL).toRad();

			// difference latitude coords phi
			var diffPhi = Math.log(
				Math.tan(
					this.latitude(destLL).toRad() / 2 + Math.PI / 4
				) / 
				Math.tan(
					this.latitude(originLL).toRad() / 2 + Math.PI / 4
				)
			);

			// recalculate diffLon if it is greater than pi
			if(Math.abs(diffLon) > Math.PI) {
				if(diffLon > 0) {
					diffLon = (2 * Math.PI - diffLon) * -1;
				}
				else {
					diffLon = 2 * Math.PI + diffLon;
				}
			}

			//return the angle, normalized
			return (Math.atan2(diffLon, diffPhi).toDeg() + 360) % 360;

		},


		/**
		* Gets great circle bearing of two points. See description of getRhumbLineBearing for more information
		*
		* @param		object		origin coordinate (e.g. {latitude: 51.5023, longitude: 7.3815})
		* @param		object		destination coordinate
		* @return		integer		calculated bearing
		*/
		getBearing: function(originLL, destLL) {

			destLL['latitude'] = this.latitude(destLL);
			destLL['longitude'] = this.longitude(destLL);
			originLL['latitude'] = this.latitude(originLL);
			originLL['longitude'] = this.longitude(originLL);

			var bearing = (
				(
					Math.atan2(
						Math.sin(
							destLL['longitude'].toRad() - 
							originLL['longitude'].toRad()
						) * 
						Math.cos(
							destLL['latitude'].toRad()
						), 
						Math.cos(
							originLL['latitude'].toRad()
						) * 
						Math.sin(
							destLL['latitude'].toRad()
						) - 
						Math.sin(
							originLL['latitude'].toRad()
						) * 
						Math.cos(
							destLL['latitude'].toRad()
						) * 
						Math.cos(
							destLL['longitude'].toRad() - originLL['longitude'].toRad()
						)
					)
				).toDeg() + 360
			) % 360;

			return bearing;

		},


		/**
		* Gets the compass direction from an origin coordinate to a destination coordinate.
		*
		* @param		object		origin coordinate (e.g. {latitude: 51.5023, longitude: 7.3815})
		* @param		object		destination coordinate
		* @param		string		Bearing mode. Can be either circle or rhumbline
		* @return		object		Returns an object with a rough (NESW) and an exact direction (NNE, NE, ENE, E, ESE, etc).
		*/
		getCompassDirection: function(originLL, destLL, bearingMode) {

			var direction;
			var bearing;

			if(bearingMode == 'circle') { 
				// use great circle bearing
				bearing = this.getBearing(originLL, destLL);
			} else { 
				// default is rhumb line bearing
				bearing = this.getRhumbLineBearing(originLL, destLL);
			}

			switch(Math.round(bearing/22.5)) {
				case 1:
					direction = {exact: "NNE", rough: "N"};
					break;
				case 2:
					direction = {exact: "NE", rough: "N"};
					break;
				case 3:
					direction = {exact: "ENE", rough: "E"};
					break;
				case 4:
					direction = {exact: "E", rough: "E"};
					break;
				case 5:
					direction = {exact: "ESE", rough: "E"};
					break;
				case 6:
					direction = {exact: "SE", rough: "E"};
					break;
				case 7:
					direction = {exact: "SSE", rough: "S"};
					break;
				case 8:
					direction = {exact: "S", rough: "S"};
					break;
				case 9:
					direction = {exact: "SSW", rough: "S"};
					break;
				case 10:
					direction = {exact: "SW", rough: "S"};
					break;
				case 11:
					direction = {exact: "WSW", rough: "W"};
					break;
				case 12:
					direction = {exact: "W", rough: "W"};
					break;
				case 13:
					direction = {exact: "WNW", rough: "W"};
					break;
				case 14:
					direction = {exact: "NW", rough: "W"};
					break;
				case 15:
					direction = {exact: "NNW", rough: "N"}; 
					break;
				default:
					direction = {exact: "N", rough: "N"};
			}

			direction['bearing'] = bearing;
			return direction;

		},


		/**
		* Shortcut for getCompassDirection
		*/
		getDirection: function(originLL, destLL, bearingMode) {
			return this.getCompassDirection.apply(this, arguments);
		},


		/**
		* Sorts an array of coords by distance from a reference coordinate
		*
		* @param		object		reference coordinate e.g. {latitude: 51.5023, longitude: 7.3815}
		* @param		mixed		array or object with coords [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...] 
		* @return		array		ordered array
		*/
		orderByDistance: function(latlng, coords) {

			var coordsArray = [];

			for(var coord in coords) {

				var d = this.getDistance(latlng, coords[coord]);

				coordsArray.push({
					key: coord, 
					latitude: this.latitude(coords[coord]), 
					longitude: this.longitude(coords[coord]), 
					distance: d
				});

			}

			return coordsArray.sort(function(a, b) { return a.distance - b.distance; });

		},


		/**
		* Finds the nearest coordinate to a reference coordinate
		*
		* @param		object		reference coordinate e.g. {latitude: 51.5023, longitude: 7.3815}
		* @param		mixed		array or object with coords [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...] 
		* @return		array		ordered array
		*/
		findNearest: function(latlng, coords, offset, limit) {

			offset = offset || 0;
			limit = limit || 1;
			var ordered = this.orderByDistance(latlng, coords);

			if(limit === 1) {
				return ordered[offset];
			} else {
				return ordered.splice(offset, limit);
			}

		},


		/**
		* Calculates the length of a given path
		*
		* @param		mixed		array or object with coords [{latitude: 51.5143, longitude: 7.4138}, {latitude: 123, longitude: 123}, ...] 
		* @return		integer		length of the path (in meters)
		*/
		getPathLength: function(coords) {

			var dist = 0;
			var last;

			for (var i = 0, l = coords.length; i < l; ++i) {
				if(last) {
					//console.log(coords[i], last, this.getDistance(coords[i], last));
					dist += this.getDistance(this.coords(coords[i]), last);
				}
				last = this.coords(coords[i]);
			}

			return dist;

		},


		/**
		* Calculates the speed between to points within a given time span.
		*
		* @param		object		coords with javascript timestamp {latitude: 51.5143, longitude: 7.4138, time: 1360231200880}
		* @param		object		coords with javascript timestamp {latitude: 51.5502, longitude: 7.4323, time: 1360245600460}
		* @param		object		options (currently "unit" is the only option. Default: km(h));
		* @return		float		speed in unit per hour
		*/
		getSpeed: function(start, end, options) {

			var unit = options && options.unit || 'km';

			if(unit == 'mph') {
				unit = 'mi';
			} else if(unit == 'kmh') {
				unit = 'km';
			}

			var distance = geolib.getDistance(start, end);
			var time = ((end.time*1)/1000) - ((start.time*1)/1000);
			var mPerHr = (distance/time)*3600;
			var speed = Math.round(mPerHr * this.measures[unit] * 10000)/10000;
			return speed;

		},


		/**
		* Converts a distance from meters to km, mm, cm, mi, ft, in or yd
		*
		* @param		string		Format to be converted in
		* @param		float		Distance in meters
		* @param		float		Decimal places for rounding (default: 4)
		* @return		float		Converted distance
		*/
		convertUnit: function(unit, distance, round) {

			if(distance === 0 || typeof distance === 'undefined') {

				if(this.distance === 0) {
					// throw 'No distance given.';
					return 0;
				} else {
					distance = this.distance;
				}

			}

			unit = unit || 'm';
			round = (null == round ? 4 : round);

			if(typeof this.measures[unit] !== 'undefined') {
				return this.round(distance * this.measures[unit], round);
			} else {
				throw new Error('Unknown unit for conversion.');
			}

		},


		/**
		* Checks if a value is in decimal format or, if neccessary, converts to decimal
		*
		* @param		mixed		Value(s) to be checked/converted (array of latlng objects, latlng object, sexagesimal string, float)
		* @return		float		Input data in decimal format
		*/
		useDecimal: function(value) {

			if(Object.prototype.toString.call(value) === '[object Array]') {

				var geolib = this;

				value = value.map(function(val) {

					//if(!isNaN(parseFloat(val))) {
					if(geolib.isDecimal(val)) {

						return geolib.useDecimal(val);

					} else if(typeof val == 'object') {

						if(geolib.validate(val)) {

							return geolib.coords(val);

						} else {

							for(var prop in val) {
								val[prop] = geolib.useDecimal(val[prop]);
							}

							return val;

						}

					} else if(geolib.isSexagesimal(val)) {

						return geolib.sexagesimal2decimal(val);

					} else {

						return val;

					}

				});

				return value;

			} else if(typeof value === 'object' && this.validate(value)) {

				return this.coords(value);

			} else if(typeof value === 'object') {

				for(var prop in value) {
					value[prop] = this.useDecimal(value[prop]);
				}

				return value;

			}


			if (this.isDecimal(value)) {

				return parseFloat(value);

			} else if(this.isSexagesimal(value) === true) {

				return parseFloat(this.sexagesimal2decimal(value));

			}

			throw new Error('Unknown format.');

		},

		/**
		* Converts a decimal coordinate value to sexagesimal format
		*
		* @param		float		decimal
		* @return		string		Sexagesimal value (XX° YY' ZZ")
		*/
		decimal2sexagesimal: function(dec) {

			if (dec in this.sexagesimal) {
				return this.sexagesimal[dec];
			}

			var tmp = dec.toString().split('.');

			var deg = Math.abs(tmp[0]);
			var min = ('0.' + tmp[1])*60;
			var sec = min.toString().split('.');

			min = Math.floor(min);
			sec = (('0.' + sec[1]) * 60).toFixed(2);

			this.sexagesimal[dec] = (deg + '° ' + min + "' " + sec + '"');

			return this.sexagesimal[dec];

		},


		/**
		* Converts a sexagesimal coordinate to decimal format
		*
		* @param		float		Sexagesimal coordinate
		* @return		string		Decimal value (XX.XXXXXXXX)
		*/
		sexagesimal2decimal: function(sexagesimal) {

			if (sexagesimal in this.decimal) {
				return this.decimal[sexagesimal];
			}

			var	regEx = new RegExp(this.sexagesimalPattern);
			var	data = regEx.exec(sexagesimal);
			var min = 0, sec = 0;

			if(data) {
				min = parseFloat(data[2]/60);
				sec = parseFloat(data[4]/3600) || 0;
			}

			var	dec = ((parseFloat(data[1]) + min + sec)).toFixed(8);
			//var	dec = ((parseFloat(data[1]) + min + sec));

				// South and West are negative decimals
				dec = (data[7] == 'S' || data[7] == 'W') ? parseFloat(-dec) : parseFloat(dec);
				//dec = (data[7] == 'S' || data[7] == 'W') ? -dec : dec;

			this.decimal[sexagesimal] = dec;

			return dec;

		},


		/**
		* Checks if a value is in decimal format
		*
		* @param		string		Value to be checked
		* @return		bool		True if in sexagesimal format
		*/
		isDecimal: function(value) {

			value = value.toString().replace(/\s*/, '');

			// looks silly but works as expected
			// checks if value is in decimal format
			return (!isNaN(parseFloat(value)) && parseFloat(value) == value);

		},


		/**
		* Checks if a value is in sexagesimal format
		*
		* @param		string		Value to be checked
		* @return		bool		True if in sexagesimal format
		*/
		isSexagesimal: function(value) {

			value = value.toString().replace(/\s*/, '');

			return this.sexagesimalPattern.test(value);

		},

		round: function(value, n) {
			var decPlace = Math.pow(10, n);
			return Math.round(value * decPlace)/decPlace;
		}

	});

	// Node module
	if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {

		global.geolib = module.exports = geolib;

	// AMD module
	} else if (typeof define === "function" && define.amd) {

		define("geolib", [], function () {
			return geolib; 
		});

	// we're in a browser
	} else {

		global.geolib = geolib;

	}

}(this));
