How to get accurate in-game coordinates based on GPS coordinates on a Lambert2008 projection?

Hi, I’m building a geo-localisation game where people have to go to a specific location.

I want to display a map of their surroundings, and place markers on that map to indicate

  • where the player is
  • the target location

I found some bits of code on the net that allows me to turn a gps coordinate into an in-game coordinate, but the problem is that the result is always a few (about 20 or so) meters off. But 20m is a lot for the purpose of my app.

All the code I found assumes that the earth is a sphere, but since the earth is not exactly a sphere I guess that’s where my difference comes from. Here’s the code I’m currently using.

    public static Vector3 GpsToGameCoord(Vector2 gpsCoordinates, float scale)
    {
        Vector3 result = Vector3.zero;
        // latitude is vertical, longitude is horizontal, so we need to inverse the values.
        result.y = gpsCoordinates.x * Mathf.Deg2Rad * earthRadius;
        result.x = gpsCoordinates.y * Mathf.Deg2Rad * earthRadius;
        result *= scale;
        return result;
    }

Is there a library out there that I could use to have more precision? An asset would work too, but I don’t need a full blown geolocalisation asset bundled with all kinds of thing I don’t need.


Edit: answered some questions in the comments


I’m using official gouvernement maps (the ones they use in the military etc…) the projection is Lambert2008 ( http://www.ngi.be/FR/FR2-1-7.shtm )


The waypoints coordinates come from Google maps, they’re entered directly in the inspector, so don’t rely on GPS accuracy. The player location however depends on device GPS, obviously.


The game always happen at the same location (it’s for a hotel resort, game happens in the woods nearby), so I only need a tiny patch of map like you say…


For simplification, my map always points north, I don’t try to rotate it according to player orientation. To position the waypoints, I pick a point on the map as my center point (recognisable landmark, so I can have accurate GPS coordinates from google maps). All the waypoints and player ping are positioned relative to that center point using my GpsToGameCoord method.


To scale my map properly, I pick three coordinates to triangulate the distance between each of them, and then scale the map manually in the editor (of course keeping the same aspect ratio) so that the waypoints match their location on the map. My problem though, is that with the waypoints I get, there is no way to scale the map in order to achieve a satisfying correspondance to my triangulation.


Followup:
I modified the GpsToGameCoord method when I realised the radius I’m using should be the radius of the latitude circle at my coordinates, and not the earth radius. I know it’s still an approximation, because the method still assumes the earth is a sphere and not an elipsoïd.

    public static Vector3 GpsToGameCoord(Vector2 gpsCoordinates, float scale)
    {
        Vector3 result = Vector3.zero;
        // latitude is vertical, longitude is horizontal, so we need to inverse the values.
        result.y = gpsCoordinates.x * Mathf.Deg2Rad * poleRadius;
        result.x = gpsCoordinates.y * Mathf.Deg2Rad * eqRadius * Mathf.Cos(gpsCoordinates.x * Mathf.Deg2Rad);
        result *= scale;
        return result;
    }

Here’s what the problem was with the old implementation:

alt text


Here’s what the game looks like with the new implementation of my method. Everything is actually much closer.

alt text


As you can see it’s a lot better.

Your question does not have a valid answer since in-game coordinates are arbitrary virtual coordinates. You have to give them some meaning / relation to the real world. In your case it entirely depends on where your “map” comes from, what projection it uses and what coordinate system it uses. Most maps are WGS84 based but don’t have to. The most common map projection is the Mercator projection but again it doesn’t have to be. Finally if you have only a tiny patch of map you have to know specific reference points on your map (the 4 corners) in order to do any conversion.

Like Casiell said GPS isn’t really that precise to begin with and the accuracy depends heavily on various factors (like how many satellites are in view, how much the signals are distorted due to atmospheric effects or other obstacles, …). That’s why for higher precision geo location DGPS is used (which requires realtime access to the differential data from a DGPS network). For indoor navigation GPS is never a good choice due to the shielding of the walls and ceilings. That’s why there are seperate IPS systems

Finally I don’t quite get what your code is supposed to calculate. GPS coordinates represent a polar coordinate system. So the latitude and longitude represent angles. You convert the angles from degrees into radians and then multiply be the earthRadius? What do you expect as a result? Keep in mind that the Mercator projection get more and more distorted on the longitude the further you get north or south.

So there’s a lot details missing on your side without that we can’t really answer your question.

Thanks to @Bunny83 I looked up the projection used for my map which is a specific projection used by the Belgian National Geographic Institute. On their website I found this pdf with all the formulas used to convert geographic coordinates into Lambert2008 coordinates.

http://www.ngi.be/Common/Lambert2008/Transformation_Geographic_Lambert_FR.pdf

I made a static class to transcribe those formulas into code. now I get results with a 24cm precision (as long as I use Belgian official maps).

If you’re wondering, I use double precision because it’s actually faster than floats (using System.Math instead of UnityEngine.Mathf )

Since this is quite specific, I updated the original question title. Maybe this helps someone in the future, who knows.

Here’s the code, feel free to use it for your own inscrutable purposes.

    public static class Lambert2008
    {
        public const int a = 6378137;
        public const double f = 1 / 298.257222101;
        public const double lowerLatitude = 49.8333333 * Mathf.Deg2Rad;
        public const double upperLatitude = 51.1666667 * Mathf.Deg2Rad;
        public const double originLatitude = 50.797815 * Mathf.Deg2Rad;
        public const double originLongitude = 4.359215833 * Mathf.Deg2Rad;
        public const int originX = 649328;
        public const int originY = 665262;
        public static double Excentricity {get => System.Math.Sqrt((2 * f) - (f * f));}

        static double MLower { get => M(lowerLatitude);}
        static double MUpper { get => M(upperLatitude);}

        static double M(double latitude)
        {
            return System.Math.Cos(latitude) / System.Math.Sqrt(1 - (Excentricity * Excentricity * System.Math.Pow(System.Math.Sin(latitude), 2)));
        }

        static double TLower { get => T(lowerLatitude); }
        static double TUpper { get => T(upperLatitude); }
        static double TOrigin { get => T(originLatitude); }

        static double T(double latitude)
        {
            return System.Math.Tan(Mathf.PI / 4 - latitude / 2) / System.Math.Pow((1 - Excentricity * System.Math.Sin(latitude)) / (1 + Excentricity * System.Math.Sin(latitude)), Excentricity / 2);
        }

        static double N { get => (System.Math.Log(MLower) - System.Math.Log(MUpper)) / (System.Math.Log(TLower) - System.Math.Log(TUpper)); }

        static double G { get => MLower / (N * System.Math.Pow(TLower, N)); }

        static double ROrigin { get => R(TOrigin); }

        static double R (double t)
        {
            return a * G * System.Math.Pow(t, N);
        }

        public static Vector2 FromGeographic(Vector2 coordinates)
        {

            double t = T((double)coordinates.x * Mathf.Deg2Rad);
            double r = R(t);
            double angle = N * ((double)coordinates.y * Mathf.Deg2Rad - originLongitude);
            Vector2 lambertCoord = new Vector2
            {
                x = (float) (originX + r * System.Math.Sin(angle)),
                y = (float) (originY + ROrigin - r * System.Math.Cos(angle))
            };
            return lambertCoord;

        }
    }