Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Latitude and longitude coordinates in Vector 3 unity

Discussion in 'Scripting' started by unity_-fu_9nA3E-Z1ZA, Jun 8, 2023.

  1. unity_-fu_9nA3E-Z1ZA

    unity_-fu_9nA3E-Z1ZA

    Joined:
    Jul 13, 2019
    Posts:
    24
    Hi everyone!
    I created a code to read the coordinates (lat and long), translate them into x and z axes and instantiate the game objects at the position from these coordinates.
    It works but in a very approximate way, because I read the distance from each coordinate and the distance from each instanced gemeobject, and I have a total difference of at least 60m! This is the code for those with the patience and desire to read!
    I thank in advance anyone who wants to try to approach this mess!

    Code (CSharp):
    1. namespace Coordinate
    2. {
    3.     [System.Serializable]
    4.     public struct CoordinateStruct
    5.     {
    6.         //public string latitudeST;
    7.         //public string longitudeST;
    8.         public float latitude;
    9.         public float longitude;
    10.  
    11.         public CoordinateStruct(float lat, float lon)
    12.         {
    13.            
    14.             latitude = lat;
    15.             longitude = lon;
    16.             //latitudeST = lat.ToString();
    17.             //longitudeST= lon.ToString();
    18.         }
    19.        
    20.     }
    21.  
    22. }
    Code (CSharp):
    1. public class CtrlCoordinate : MonoBehaviour
    2. {
    3.     string coordinateString = "(8.93266215617905 44.40604327943526, 8.932524131901832 44.40601228591227, 8.932359430658943 44.405996835286025, 8.93207661034279 44.40596474551083, 8.931946845727186 44.40595880295797, 8.931883627068249 44.405962368489796, 8.931785471782122 44.40598376167611, 8.93166568905997 44.406009908893175, 8.931517013271835 44.40604674149274, 8.931644270656854 44.40620701031115, 8.931767094864945 44.40631137017433, 8.931939326744915 44.40647704135852, 8.932056121350431 44.406610424857305, 8.932478536495315 44.40696008912377, 8.932023606423044 44.40720417336542, 8.931597848307996 44.40737981195299, 8.93109413448181 44.40756830155794, 8.93087226053456 44.40767968149355, 8.931100131075016 44.40803952291286, 8.931310011836054 44.40824942938514, 8.931621834680811 44.40849360535409, 8.93188568478035 44.408707793961234, 8.93145393007221 44.40921755969168, 8.931236995407682 44.4091560126473, 8.931065401064807 44.409106149460136, 8.930896715100559 44.409108227093775, 8.930669861562503 44.4091934100099, 8.930495358840817 44.409268204663185, 8.930224879622424 44.40928274805689, 8.929974759054671 44.409272359918894, 8.929942766889134 44.40937624121568, 8.929570494416307 44.40938662933528, 8.929174954913956 44.40941156081472, 8.92888120866598 44.40943649228339)";
    4.     public List<CoordinateStruct> coordinateList = new List<CoordinateStruct>();
    5.     public List<GameObject> PuntiList = new List<GameObject>();
    6.     private static float scale = 10000000f;
    7.     public GameObject referenceObject;
    8.  
    9.     // Start is called before the first frame update
    10.     void Start()
    11.     {
    12.         // All of this stuff is used to convert the string to float and serialize the class Coordinate for eac value from the array!
    13.         string modifiedCoordinaateString = coordinateString.Replace("(", "").Replace(")", "");
    14.         string[] coordinateArray = modifiedCoordinaateString.Split(new string[] { ", " }, StringSplitOptions.None);
    15.         foreach (string coordinatePair in coordinateArray)
    16.         {
    17.             string[] coordinateValues = coordinatePair.Split(' ');
    18.             decimal decimLongitude = decimal.Parse(coordinateValues[0], CultureInfo.InvariantCulture);
    19.             decimal decimLatitude = decimal.Parse(coordinateValues[1], CultureInfo.InvariantCulture);
    20.             float longitude = (float)decimal.Round(decimLongitude, 6);
    21.             float latitude = (float)decimal.Round(decimLatitude, 6);
    22.             CoordinateStruct coordinate = new CoordinateStruct(latitude, longitude);
    23.             coordinateList.Add(coordinate);
    24.             //Debug.Log(coordinate.latitude);
    25.            
    26.         }
    27.         InstantiatePoints();
    28.     }
    29.  
    30.     // use this method to instanciate the gameobjects!
    31.     void InstantiatePoints()
    32.     {
    33.         float distanzaTotale=0;
    34.         float distanzaTotaleGeografica = 0;
    35.         for (int i = 0; i < coordinateList.Count; i++)
    36.         {
    37.             CoordinateStruct coordinate = coordinateList[i];
    38.             GameObject pointObject = Instantiate(referenceObject);
    39.             Vector3 position = AlbersProjection(coordinate.longitude, coordinate.latitude);
    40.             pointObject.transform.position = position;
    41.             PuntiList.Add(pointObject);
    42.             if (i > 0)
    43.             {
    44.                 float geographicDistance = CalculateGeographicDistance(coordinateList[i], coordinateList[i-1]);
    45.                 float distance = Vector3.Distance(PuntiList[i].transform.position, PuntiList[i-1].transform.position);
    46.                 Debug.Log($"distanza geografica = {geographicDistance}");
    47.                 Debug.Log($"distnza vettori = {distance}");
    48.                 distanzaTotale += distance;
    49.                 distanzaTotaleGeografica += geographicDistance;
    50.             }
    51.            
    52.         }
    53.         Debug.Log(distanzaTotale);
    54.         Debug.Log(distanzaTotaleGeografica);
    55.  
    56.         //float geographicDistance = CalculateGeographicDistance(coordinateList[0], coordinateList[1]);
    57.         //float distance = Vector3.Distance(PuntiList[0].transform.position, PuntiList[1].transform.position);
    58.         //Debug.Log($"distanza geografica = {geographicDistance}");
    59.         //Debug.Log($"distnza vettori = {distance}");
    60.     }
    61.  
    62.     public static Vector3 AlbersProjection(float latitude, float longitude)
    63.     {
    64.         // Coordinate di riferimento per Genova
    65.         float centralMeridian = 8.9463f; // Longitudine di riferimento
    66.         float standardParallel1 = 44f; // Primo parallelo standard
    67.         float standardParallel2 = 45f; // Secondo parallelo standard
    68.  
    69.         // Conversione da gradi a radianti
    70.         float latRad = Mathf.Deg2Rad * latitude;
    71.         float longRad = Mathf.Deg2Rad * longitude;
    72.         float centralMeridianRad = Mathf.Deg2Rad * centralMeridian;
    73.         float stdParallel1Rad = Mathf.Deg2Rad * standardParallel1;
    74.         float stdParallel2Rad = Mathf.Deg2Rad * standardParallel2;
    75.  
    76.         // Parametri per la proiezione di Albers
    77.         float n = (Mathf.Sin(stdParallel1Rad) + Mathf.Sin(stdParallel2Rad)) / 2f;
    78.         float c = Mathf.Pow(Mathf.Cos(stdParallel1Rad), 2f) + 2f * n * Mathf.Sin(stdParallel1Rad);
    79.         float rho0 = Mathf.Sqrt(c - 2f * n * Mathf.Sin(stdParallel1Rad)) / n;
    80.  
    81.         // Calcolo della proiezione di Albers
    82.         float rho = Mathf.Sqrt(c - 2f * n * Mathf.Sin(latRad)) / n;
    83.         float theta = n * (longRad - centralMeridianRad);
    84.  
    85.         float x = scale * (rho * Mathf.Sin(theta));
    86.         float z = scale * (rho0 - rho * Mathf.Cos(theta));
    87.  
    88.         return new Vector3(ScaleCoordinates(x)/2, 0, ScaleCoordinates(z)/2);
    89.     }
    90.     public static Vector3 GPSToMapCoords(float latitude, float longitude)
    91.     {
    92.         float radius = 6371000f; // raggio della Terra in metri
    93.         float longitude0 = 0f; // longitudine di riferimento
    94.  
    95.         float latRad = Mathf.Deg2Rad * latitude;
    96.         float longRad = Mathf.Deg2Rad * longitude;
    97.  
    98.         float x = scale*(radius * (longRad - Mathf.Deg2Rad * longitude0));
    99.         float y = 0;
    100.         float z = scale*(radius * Mathf.Log(Mathf.Tan(Mathf.PI / 4f + latRad / 2f)));
    101.  
    102.         return new Vector3(ScaleCoordinates(x), y, ScaleCoordinates(z));
    103.     }
    104.  
    105.  
    106.     //use this method to reduce the size of the number cause u really dont need all the firs part of the number, and smaller one can readed easily from unity transform!
    107.     public static float ScaleCoordinates(float num)
    108.     {
    109.         // Converti il numero in una stringa
    110.         string numString = num.ToString();
    111.  
    112.         // Rimuovi le prime quattro cifre decimali
    113.         string scaledString = numString.Substring(2);
    114.  
    115.         // Converti la stringa risultante in float
    116.         float scaledNum = float.Parse(scaledString);
    117.  
    118.         // Restituisce il numero con le cifre decimali ridotte
    119.         return scaledNum;
    120.     }
    121.  
    122.     public static float CalculateGeographicDistance(CoordinateStruct coordinate1, CoordinateStruct coordinate2)
    123.     {
    124.         float earthRadius = 6371000f;
    125.  
    126.         float lat1Rad = Mathf.Deg2Rad * coordinate1.latitude;
    127.         float lat2Rad = Mathf.Deg2Rad * coordinate2.latitude;
    128.         float lon1Rad = Mathf.Deg2Rad * coordinate1.longitude;
    129.         float lon2Rad = Mathf.Deg2Rad * coordinate2.longitude;
    130.  
    131.         float deltaLat = lat2Rad - lat1Rad;
    132.         float deltaLon = lon2Rad - lon1Rad;
    133.  
    134.         float a = Mathf.Sin(deltaLat / 2) * Mathf.Sin(deltaLat / 2) + Mathf.Cos(lat1Rad) * Mathf.Cos(lat2Rad) * Mathf.Sin(deltaLon / 2) * Mathf.Sin(deltaLon / 2);
    135.         float c = 2 * Mathf.Atan2(Mathf.Sqrt(a), Mathf.Sqrt(1 - a));
    136.         float distance = earthRadius * c;
    137.  
    138.         return distance;
    139.     }
    140.  
    141. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    What does this mean? What do you want? Scale it down if it's too big.

    Here is some possibly-relevant reading:

    https://en.wikipedia.org/wiki/Map_projection

    How to report your problem productively in the Unity3D forums:

    http://plbm.com/?p=220

    This is the bare minimum of information to report:

    - what you want <---------------- start here
    - what you tried
    - what you expected to happen
    - what actually happened, log output, variable values, and especially any errors you see
    - links to documentation you used to cross-check your work (CRITICAL!!!)
     
  3. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Try with this
    Code (csharp):
    1. static readonly float QUAD = .5f * MathF.PI;
    2. static readonly float TAU = 2f * MathF.PI;
    3.  
    4. static public Vector2 FromPolarAngle(float theta)
    5.   => new Vector2(MathF.Cos(theta), MathF.Sin(theta));
    6.  
    7. static public float ToPolarAngle(Vector2 v)
    8.   => Mod(MathF.Atan2(v.y, v.x), TAU);
    9.  
    10. /// <summary> Conversion from spherical to cartesian coordinates. </summary>
    11. /// <param name="theta"> Polar angle 0..Tau (top-down). </param>
    12. /// <param name="phi"> Azimuthal angle -Pi/2..+Pi/2 where 0 represents equator. </param>
    13. /// <returns> A unit vector. </returns>
    14. static public Vector3 FromSpherical(float theta, float phi) {
    15.   var th = FromPolarAngle(theta); var ph = FromPolarAngle(QUAD - phi);
    16.   return new Vector3(th.x * ph.y, ph.x, th.y * ph.y);
    17. }
    18.  
    19. static public Vector3 FromSpherical(Vector2 coords)
    20.   => FromSpherical(coords.x, coords.y);
    21.  
    22. /// <summary> Conversion from cartesian to spherical coordinates.
    23. /// Returns <see langword="true"/> on success, <see langword="false"/>
    24. /// otherwise. (Values are defined in any case.) </summary>
    25. /// <param name="spherical"> The resulting spherical unit coordinates. </param>
    26. /// <param name="magnitude"> Optional magnitude of the input vector.
    27. /// Leave at 1 when input vector is unit to avoid normalization. </param>
    28. static public bool ToSpherical(Vector3 v, out Vector2 spherical, float magnitude = 1f) {
    29.   var theta = MathF.Atan2(v.z, v.x);
    30.   theta = theta < 0f? theta + TAU : theta;
    31.   var im = (magnitude == 1f)? 1f : 1f / SubstZero(MathF.Max(0f, magnitude), float.NaN);
    32.   var phi = QUAD - MathF.Acos(v.y * im);
    33.   var success = true;
    34.   if(float.IsNaN(theta)) { theta = 0f; success = false; }
    35.   if(float.IsNaN(phi)) { phi = 0f; success = false; }
    36.   spherical = new Vector2(theta, phi);
    37.   return success;
    38. }
    39.  
    40. static public float SubstZero(float v, float subst, float epsilon = 1E-6f) => MathF.Abs(v) < epsilon? subst : v;
    41. static public float Mod(float n, float m) => (m <= 0f)? 0f : (n %= m) < 0f? n + m : n;
    You can now easily transform some 3D coordinates to longitude / latitude (in degrees) if you do
    Code (csharp):
    1. static public Vector2 ToLongLat(Vector3 coords, Vector3 center = default) {
    2.   coords -= center;
    3.   ToSpherical(coords, out var spherical, coords.magnitude);
    4.   if(spherical.x < 0f) spherical.x += 2f * MathF.PI;
    5.   return spherical * (180f / MathF.PI);
    6. }
    And back (from longitude / latitude in degrees, to some exact 3D point on the surface)
    Code (csharp):
    1. static public Vector3 FromLongLat(Vector2 longLat, Vector3 center = default, float radius = 1f)
    2.   => center + radius * FromSpherical(longLat * (MathF.PI / 180f));
    If this is too much for you, ask questions or don't try it! :)

    Edit: Noticed a typo in ToLongLat, should be 2f * MathF.PI. The bottom part is untested, but the spherical coords themselves should work.
     
    Last edited: Jun 13, 2023
    Bunny83 likes this.
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Btw if you intend to do crazy globe projections and use true geographic parameters (like the radius of Earth in meters, wow that's too much), I don't think 32-bit floats will suffice, but 64-bit doubles might! So make sure to translate my code to doubles (and also switch all MathF calls to just Math which is 64-bit). That should give you ample precision.
     
    Bunny83 likes this.
  5. unity_-fu_9nA3E-Z1ZA

    unity_-fu_9nA3E-Z1ZA

    Joined:
    Jul 13, 2019
    Posts:
    24
    no, I don't think I need to do such precise calculations! :)
    meanwhile I try to understand the code you wrote me, and I thank you so much for your help and support!
    I'll let you know if I managed to instantiate the gameobjects with greater precision and if so, I'll post the complete code that might be useful to someone else!
     
    orionsyndrome likes this.
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Noticed a typo (in post #3) just now. Fixed there.
     
    Last edited: Jun 13, 2023
  7. unity_-fu_9nA3E-Z1ZA

    unity_-fu_9nA3E-Z1ZA

    Joined:
    Jul 13, 2019
    Posts:
    24
    I'm really trying to understand the code you wrote, but I admit I'm having a lot of trouble, I don't understand how I can structure my code to pass the two parameters (latitude and longitude) and get x and z positions on my plane in unity that respects the distances in meters between one point and another.
    I tried to call the "FromLongLat" method which should return a vector3 but it instantiates the points attached to each other, with very little difference.
    Code (CSharp):
    1. void InstantiatePoints()
    2.     {
    3.         float distanzaTotale=0;
    4.         float distanzaTotaleGeografica = 0;
    5.         for (int i = 0; i < coordinateList.Count; i++)
    6.         {
    7.             CoordinateStruct coordinate = coordinateList[i];
    8.             GameObject pointObject = Instantiate(referenceObject);
    9.             Vector2 coordinateAttuali = new Vector2(coordinate.longitude, coordinate.latitude);
    10.             Vector3 position = FromLongLat(coordinateAttuali);
    11.             pointObject.transform.position = position;
    12.             PuntiList.Add(pointObject);
    13.             if (i > 0)
    14.             {
    15.                 float geographicDistance = CalculateGeographicDistance(coordinateList[i], coordinateList[i-1]);
    16.                 float distance = Vector3.Distance(PuntiList[i].transform.position, PuntiList[i-1].transform.position);
    17.                 Debug.Log($"distanza geografica = {geographicDistance}");
    18.                 Debug.Log($"distnza vettori = {distance}");
    19.                 distanzaTotale += distance;
    20.                 distanzaTotaleGeografica += geographicDistance;
    21.             }
    22.            
    23.         }
    24.         CoordinatesLoad();
    25.         Debug.Log(distanzaTotale);
    26.         Debug.Log(distanzaTotaleGeografica);
    27.  
    28.         //float geographicDistance = CalculateGeographicDistance(coordinateList[0], coordinateList[1]);
    29.         //float distance = Vector3.Distance(PuntiList[0].transform.position, PuntiList[1].transform.position);
    30.         //Debug.Log($"distanza geografica = {geographicDistance}");
    31.         //Debug.Log($"distnza vettori = {distance}");
    32.     }
    this is the first point instantiated!


    upload_2023-6-13_11-35-10.png

    this is the last instantiated point

    upload_2023-6-13_11-36-5.png

    as you can see from the inspector values there is not a big difference in distance!

    if i calculate the distance between the first coordinate and the last one, even with google maps, the distance between u two points is 800 meters!
     
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Well, you need to supply the radius to this call
    Code (csharp):
    1. Vector3 position = FromLongLat(coordinateAttuali);
    And this is why I said you need to amp up the precision. You can't possibly expect this to work in meters, when radius is 6371000 on average.

    With 32-bit floats the best you can do is
    Code (csharp):
    1. var earthRadiusKm = 6_371f;
    2. Vector3 position = FromLongLat(longLat, radius: earthRadiusKm);
    When encoded into floating-point, 6371000 has a pretty big exponent, and so you're sacrificing accuracy on the meter (unit) scale. This is because the priority is always on the most significant part of the number. That's just how floating point works. Only a 64-bit value can reasonably satisfy your specs. And I'm sure Google Maps doesn't rely on 32-bit floating points.

    (Note: In case you missed it, also make sure ToLongLat is up-to-date because it contained an error fixed in post #6)
     
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    As I said, to make use of the methods, you need to respect their signature. If a method asks for radius, don't just ignore it.

    Spherical coordinates work on unit spheres, because solving a unit sphere lets us solve any sphere. However you need to scale the result to a sphere of desired size.

    If you're curious about the mathematics used, you can find everything you need to know here. (If you observe any discrepancies, take into account that Unity is using a left-handed coordinate system and this is my own implementation catered for Unity specifically.)

    Btw, the only reason behind the existence of ToLongLat and FromLongLat is so that I can change the notation slightly (the longitude is measured as a signed angle from the prime meridian), convert degrees into radians and vice-versa, and introduce center and radius. This is merely a consumer convenience.
     
    Last edited: Jun 13, 2023
  10. unity_-fu_9nA3E-Z1ZA

    unity_-fu_9nA3E-Z1ZA

    Joined:
    Jul 13, 2019
    Posts:
    24
    not sure if I should use "spherical coordinates" since my intention is to instantiate gameobjects on a plane, then treat the data as if it were a Cartesian plane.
     
  11. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    And one other thing, the distances on a longitude/latitude grid are not uniform. They vary with latitude and altitude. For example a difference of one degree longitude is the greatest at the equator (*) and non-extant at the poles. On top of that, Earth is not a perfect sphere, but an oblate spheroid or geoid.

    *
    The distance in this context is actually computed as arc distance on the great circle with a central angle between two points. If you have two points on a sphere, you can find this central angle with Vector3.Angle.

    arc distance = angle * radius [in radians]

    So if the great circle aligns with the equator, you can see that longitudinal shift of just 1 degree equals ~111 km, which is around 1/360th of the equator that is said to be 40,075 km in length. A longitudinal shift of 1 degree on a different parallel would be shorter (in proportion with the cosine of latitude).
     
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Ok, makes sense if you're zoomed in enough, the lat/long system becomes indistinguishable from the planar coordinates. I was under impression that you were explicitly going after the true lat/long coordinate system.

    But then I'm not sure what the problem was. Simply transfer long to x and and lat to y (or z) and offset everything to bring it near the world origin.
     
  13. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,084
    A float only has 24-bits worth of precision which comes out to approximately seven digits. Everything beyond that is going to be gibberish. You can see this if you play around with floating point calculations like this one.

    https://www.h-schmidt.net/FloatConverter/IEEE754.html

    For example here is one of the coordinates from your string (8.93266215617905 44.40604327943526) and here is what it actually is (8.93266201019287109375 44.40604400634765625). I've applied bolding to show what little of it is still accurate.

    With every calculation you perform the number will become a little less accurate. If you want to maintain as much of that accuracy as possible you will want to use double/decimal in your calculations and only turn it into a float when you're ready to turn it into a Vector3.
     
    Last edited: Jun 13, 2023
    CodeRonnie likes this.
  14. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    If you have a decimal representation of a floating point number that has 7 significant digits or less, it will be converted accurately into a single-precision floating-point value. If you have a single-precision floating-point value, and you round the actual value stored by the bits into a 9 digit decimal string representation, that 9 digit string will always parse back to the original floating-point value in bits that it was taken from. The same pattern applies to double-precision floating except that an original 15 digit number will parse accurately into a double, and an actual double can be rounded to a 17 digit string that will parse back into the double it was rounded down from.
     
  15. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    Also, floating-point numbers can represent much larger values than their integer counterparts, but when the integral part of the number gets really large, they can represent less and less of a fractional value. If a floating-point variable holds a very large integral value (I think it's just in the millions for floats off the top of my head) then it can even lose integral digit precision. So, a difference of 1.0f unit between two large numbers may be something you literally can't hold in the bits.

    EDIT: In the tens to hundred millions, duh, once you hit 8 digits for your starting decimal number you're trying to store.
     
    Last edited: Jun 13, 2023
  16. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    Using that link Ryiah provided you can see for yourself that any number that you try to represent between 99,999,992 and 100,000,000 will actually get converted to one of those two nearest numbers because that's all the bits can represent as a single-precision float.
     
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    As a wise man once said,

    "Think of [floating point] as JPEG of numbers." - orionsyndrome on the Unity3D Forums
     
    orionsyndrome and CodeRonnie like this.