File: Types\Location.shared.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
using System;
using Microsoft.Maui.Media;
 
namespace Microsoft.Maui.Devices.Sensors
{
	/// <summary>Distance unit for use in conversion.</summary>
	public enum DistanceUnits
	{
		/// <summary>Kilometers.</summary>
		Kilometers,
 
		/// <summary>Miles.</summary>
		Miles
	}
 
	/// <summary>
	/// Indicates the altitude reference system to be used in defining a location.
	/// </summary>
	public enum AltitudeReferenceSystem
	{
		/// <summary>The altitude reference system was not specified.</summary>
		Unspecified = 0,
 
		/// <summary>The altitude reference system is based on distance above terrain or ground level</summary>
		Terrain = 1,
 
		/// <summary>The altitude reference system is based on an ellipsoid (usually WGS84), which is a mathematical approximation of the shape of the Earth.</summary>
		Ellipsoid = 2,
 
		/// <summary>The altitude reference system is based on the distance above sea level (parametrized by a so-called Geoid).</summary>
		Geoid = 3,
 
		/// <summary>The altitude reference system is based on the distance above the tallest surface structures, such as buildings, trees, roads, etc., above terrain or ground level.</summary>
		Surface = 4
	}
 
	/// <summary>
	/// Represents a physical location with the latitude, longitude, altitude and time information reported by the device.
	/// </summary>
	public class Location
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="Location"/> class.
		/// </summary>
		public Location()
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="Location"/> class with the specified latitude and longitude.
		/// </summary>
		/// <param name="latitude">Latitude in degrees. Must be in the interval [-90, 90].</param>
		/// <param name="longitude">Longitude in degrees. Will be projected to the interval (-180, 180].</param>
		public Location(double latitude, double longitude) : this(latitude, longitude, DateTimeOffset.UtcNow)
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="Location"/> class with the specified latitude, longitude, and timestamp.
		/// </summary>
		/// <param name="latitude">Latitude in degrees. Must be in the interval [-90, 90].</param>
		/// <param name="longitude">Longitude in degrees. Will be projected to the interval (-180, 180].</param>
		/// <param name="timestamp">UTC timestamp for the location.</param>
		public Location(double latitude, double longitude, DateTimeOffset timestamp)
		{
			// check if latitude is in [-90, 90]
			if (Math.Abs(latitude) > 90)
				throw new ArgumentOutOfRangeException(nameof(latitude));
			else
				Latitude = latitude;
 
			// make sure that longitude is in (-180, 180]
			Longitude = longitude;
			while (Longitude > 180)
				Longitude -= 360;
			while (Longitude <= -180)
				Longitude += 360;
 
			Timestamp = timestamp;
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="Location"/> class with the specified latitude, longitude, and altitude.
		/// </summary>
		/// <param name="latitude">Latitude in degrees. Must be in the interval [-90, 90].</param>
		/// <param name="longitude">Longitude in degrees. Will be projected to the interval (-180, 180].</param>
		/// <param name="altitude">Altitude in meters.</param>
		public Location(double latitude, double longitude, double altitude) : this(latitude, longitude)
		{
			Altitude = altitude;
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="Location"/> class from an existing instance.
		/// </summary>
		/// <param name="point">A <see cref="Location"/> instance that will be used to clone.</param>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="point"/> is <see langword="null"/>.</exception>
		public Location(Location point)
		{
			if (point == null)
				throw new ArgumentNullException(nameof(point));
 
			Latitude = point.Latitude;
			Longitude = point.Longitude;
			Timestamp = DateTime.UtcNow;
			Altitude = point.Altitude;
			Accuracy = point.Accuracy;
			VerticalAccuracy = point.VerticalAccuracy;
			ReducedAccuracy = point.ReducedAccuracy;
			Speed = point.Speed;
			Course = point.Course;
			IsFromMockProvider = point.IsFromMockProvider;
		}
 
		/// <summary>
		/// Gets or sets the timestamp of the location in UTC.
		/// </summary>
		public DateTimeOffset Timestamp { get; set; }
 
		/// <summary>
		/// Gets or sets the latitude coordinate of this location.
		/// </summary>
		public double Latitude { get; set; }
 
		/// <summary>
		/// Gets or sets the longitude coordinate of this location.
		/// </summary>
		public double Longitude { get; set; }
 
		/// <summary>
		/// Gets the altitude in meters (if available) in a reference system which is specified by <see cref="AltitudeReferenceSystem"/>.
		/// </summary>
		/// <remarks>Returns 0 or <see langword="null"/> if not available.</remarks>
		public double? Altitude { get; set; }
 
		/// <summary>
		/// Gets or sets the horizontal accuracy (in meters) of the location.
		/// </summary>
		public double? Accuracy { get; set; }
 
		/// <summary>
		/// Gets or sets the vertical accuracy (in meters) of the location.
		/// </summary>
		public double? VerticalAccuracy { get; set; }
 
		/// <summary>
		/// Gets or sets whether this location has a reduced accuracy reading.
		/// </summary>
		/// <remarks>This functionality only applies to iOS. Other platforms will always report false.</remarks>
		public bool ReducedAccuracy { get; set; }
 
		/// <summary>
		/// Gets or sets the current speed in meters per second at the time when this location was determined.
		/// </summary>
		/// <remarks>
		/// <para>Returns 0 or <see langword="null"/> if not available. Otherwise the value will range between 0-360.</para>
		/// <para>Requires <see cref="Accuracy"/> to be <see cref="GeolocationAccuracy.High"/> or better
		/// and may not be returned when calling <see cref="Geolocation.GetLastKnownLocationAsync"/>.</para>
		/// </remarks>
		public double? Speed { get; set; }
 
		/// <summary>
		/// Gets or sets the current degrees relative to true north at the time when this location was determined.
		/// </summary>
		/// <remarks>Returns 0 or <see langword="null"/> if not available.</remarks>
		public double? Course { get; set; }
 
		/// <summary>
		/// Gets or sets whether this location originates from a mocked sensor and thus might not be the real location of the device.
		/// </summary>
		public bool IsFromMockProvider { get; set; }
 
		/// <summary>
		/// Specifies the reference system in which the <see cref="Altitude"/> value is expressed.
		/// </summary>
		public AltitudeReferenceSystem AltitudeReferenceSystem { get; set; }
 
		/// <summary>
		/// Calculate distance between two locations.
		/// </summary>
		/// <param name="latitudeStart">Latitude coordinate of the starting location.</param>
		/// <param name="longitudeStart">Longitude coordinate of the starting location.</param>
		/// <param name="locationEnd">The end location.</param>
		/// <param name="units">The unit in which the result distance is returned.</param>
		/// <returns>Distance between two locations in the unit selected.</returns>
		public static double CalculateDistance(double latitudeStart, double longitudeStart, Location locationEnd, DistanceUnits units) =>
			CalculateDistance(latitudeStart, longitudeStart, locationEnd.Latitude, locationEnd.Longitude, units);
 
		/// <summary>
		/// Calculate distance between two locations.
		/// </summary>
		/// <param name="locationStart">The start location.</param>
		/// <param name="latitudeEnd">Latitude coordinate of the end location.</param>
		/// <param name="longitudeEnd">Longitude coordinate of the end location.</param>
		/// <param name="units">The unit in which the result distance is returned.</param>
		/// <returns>Distance between two locations in the unit selected.</returns>
		public static double CalculateDistance(Location locationStart, double latitudeEnd, double longitudeEnd, DistanceUnits units) =>
			CalculateDistance(locationStart.Latitude, locationStart.Longitude, latitudeEnd, longitudeEnd, units);
 
		/// <summary>
		/// Calculate distance between two locations.
		/// </summary>
		/// <param name="locationStart">The start location.</param>
		/// <param name="locationEnd">The end location.</param>
		/// <param name="units">The unit in which the result distance is returned.</param>
		/// <returns>Distance between two locations in the unit selected.</returns>
		public static double CalculateDistance(Location locationStart, Location locationEnd, DistanceUnits units) =>
			CalculateDistance(locationStart.Latitude, locationStart.Longitude, locationEnd.Latitude, locationEnd.Longitude, units);
 
		/// <summary>
		/// Calculate distance between two <see cref="Location"/> instances.
		/// </summary>
		/// <param name="latitudeStart">Latitude coordinate of the starting location.</param>
		/// <param name="longitudeStart">Longitude coordinate of the starting location.</param>
		/// <param name="latitudeEnd">Latitude coordinate of the end location.</param>
		/// <param name="longitudeEnd">Longitude coordinate of the end location.</param>
		/// <param name="units">The unit in which the result distance is returned.</param>
		/// <returns>Distance between two locations in the unit selected.</returns>
		public static double CalculateDistance(
			double latitudeStart,
			double longitudeStart,
			double latitudeEnd,
			double longitudeEnd,
			DistanceUnits units)
		{
			switch (units)
			{
				case DistanceUnits.Kilometers:
					return UnitConverters.CoordinatesToKilometers(latitudeStart, longitudeStart, latitudeEnd, longitudeEnd);
				case DistanceUnits.Miles:
					return UnitConverters.CoordinatesToMiles(latitudeStart, longitudeStart, latitudeEnd, longitudeEnd);
				default:
					throw new ArgumentOutOfRangeException(nameof(units));
			}
		}
 
		/// <summary>
		/// Returns a string representation of the current values of <see cref="Location"/>.
		/// </summary>
		/// <returns>A string representation of this instance in the format of <c>Latitude: {value}, Longitude: {value}, Altitude: {value}, Accuracy: {value}, VerticalAccuracy: {value}, Speed: {value}, Course: {value}, Timestamp: {value}</c>.</returns>
		public override string ToString() =>
			$"{nameof(Latitude)}: {Latitude}, " +
			$"{nameof(Longitude)}: {Longitude}, " +
			$"{nameof(Altitude)}: {Altitude}, " +
			$"{nameof(Accuracy)}: {Accuracy}, " +
			$"{nameof(VerticalAccuracy)}: {VerticalAccuracy}, " +
			$"{nameof(Speed)}: {Speed}, " +
			$"{nameof(Course)}: {Course}, " +
			$"{nameof(Timestamp)}: {Timestamp}";
 
		/// <inheritdoc cref="object.Equals(object)"/>
		public override bool Equals(object obj)
		{
			if (obj is null)
				return false;
			if (obj.GetType() != GetType())
				return false;
			var other = (Location)obj;
			return Latitude == other.Latitude && Longitude == other.Longitude;
		}
 
		/// <inheritdoc cref="object.GetHashCode"/>
		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = Latitude.GetHashCode();
				hashCode = (hashCode * 397) ^ Longitude.GetHashCode();
				return hashCode;
			}
		}
 
		/// <summary>
		///	Equality operator for equals.
		/// </summary>
		/// <param name="left">Left to compare.</param>
		/// <param name="right">Right to compare.</param>
		/// <returns><see langword="true"/> if objects are equal, otherwise <see langword="false"/>.</returns>
		public static bool operator ==(Location left, Location right)
		{
			return Equals(left, right);
		}
 
		/// <summary>
		/// Inequality operator.
		/// </summary>
		/// <param name="left">Left to compare.</param>
		/// <param name="right">Right to compare.</param>
		/// <returns><see langword="true"/> if objects are not equal, otherwise <see langword="false"/>.</returns>
		public static bool operator !=(Location left, Location right)
		{
			return !Equals(left, right);
		}
	}
}