Convert GPS position to Vector3 (X, Y, Z)

Hello, I’m looking for a long time for a way to convert a GPS location (lat, lon) to a unity proportional location, I found a script by MichaelTaylor3D that I honestly don’t know if it works, but I can’t even tell when I try.

Try using your script in another Script I made, but I don’t know if I’m doing things right or what I should change.

This is the script I found on the internet:

//Copyright 2013 MichaelTaylor3D
//www.michaeltaylor3d.com

using UnityEngine;

public sealed class GPSEncoder {

    /////////////////////////////////////////////////
    //////-------------Public API--------------//////
    /////////////////////////////////////////////////
   
    /// <summary>
    /// Convert UCS (X,Y,Z) coordinates to GPS (Lat, Lon) coordinates
    /// </summary>
    /// <returns>
    /// Returns Vector2 containing Latitude and Longitude
    /// </returns>
    /// <param name='position'>
    /// (X,Y,Z) Position Parameter
    /// </param>
    public static Vector2 USCToGPS(Vector3 position)
    {
        return GetInstance().ConvertUCStoGPS(position);
    }
   
    /// <summary>
    /// Convert GPS (Lat, Lon) coordinates to UCS (X,Y,Z) coordinates
    /// </summary>
    /// <returns>
    /// Returns a Vector3 containing (X, Y, Z)
    /// </returns>
    /// <param name='gps'>
    /// (Lat, Lon) as Vector2
    /// </param>
    public static Vector3 GPSToUCS(Vector2 gps)
    {
        return GetInstance().ConvertGPStoUCS(gps);
    }
   
    /// <summary>
    /// Convert GPS (Lat, Lon) coordinates to UCS (X,Y,Z) coordinates
    /// </summary>
    /// <returns>
    /// Returns a Vector3 containing (X, Y, Z)
    /// </returns>
    public static Vector3 GPSToUCS(float latitude, float longitude)
    {
        return GetInstance().ConvertGPStoUCS(new Vector2(latitude,longitude));
    }
   
    /// <summary>
    /// Change the relative GPS offset (Lat, Lon), Default (0,0),
    /// used to bring a local area to (0,0,0) in UCS coordinate system
    /// </summary>
    /// <param name='localOrigin'>
    /// Referance point.
    /// </param>
    public static void SetLocalOrigin(Vector2 localOrigin)
    {
        GetInstance()._localOrigin = localOrigin;
    }
       
    /////////////////////////////////////////////////
    //////---------Instance Members------------//////
    /////////////////////////////////////////////////
   
    #region Singleton
    private static GPSEncoder _singleton;
   
    private GPSEncoder()
    {
       
    }
   
    private static GPSEncoder GetInstance()
    {
        if(_singleton == null)
        {
            _singleton = new GPSEncoder();
        }
        return _singleton;
    }
    #endregion
   
    #region Instance Variables
    private Vector2 _localOrigin = Vector2.zero;
    private float _LatOrigin { get{ return _localOrigin.x; }}   
    private float _LonOrigin { get{ return _localOrigin.y; }}

    private float metersPerLat;
    private float metersPerLon;
    #endregion
   
    #region Instance Functions
    private void FindMetersPerLat(float lat) // Compute lengths of degrees
    {
        // Set up "Constants"
        float m1 = 111132.92f;    // latitude calculation term 1
        float m2 = -559.82f;        // latitude calculation term 2
        float m3 = 1.175f;      // latitude calculation term 3
        float m4 = -0.0023f;        // latitude calculation term 4
        float p1 = 111412.84f;    // longitude calculation term 1
        float p2 = -93.5f;      // longitude calculation term 2
        float p3 = 0.118f;      // longitude calculation term 3
       
        lat = lat * Mathf.Deg2Rad;
   
        // Calculate the length of a degree of latitude and longitude in meters
        metersPerLat = m1 + (m2 * Mathf.Cos(2 * (float)lat)) + (m3 * Mathf.Cos(4 * (float)lat)) + (m4 * Mathf.Cos(6 * (float)lat));
        metersPerLon = (p1 * Mathf.Cos((float)lat)) + (p2 * Mathf.Cos(3 * (float)lat)) + (p3 * Mathf.Cos(5 * (float)lat));      
    }

    private Vector3 ConvertGPStoUCS(Vector2 gps) 
    {
        FindMetersPerLat(_LatOrigin);
        float zPosition  = metersPerLat * (gps.x - _LatOrigin); //Calc current lat
        float xPosition  = metersPerLon * (gps.y - _LonOrigin); //Calc current lat
        return new Vector3((float)xPosition, 0, (float)zPosition);
    }
   
