I have a Mercator projection map as a JPEG and I would like to know how to relate a given x, y coordinate to its latitude and longitude. I've looked at the Gudermannian function but I honestly don't understand how to take that function and apply it. Namely, what input is it expecting? The implementation I found (JavaScript) seems to take a range between -PI and PI, but what's the correlation between my y-value in pixels and that range?
Also, I found this function which takes a latitude and returns the tile for Google Maps, which also uses Mercator. It would seem that if I knew how to inverse this function, I'd be pretty close to having my answer.
/*<summary>Get the vertical tile number from a latitude
using Mercator projection formula</summary>*/
    private int getMercatorLatitude(double lati)
    {
        double maxlat = Math.PI;
        double lat = lati;
        if (lat > 90) lat = lat - 180;
        if (lat < -90) lat = lat + 180;
        // conversion degre=>radians
        double phi = Math.PI * lat / 180;
        double res;
        //double temp = Math.Tan(Math.PI / 4 - phi / 2);
        //res = Math.Log(temp);
        res = 0.5 * Math.Log((1 + Math.Sin(phi)) / (1 - Math.Sin(phi)));
        double maxTileY = Math.Pow(2, zoom);
        int result = (int)(((1 - res / maxlat) / 2) * (maxTileY));
        return (result);
    }
Here is some code for you... Let me know if you need more explanation.
    /// <summary>
    /// Calculates the Y-value (inverse Gudermannian function) for a latitude. 
    /// <para><see cref="http://en.wikipedia.org/wiki/Gudermannian_function"/></para>
    /// </summary>
    /// <param name="latitude">The latitude in degrees to use for calculating the Y-value.</param>
    /// <returns>The Y-value for the given latitude.</returns>
    public static double GudermannianInv(double latitude)
    {
        double sign = Math.Sign(latitude);
        double sin = Math.Sin(latitude * RADIANS_PER_DEGREE * sign);
        return sign * (Math.Log((1.0 + sin) / (1.0 - sin)) / 2.0);
    }
    /// <summary>
    /// Returns the Latitude in degrees for a given Y.
    /// </summary>
    /// <param name="y">Y is in the range of +PI to -PI.</param>
    /// <returns>Latitude in degrees.</returns>
    public static double Gudermannian(double y)
    {
        return Math.Atan(Math.Sinh(y)) * DEGREES_PER_RADIAN;
    }
Erich Mirabal's answer was completely correct (if not completely complete).
I have just tested it using a 'theoretical 256x256 Mercator tile' (Google's single tile version of a world map).
/tile0.png/)
Here's a little more code (JavaScript, but easy to follow) to elucidate.
I live in Australia, at a latitude of about -33°.
convertRange(
    GudermannianInv(-33), 
    [Math.PI, - Math.PI], 
    [0, 256]
);
152.88327883810192
If you count 152 pixels down from the top of the tile, you will find Australia. I have also verified this answer is correct by comparing the result to known-good functions.
To be sure, we can reverse that calculation:
Gudermannian(
    convertRange(
        152.88, 
        [0, 256], 
        [Math.PI, - Math.PI]
));
And we are returned -32.99613291758226.
The tricky part isn't in the Gudermannian function, but in the conversion between two scales.
Fortunately, being rather lazy, and hating these kind of scaling problems, I already had a little function to do that messy conversion for me.
    /**
     * convert number from _n_ of r1[0] .. r1[1] to _n_ of r2[0] .. r2[1]
     * @example `convertRange( 5, [0, 10], [0, 100] ) === 50`
     *
     * @param {number} value
     * @param {array<number>} r1 old range
     * @param {array<number>} r2 new range
     * @returns {number} value adjusted for new range
     */
    function convertRange( value, r1, r2 ) {
        return ( value - r1[0] )
             * ( r2[1] - r2[0] )
             / ( r1[1] - r1[0] )
             +   r2[0];
    }
And the JavaScript versions of the original functions are naturally:
function Gudermannian(y) {
    return Math.atan(Math.sinh(y)) * (180 / Math.PI)
}
function GudermannianInv(latitude)
{
    var sign = Math.sign(latitude);
    var sin  = Math.sin(
                          latitude 
                        * (Math.PI / 180) 
                        * sign
    );
    return sign * (
        Math.log(
            (1 + sin) / (1 - sin)
        ) / 2
    );
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With