using UnityEngine;
using System;
using UnityEngine.Serialization;
namespace ARLocation
{
public enum AltitudeMode {
GroundRelative,
DeviceRelative,
Absolute,
Ignore
};
///
/// Represents a geographical location.
///
[Serializable]
public class Location
{
[FormerlySerializedAs("latitude")] [Tooltip("The latitude, in degrees.")]
public double Latitude;
[FormerlySerializedAs("longitude")] [Tooltip("The longitude, in degrees.")]
public double Longitude;
[FormerlySerializedAs("altitude")] [Tooltip("The altitude, in meters.")]
public double Altitude;
[FormerlySerializedAs("altitudeMode")]
[Space(4)]
[Tooltip("The altitude mode. 'Absolute' means absolute altitude, relative to the sea level. 'DeviceRelative' meas it is " +
"relative to the device's initial position. 'GroundRelative' means relative to the nearest detected plane, and 'Ignore' means the " +
"altitude is ignored (equivalent to setting it to zero).")]
public AltitudeMode AltitudeMode = AltitudeMode.GroundRelative;
[FormerlySerializedAs("label")] [Tooltip("An optional label for the location.")]
public string Label = "";
public bool IgnoreAltitude => AltitudeMode == AltitudeMode.Ignore;
///
/// Gets the horizontal vector.
///
/// The horizontal vector.
public DVector2 HorizontalVector => new DVector2(Latitude, Longitude);
public Location(double latitude = 0.0, double longitude = 0.0, double altitude = 0.0)
{
Latitude = latitude;
Longitude = longitude;
Altitude = altitude;
}
///
/// Clones this instance.
///
/// The clone.
public Location Clone()
{
return new Location()
{
Label = Label,
Latitude = Latitude,
Longitude = Longitude,
Altitude = Altitude,
AltitudeMode = AltitudeMode
};
}
public override string ToString()
{
return "(" + Latitude + ", " + Longitude + ", " + Altitude + ")";
}
public DVector3 ToDVector3()
{
return new DVector3(Longitude, Altitude, Latitude);
}
public Vector3 ToVector3()
{
return ToDVector3().toVector3();
}
///
/// Calculates the horizontal distance according to the current function
/// set in the configuration.
///
/// The distance, in meters.
/// L1.
/// L2.
public static double HorizontalDistance(Location l1, Location l2)
{
var type = ARLocation.Config.DistanceFunction;
switch (type)
{
case ARLocationConfig.ARLocationDistanceFunc.Haversine:
return HaversineDistance(l1, l2);
case ARLocationConfig.ARLocationDistanceFunc.PlaneSpherical:
return PlaneSphericalDistance(l1, l2);
case ARLocationConfig.ARLocationDistanceFunc.PlaneEllipsoidalFcc:
return PlaneEllipsoidalFccDistance(l1, l2);
default:
return HaversineDistance(l1, l2);
}
}
///
/// Horizontal distance using spherical projection on a plane.
/// https://en.wikipedia.org/wiki/Geographical_distance
///
/// The distance, in meters.
///
///
///
public static double PlaneSphericalDistance(Location l1, Location l2)
{
var r = ARLocation.Config.EarthRadiusInKM;
var rad = Math.PI / 180;
var dLat = (l2.Latitude - l1.Latitude) * rad;
var dLon = (l2.Longitude - l1.Longitude) * rad;
var lat1 = l1.Latitude * rad;
var lat2 = l2.Latitude * rad;
var mLat = (lat1 + lat2) / 2.0;
var mLatC = Math.Cos(mLat);
var a = dLat * dLat;
var b = mLatC * mLatC * dLon * dLon;
return r * Math.Sqrt(a + b) * 1000.0;
}
///
/// Horizontal distance using ellipsoidal projection on a plane.
/// https://en.wikipedia.org/wiki/Geographical_distance
///
/// The distance, in meters.
///
///
///
public static double PlaneEllipsoidalFccDistance(Location l1, Location l2)
{
var rad = Math.PI / 180;
var lat1 = l1.Latitude * rad;
var lat2 = l2.Latitude * rad;
var mLat = (lat1 + lat2) / 2.0;
var k1 = 111.13209 - 0.56605 * Math.Cos(2 * mLat) + 0.00120 * Math.Cos(4 * mLat);
var k2 = 111.41513 * Math.Cos(mLat) - 0.09455 * Math.Cos(3 * mLat) + 0.00012 * Math.Cos(5 * mLat);
var a = k1 * (l2.Latitude - l1.Latitude);
var b = k2 * (l2.Longitude - l1.Longitude);
return 1000.0 * Math.Sqrt(a * a + b * b);
}
///
/// Horizontal distance, using the Haversine formula.
/// https://stackoverflow.com/questions/41621957/a-more-efficient-haversine-function
///
/// The distance, in meters.
/// L1.
/// L2.
public static double HaversineDistance(Location l1, Location l2)
{
var r = ARLocation.Config.EarthRadiusInKM;
var rad = Math.PI / 180;
var dLat = (l2.Latitude - l1.Latitude) * rad;
var dLon = (l2.Longitude - l1.Longitude) * rad;
var lat1 = l1.Latitude * rad;
var lat2 = l2.Latitude * rad;
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Sin(dLon / 2) * Math.Sin(dLon / 2) * Math.Cos(lat1) * Math.Cos(lat2);
return r * 2 * Math.Asin(Math.Sqrt(a)) * 1000;
}
///
/// Calculates the full distance between locations, taking altitude into account.
///
/// The with altitude.
/// L1.
/// L2.
public static double DistanceWithAltitude(Location l1, Location l2)
{
var d = HorizontalDistance(l1, l2);
var h = Math.Abs(l1.Altitude - l2.Altitude);
return Math.Sqrt(d * d + h * h);
}
///
/// Calculates the horizontal vector pointing from l1 to l2, in meters.
///
/// The vector from to.
/// L1.
/// L2.
public static DVector2 HorizontalVectorFromTo(Location l1, Location l2)
{
var d = HorizontalDistance(l1, l2);
var direction = (l2.HorizontalVector - l1.HorizontalVector).normalized;
return direction * d;
}
///
/// Calculates the vector from l1 to l2, in meters, taking altitude into account.
///
/// The from to.
/// L1.
/// L2.
/// If true, y = 0 in the output vector.
public static DVector3 VectorFromTo(Location l1, Location l2, bool ignoreHeight = false)
{
var horizontal = HorizontalVectorFromTo(l1, l2);
var height = l2.Altitude - l1.Altitude;
return new DVector3(horizontal.y, ignoreHeight ? 0 : height, horizontal.x);
}
///
/// Gets the game object world-position for location.
///
///
///
///
///
///
///
public static Vector3 GetGameObjectPositionForLocation(Transform arLocationRoot, Vector3 userPosition, Location userLocation, Location objectLocation, bool heightIsRelative)
{
var displacementVector = VectorFromTo(userLocation, objectLocation, objectLocation.IgnoreAltitude || heightIsRelative)
.toVector3();
var displacementPosition = arLocationRoot ? arLocationRoot.TransformVector(displacementVector) : displacementVector;
return userPosition + displacementPosition + new Vector3(0, (heightIsRelative && !objectLocation.IgnoreAltitude) ? ((float)objectLocation.Altitude - userPosition.y) : 0, 0);
}
///
/// Gets the game object world-position for location.
///
/// The game object position for location.
///
/// User.
/// User location.
/// Object location.
/// If set to true height is relative.
///
public static Vector3 GetGameObjectPositionForLocation(Transform arLocationRoot, Transform user, Location userLocation, Location objectLocation, bool heightIsRelative)
{
return GetGameObjectPositionForLocation(arLocationRoot, user.position, userLocation, objectLocation,
heightIsRelative);
}
///
/// Places the game object at location.
///
///
/// The GameObject's transform.
/// The user's point of view Transform, e.g., camera.
/// User Location.
/// Object Location.
///
public static void PlaceGameObjectAtLocation(Transform arLocationRoot, Transform transform, Transform user, Location userLocation, Location objectLocation, bool heightIsRelative)
{
transform.position = GetGameObjectPositionForLocation(arLocationRoot, user, userLocation, objectLocation, heightIsRelative);
}
public static bool Equal(Location a, Location b, double eps = 0.0000001)
{
return (Math.Abs(a.Latitude - b.Latitude) <= eps) &&
(Math.Abs(a.Longitude - b.Longitude) <= eps) &&
(Math.Abs(a.Altitude - b.Altitude) <= eps);
}
}
}