Get SW & NE corners of Google Static Maps API
Asked Answered
C

1

2

I'm having a problem with getting the bounds of my Google Static Map in my Unity application. I've tried multiple answers found here on stackoverflow.

.NET related question

Javascript related question

The map I'm trying to calculate the corners for is this one.

First I tried translating marcelo his code from javascript to C#. Which is translated into the following class.

using System;
using UnityEngine;
public class MercatorProjection
{
    static double MERCATOR_RANGE = 256;
    Point pixelsOrigin;
    double pixelsPerLonDegree;
    double pixelsPerLonRadian;

    public MercatorProjection()
    {
        pixelsOrigin = new Point(MERCATOR_RANGE / 2, MERCATOR_RANGE / 2);
        pixelsPerLonDegree = MERCATOR_RANGE / 360;
        pixelsPerLonRadian = MERCATOR_RANGE / (2 * Math.PI);
    }

    double bound(double value, double opt_min, double opt_max)
    {
        if (opt_min != 0)
            value = Math.Max(value, opt_min);
        if (opt_max != 0)
            value = Math.Min(value, opt_max);
        return value;
    }

    double degreesToRadians(double deg)
    {
        return deg * (Math.PI / 180);
    }

    double radiansToDegrees(double rad)
    {
        return rad / (Math.PI / 180);
    }

    Point fromLatLonToPoint(Coordinates latLon)
    {
        Point point = new Point();
        Point origin = pixelsOrigin;

        point.X = origin.X + latLon.Longitude * pixelsPerLonDegree;

        double sinY = bound(Math.Sin(degreesToRadians(latLon.Latitude)), -0.9999, 0.9999);

        point.Y = origin.Y + 0.5 * Math.Log((1 + sinY) / (1 - sinY)) * -pixelsPerLonRadian;

        return point;
    }

    Coordinates fromPointToLatlon(Point point)
    {
        Point origin = pixelsOrigin;
        Coordinates latLon = new Coordinates();

        latLon.Latitude = (point.X - origin.X) / pixelsPerLonDegree;
        double latRadians = (point.Y - origin.Y) / -pixelsPerLonRadian;
        latLon.Longitude = radiansToDegrees(2 * Math.Atan(Math.Exp(latRadians)) - Math.PI / 2);

        return latLon;
    }

    public void GetCorners(Coordinates center, float zoom, float mapWidth, float mapHeight)
    {
        double scale = Math.Pow(2, zoom);
        Point centerPx = fromLatLonToPoint(center);
        Point SWPoint = new Point(centerPx.X - (mapWidth / 2) / scale, centerPx.Y + (mapHeight / 2) / scale);
        Coordinates SWLatLon = fromPointToLatlon(SWPoint);
        Debug.Log(SWLatLon.Latitude + " " + SWLatLon.Longitude + " " + SWPoint.X + " " + SWPoint.Y);
    }
}

So at first I tried only getting the South West corner of the map and it's pretty close, but it's a bit off.

MercatorProjection.GetCorners(new Coordinates(Coordinates.Longitude, Coordinates.Latitude), 16, 640, 640);

Brought me this result.

Since so many people voted First I tried translating marcelo his answer up I figured I did something wrong during the translation of the code to C#.

Than I stumbled across peterjb's answer who already did the translation of the javascript answer to C# and even gave examples where his code got the right bounds of a Google static map. So after trying peterjb's code in the following class;

using System;

public static class GoogleMapsAPI{

    static GoogleMapsAPI()
    {
        OriginX = TileSize / 2;
        OriginY = TileSize / 2;
        PixelsPerLonDegree = TileSize / 360.0;
        PixelsPerLonRadian = TileSize / (2 * Math.PI);
    }

    public static int TileSize = 256;
    public static double OriginX, OriginY;
    public static double PixelsPerLonDegree;
    public static double PixelsPerLonRadian;

    public static double DegreesToRadians(double deg)
    {
        return deg * Math.PI / 180.0;
    }

    public static double RadiansToDegrees(double rads)
    {
        return rads * 180.0 / Math.PI;
    }

    public static double Bound(double value, double min, double max)
    {
        value = Math.Min(value, max);
        return Math.Max(value, min);
    }

    //From Lat, Lon to World Coordinate X, Y. I'm being explicit in assigning to
    //X and Y properties.
    public static Coordinate Mercator(double latitude, double longitude)
    {
        double siny = Bound(Math.Sin(DegreesToRadians(latitude)), -.9999, .9999);

        Coordinate c = new Coordinate(0, 0);
        c.X = OriginX + longitude * PixelsPerLonDegree;
        c.Y = OriginY + .5 * Math.Log((1 + siny) / (1 - siny)) * -PixelsPerLonRadian;

        return c;
    }