    private Vector2 ConvertUCStoGPS(Vector3 position)
    {
        FindMetersPerLat(_LatOrigin);
        Vector2 geoLocation = new Vector2(0,0);
        geoLocation.x = (_LatOrigin + (position.z)/metersPerLat); //Calc current lat
        geoLocation.y = (_LonOrigin + (position.x)/metersPerLon); //Calc current lon
        return geoLocation;
    }
    #endregion
}

and this would be the other Script I made, which uses this previous one:

using System.Collections;
using UnityEngine;

public class GPS: MonoBehaviour
{

    public static GPS Instance { set; get; }

    public float latitude;
    public float longitude;

    private void Start()
    {
        Instance = this;
        Vector3 localOrigin = GPSEncoder.GPSToUCS(12.31565f, -12.53465f);
        Vector2 vector2 = new Vector2(localOrigin.x, localOrigin.z);
        GPSEncoder.SetLocalOrigin(vector2);
        StartCoroutine(StartLocationService());
    }

    private void Update()
    {
        Vector3 coord = GPSEncoder.GPSToUCS(latitude, longitude);
        transform.position = coord;
    }

    private IEnumerator StartLocationService()
    {
        if(!Input.location.isEnabledByUser)
        {
            Debug.Log("Usuario no activo el GPS");
            yield break;
        }

        Input.location.Start();
        int maxWait = 20;
        while(Input.location.status == LocationServiceStatus.Initializing && maxWait > 0)
        {
            yield return new WaitForSeconds(1);
            maxWait--;
        }

        if(maxWait <= 0)
        {
            Debug.Log("Tiempo agotado");
            yield break;
        }

        if (Input.location.status == LocationServiceStatus.Failed)
        {
            Debug.Log("Imposible determinar la ubicación del dispositivo");
            yield break;
        }

        latitude = Input.location.lastData.latitude;
        longitude = Input.location.lastData.longitude;
        yield break;
    }
}

To make it clear what I want:

I want to navigate a real life 2D map, so I want X coordinate to be the 0, 0, 0 point in unity, so that when I’m standing there, it’s on the map and I can see it on the mobile device. The problem is that with what I did, the camera to which I apply the script goes to I don’t know where (to a place all black, I can’t even see anything when I go there in the scene, it seems like an unknown dimension), without However, in its position X and Z it is not very far from the point 0, 0, I don’t understand what happens there.

I hope you can help me and if I have an error, you can explain me what it is :slight_smile:

PS: I’m sorry if the explanation is not very well understood.

First check the math. Mock up known GPS coordinate inputs and make sure they come through as expected.

In other words if you expect your kitchen to be at (-1,-5) and your bedroom to be at (+3,+1), put those two GPS coordinates in and make sure that’s what you get.

Beyond that, keep debugging!

You must find a way to get the information you need in order to reason about what the problem is.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: How To - Capturing Device Logs on iOS or this answer for Android: How To - Capturing Device Logs on Android

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

When in doubt, print it out!™

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

Thank you very much, it’s something super simple but I didn’t even think about it.

Now I understand why the camera goes to an unknown dimension:


Should I think that the script I got from the internet doesn’t work? I don’t know if it’s me doing something wrong or that’s it.

I’m naturally skeptical so that’s the first place my brain goes.

But there’s also the possibility there’s nothing wrong with the script and it is just intended to be used a different way. That would be up to the creator to correctly express the expectations of your use of the script, as well as to characterize the limitations of that script.

It’s kinda like if I give you a car to use but then it turns out there are no wheels on it. “I expect you to bring your OWN wheels, I’m not letting you wear out my wheels!” It’s all in the “what are the assumptions here.”

But there’s also the problem that latitude / longitude coordinates are on a sphere (the world), yet all paper maps are nominally flat. This is achieved by a process called projection and there are many ways: conformal, mercator, cylindrical, etc., and each of those ways has input parameters (reference datum, distortion corrections, etc.)

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

1 Like

I know this is not a solution here but I had made a manual compass by knowing the position of the sun, the time of day and date, the country I was in and the direction i was facing.

I assume if you are taking coordinate data from gps sat, the scale of your sphere will need to be pretty accurate to and a scaled close fit to the likes of google earth. And you will likely need to depower your coordinate to replicate the scale. It would take a lot of tweaking and reference to known systems that we understand to be true.

Thanks to both of you, yes it’s a big problem, I still think I found a way to do it, but the GPS is 0% accurate, at least in my case, I thought the accuracy would be better, but no. Do you have any other ideas how to achieve this? I have something done with ARCore, but the problem is that many devices are not compatible with it.

Check up one level in the forum hierarchy… there is an AR/VR/XR group of forums. A lot of those apps do GPS stuff so maybe folks there have a better more practical bead on what you’re after.

Thank you very much, for now the test I did is going “good”, I’ll see tomorrow if it goes better.