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(); } /// /// Given a Location returns the corresponding (x, y, z) ECEF coordinates. /// /// /// public static DVector3 LocationToEcef(Location l) { var rad = Math.PI / 180; var lat = l.Latitude * rad; var lon = l.Longitude * rad; var a = ARLocation.Config.EarthEquatorialRadiusInKM * 1000; var e2 = ARLocation.Config.EarthFirstEccentricitySquared; var N = a / Math.Sqrt(1 - e2 * Math.Pow(Math.Sin(lat), 2)); var x = N * Math.Cos(lat) * Math.Cos(lon); var y = N * Math.Cos(lat) * Math.Sin(lon); var z = (1 - e2) * N * Math.Sin(lat); return new DVector3(x, y, z); } /// /// Given a pair of locations, returns the local-plane ENU coordinates, considering the first location, l1, to be the center of the local plane. /// /// /// /// public static DVector2 VectorFromToEcefEnu(Location l1, Location l2) { var rad = Math.PI / 180; var lat = l1.Latitude * rad; var lon = l1.Longitude * rad; var p1 = LocationToEcef(l1); var p2 = LocationToEcef(l2); var delta = p2 - p1; var slat = Math.Sin(lat); var clat = Math.Cos(lat); var slon = Math.Sin(lon); var clon = Math.Cos(lon); var e = -slon * delta.x + clon * delta.y; var n = -clon * slat * delta.x -slat * slon * delta.y+ clat*delta.z; return new DVector2(n, e); } /// /// Given a center location, and the local-plane ENU coordinates of a second point, calculates the Location of the second point. /// /// /// /// /// /// public static Location LocationFromEnu(Location center, double e, double n, double u) { var lat = DegToRad(center.Latitude); var lon = DegToRad(center.Longitude); var slat = Math.Sin(lat); var clat = Math.Cos(lat); var slon = Math.Sin(lon); var clon = Math.Cos(lon); var dx = -e * slon - n * clon * slat + u * clon * clat; var dy = e * clon - n * slon * slat + u * slon * clat; var dz = n * clat + u * slat; var centerEcef = LocationToEcef(center); var pointEcef = centerEcef + new DVector3(dx, dy, dz); var pointLocation = EcefToLocation(pointEcef); return pointLocation; } /// /// Converts from (x, y, z) ECEF coordinates to a wgs84 Location. /// /// /// public static Location EcefToLocation(DVector3 ecef) { var a = ARLocation.Config.EarthEquatorialRadiusInKM * 1000.0; var e2 = ARLocation.Config.EarthFirstEccentricitySquared; var b = a * Math.Sqrt(1 - e2); var x = ecef.x; var y = ecef.y; var z = ecef.z; var r = Math.Sqrt(x * x + y * y); var E2 = (a * a - b * b) / (b * b); var F = 54 * b * b * z * z; var G = r * r + (1 - e2) * z * z - e2 * (a * a - b * b); var c = (e2 * e2 * F * r * r) / (G * G * G); var s = Math.Pow(1 + c + Math.Sqrt(c*c + 2*c), 1.0 / 3.0); var P = F / (3 * Math.Pow(s + (1.0/s) +1, 2.0) * G * G); var Q = Math.Sqrt(1 + 2 * e2 * e2 * P); var r0 = -(P * e2 * r) / (1 + Q) + Math.Sqrt(((a * a * 0.5) * (1.0 + (1.0 / Q))) - ((P * (1 - e2) * z * z) / (Q * (1.0 + Q))) - (P*r*r*0.5)); var U = Math.Sqrt(Math.Pow(r - e2 * r0, 2) + z*z); var V = Math.Sqrt(Math.Pow(r - e2 * r0, 2) + (1 - e2) * z * z); var z0 = (b * b * z) / (a * V); var h = U * (1 - ((b * b) / (a * V))); var phi = Math.Atan((z + E2 * z0) / r); var lambda = Math.Atan2(y, x); var rad2deg = 180.0 / Math.PI; return new Location() { Latitude = rad2deg * phi, Longitude = rad2deg * lambda, Altitude = h, AltitudeMode = AltitudeMode.GroundRelative }; } /// /// 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) { #if ARGPS_CUSTOM_GEO_CALC return ArGpsCustomGeoCalc.HorizontalVectorFromTo(l1, l2).magnitude; #else return VectorFromToEcefEnu(l1, l2).magnitude; #endif } /// /// 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) { #if ARGPS_USE_CUSTOM_GEO_CALC return ArGpsCustomGeoCalc.HorizontalVectorFromTo(l1, l2); #else return VectorFromToEcefEnu(l1, l2); #endif } /// /// 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); } /// /// Calculates the wgs84 Location for a given world position vector. /// /// The ARLocationRoot game object transform. /// The position of the center (i.e., the device). /// The wgs84 Location of the center/device. /// The world position. /// public static Location GetLocationForWorldPosition(Transform arLocationRoot, Vector3 center, Location userLocation, Vector3 worldPosition) { center = arLocationRoot.InverseTransformVector(center); Vector3 position = arLocationRoot.InverseTransformVector(worldPosition); var n = position.z - center.z; var e = position.x - center.x; var u = 0; var loc = LocationFromEnu(userLocation, e, n, u); return loc; } 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); } public static double RadToDeg(double rad) { return (180.0 * Math.PI) * rad; } public static double DegToRad(double deg) { return (Math.PI/ 180.0) * deg; } } }