    //From World Coordinate X, Y to Lat, Lon. I'm being explicit in assigning to
    //Latitude and Longitude properties.
    public static Coordinate InverseMercator(double x, double y)
    {
        Coordinate c = new Coordinate(0, 0);

        c.Longitude = (x - OriginX) / PixelsPerLonDegree;
        double latRadians = (y - OriginY) / -PixelsPerLonRadian;
        c.Latitude = RadiansToDegrees(Math.Atan(Math.Sinh(latRadians)));

        return c;
    }

    public static MapCoordinates GetBounds(Coordinate center, int zoom, int mapWidth, int mapHeight)
    {
        var scale = Math.Pow(2, zoom);

        var centerWorld = Mercator(center.Latitude, center.Longitude);
        var centerPixel = new Coordinate(0, 0);
        centerPixel.X = centerWorld.X * scale;
        centerPixel.Y = centerWorld.Y * scale;

        var NEPixel = new Coordinate(0, 0);
        NEPixel.X = centerPixel.X + mapWidth / 2.0;
        NEPixel.Y = centerPixel.Y - mapHeight / 2.0;

        var SWPixel = new Coordinate(0, 0);
        SWPixel.X = centerPixel.X - mapWidth / 2.0;
        SWPixel.Y = centerPixel.Y + mapHeight / 2.0;

        var NEWorld = new Coordinate(0, 0);
        NEWorld.X = NEPixel.X / scale;
        NEWorld.Y = NEPixel.Y / scale;

        var SWWorld = new Coordinate(0, 0);
        SWWorld.X = SWPixel.X / scale;
        SWWorld.Y = SWPixel.Y / scale;

        var NELatLon = InverseMercator(NEWorld.X, NEWorld.Y);
        var SWLatLon = InverseMercator(SWWorld.X, SWWorld.Y);

        return new MapCoordinates() { NorthEast = NELatLon, SouthWest = SWLatLon };
    }
}
public class MapCoordinates
{
    public Coordinate SouthWest { get; set; }
    public Coordinate NorthEast { get; set; }
}

public class Coordinate
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public double Y { get { return Latitude; } set { Latitude = value; } }
    public double X { get { return Longitude; } set { Longitude = value; } }

    public Coordinate(double lng, double lat)
    {
        Latitude = lat;
        Longitude = lng;
    }

    public override string ToString()
    {
        return Math.Round(X, 6).ToString() + ", " + Math.Round(Y, 6).ToString();
    }
}

I tried calculating the SW and NE corners again by calling the following

Coordinates = new Coordinate(51.445691, 5.460318);
var result = GoogleMapsAPI.GetBounds(Coordinates, 16, 640, 640);
Debug.Log("SouthWest: "+result.SouthWest.ToString() + " NorthEast: "+result.NorthEast.ToString());

Output:

SouthWest: 51.438825, 5.453483 NorthEast: 51.452557, 5.467153

The SouthWest coordinates are the exact same as my own translation of the javascript. So since peterjb claimed in his answer that his solution worked for a certain area in Belgium, I tried the code on the same area. However, the result again was quite a bit off.

So quite randomly I tried using the calculation for a town in Nigeria named Kano / Static map and to my surprise, the SouthWest corner was extremely accurate!

Output:

SouthWest: 12.015251, 8.527824 NorthEast: 12.028983, 8.541404

SouthWest position.

I've tried multiple other area's in the world afterwards, but it often was way off and sometimes even hundreds of miles. So maybe this town in Nigeria was just an coincidence, but I'm hoping someone has an explanation and/or solution for me.

Special thanks to peterjb and marcelo

Caz answered 3/5, 2016 at 15:17 Comment(4)
Related info: alastaira.wordpress.com/2011/01/23/…Abbeyabbi
Hi Leniaal, I think the issue is that your coordinate constructor takes parameters as lng, lat but you are passing in lat, lng. The Kano example probably got close because the lat and lng are almost the same. I hope this helps, let me know if it's still not quite there.Prodigal
Hey @Prodigal thanks for your time and interest. You are right, I can't believe I overlooked this. I'm very happy you noticed it because I was staring blind on the problem, thanks a ton! You could add it as an answer if you like so I can mark this question answered.Caz
No problem, glad I can help. I've definitely had similar experiences before :)Prodigal
G
0

The constructor for Coordinate class accepts (lng, lat) instead of (lat, lng) as you might expect, so the parameters are in a different order than you are passing in.

I also noticed that Coordinate's ToString override prints as (lng, lat) as well, so it could be confusing if you expect to see (lat, lng).

Simply swap both of these in the static API.

Graze answered 24/8, 2016 at 18:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.