/// <summary>
		/// Calculates bearing between start and stop.
		/// </summary>
		/// <param name="start">Start coordinates.</param>
		/// <param name="stop">Stop coordinates.</param>
		/// <returns>The <see cref="System.Double" />.</returns>
		public static double BearingFrom(this Position start, Position stop)
		{
			var deltaLon = stop.Longitude - start.Longitude;
			var cosStop = Math.Cos(stop.Latitude);
			return Math.Atan2(
				(Math.Cos(start.Latitude) * Math.Sin(stop.Latitude)) -
				(Math.Sin(start.Latitude) * cosStop * Math.Cos(deltaLon)),
				Math.Sin(deltaLon) * cosStop);
		}
		/// <summary>
		/// Initializes a new instance of the <see cref="Position" /> class.
		/// </summary>
		/// <param name="position">The position.</param>
		/// <exception cref="System.ArgumentNullException">position</exception>
		public Position(Position position)
		{
			if (position == null)
			{
				throw new ArgumentNullException("position");
			}

			Timestamp = position.Timestamp;
			Latitude = position.Latitude;
			Longitude = position.Longitude;
			Altitude = position.Altitude;
			AltitudeAccuracy = position.AltitudeAccuracy;
			Accuracy = position.Accuracy;
			Heading = position.Heading;
			Speed = position.Speed;
		}
		/// <summary>
		/// Calculates distance between two locations.
		/// </summary>
		/// <param name="a">Location a</param>
		/// <param name="b">Location b</param>
		/// <returns>The <see cref="System.Double" />The distance in meters</returns>
		public static double DistanceFrom(this Position a, Position b)
		{
			/*
			double distance = Math.Acos(
				(Math.Sin(a.Latitude) * Math.Sin(b.Latitude)) +
				(Math.Cos(a.Latitude) * Math.Cos(b.Latitude))
				* Math.Cos(b.Longitude - a.Longitude));
			 * */

			var dLat = b.Latitude.DegreesToRadians() - a.Latitude.DegreesToRadians();
			var dLon = b.Longitude.DegreesToRadians() - a.Longitude.DegreesToRadians();

			var a1 = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(a.Latitude.DegreesToRadians()) * Math.Cos(b.Latitude.DegreesToRadians()) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
			var distance = 2 * Math.Atan2(Math.Sqrt(a1), Math.Sqrt(1 - a1));

			return EquatorRadius * distance;
		}
		/// <summary>
		/// Initializes a new instance of the <see cref="PositionEventArgs"/> class.
		/// </summary>
		/// <param name="position">The position.</param>
		/// <exception cref="System.ArgumentNullException">position</exception>
		public PositionEventArgs(Position position)
		{
			if (position == null)
			{
				throw new ArgumentNullException("position");
			}

			Position = position;
		}
		/// <summary>
		///     Finishes the specified location.
		/// </summary>
		/// <param name="location">The location.</param>
		private void Finish(Location location)
		{
			var p = new Position();
			if (location.HasAccuracy)
			{
				p.Accuracy = location.Accuracy;
			}
			if (location.HasAltitude)
			{
				p.Altitude = location.Altitude;
			}
			if (location.HasBearing)
			{
				p.Heading = location.Bearing;
			}
			if (location.HasSpeed)
			{
				p.Speed = location.Speed;
			}

			p.Longitude = location.Longitude;
			p.Latitude = location.Latitude;
			p.Timestamp = Geolocator.GetTimestamp(location);

			if (_finishedCallback != null)
			{
				_finishedCallback();
			}

			_completionSource.TrySetResult(p);
		}
		/// <summary>
		///     Called when the location has changed.
		/// </summary>
		/// <param name="location">The new location, as a Location object.</param>
		/// <since version="Added in API level 1" />
		/// <remarks>
		///     <para tool="javadoc-to-mdoc">
		///         Called when the location has changed.
		///     </para>
		///     <para tool="javadoc-to-mdoc"> There are no restrictions on the use of the supplied Location object.</para>
		///     <para tool="javadoc-to-mdoc">
		///         <format type="text/html">
		///             <a
		///                 href="http://developer.android.com/reference/android/location/LocationListener.html#onLocationChanged(android.location.Location)"
		///                 target="_blank">
		///                 [Android Documentation]
		///             </a>
		///         </format>
		///     </para>
		/// </remarks>
		public void OnLocationChanged(Location location)
		{
			if (location.Provider != _activeProvider)
			{
				if (_activeProvider != null && _manager.IsProviderEnabled(_activeProvider))
				{
					var pr = _manager.GetProvider(location.Provider);
					var lapsed = GetTimeSpan(location.Time) - GetTimeSpan(_lastLocation.Time);

					if (pr.Accuracy > _manager.GetProvider(_activeProvider).Accuracy && lapsed < _timePeriod.Add(_timePeriod))
					{
						location.Dispose();
						return;
					}
				}

				_activeProvider = location.Provider;
			}

			var previous = Interlocked.Exchange(ref _lastLocation, location);
			if (previous != null)
			{
				previous.Dispose();
			}

			var p = new Position();
			if (location.HasAccuracy)
			{
				p.Accuracy = location.Accuracy;
			}
			if (location.HasAltitude)
			{
				p.Altitude = location.Altitude;
			}
			if (location.HasBearing)
			{
				p.Heading = location.Bearing;
			}
			if (location.HasSpeed)
			{
				p.Speed = location.Speed;
			}

			p.Longitude = location.Longitude;
			p.Latitude = location.Latitude;
			p.Timestamp = Geolocator.GetTimestamp(location);

			var changed = PositionChanged;
			if (changed != null)
			{
				changed(this, new PositionEventArgs(p));
			}
		}
		/// <summary>
		///     Handles the <see cref="E:ListenerPositionChanged" /> event.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="PositionEventArgs" /> instance containing the event data.</param>
		private void OnListenerPositionChanged(object sender, PositionEventArgs e)
		{
			if (!IsListening) // ignore anything that might come in afterwards
			{
				return;
			}

			lock (_positionSync)
			{
				_lastPosition = e.Position;

				var changed = PositionChanged;
				if (changed != null)
				{
					changed(this, e);
				}
			}
		}
		/// <summary>
		/// Updates the position.
		/// </summary>
		/// <param name="location">The location.</param>
		private void UpdatePosition(CLLocation location)
		{
			var p = (_position == null) ? new Position() : new Position(_position);

			if (location.HorizontalAccuracy > -1)
			{
				p.Accuracy = location.HorizontalAccuracy;
				p.Latitude = location.Coordinate.Latitude;
				p.Longitude = location.Coordinate.Longitude;
			}

			if (location.VerticalAccuracy > -1)
			{
				p.Altitude = location.Altitude;
				p.AltitudeAccuracy = location.VerticalAccuracy;
			}

			if (location.Speed > -1)
			{
				p.Speed = location.Speed;
			}

			p.Timestamp = new DateTimeOffset((DateTime)location.Timestamp);

			_position = p;

			OnPositionChanged(new PositionEventArgs(p));

			location.Dispose();
		}
		/// <summary>
		/// Handles the <see cref="E:UpdatedHeading" /> event.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="CLHeadingUpdatedEventArgs"/> instance containing the event data.</param>
		private void OnUpdatedHeading(object sender, CLHeadingUpdatedEventArgs e)
		{
			if (e.NewHeading.TrueHeading == -1)
			{
				return;
			}

			var p = (_position == null) ? new Position() : new Position(_position);

			p.Heading = e.NewHeading.TrueHeading;

			_position = p;

			OnPositionChanged(new PositionEventArgs(p));
		}
		/// <summary>
		/// Stop listening to location changes
		/// </summary>
		public void StopListening()
		{
			if (!IsListening)
			{
				return;
			}

			IsListening = false;
			if (CLLocationManager.HeadingAvailable)
			{
				_manager.StopUpdatingHeading();
			}

			_manager.StopUpdatingLocation();
			_position = null;
		}