Mercator longitude and latitude calculations to x and y on a cropped map (of the UK)

29,991

Solution 1

I think it's worthwhile to keep in mind that not all flat maps are Mercator projections. Without knowing more about that map in particular, it's hard to be sure. You may find that most maps of a small area of the world are more likely to be a conical type projection, where the area of interest on the map is "flatter" than would be on a global Mercator projection. This is especially more important the further you get away from the equator (and the UK is far enough away for it to matter).

You may be able to get "close enough" using the calculations you're trying, but for best accuracy you may want to either use a map with a well-defined projection, or create your own map.

Solution 2

I wrote a function which does exactly what you were looking for. I know it's a bit late, but maybe there are some other people interested in.

You need a map which is a mercator projection and you need to know the lat / lon positions of your map. You get great customized mercator maps with perfect matching lat / lon positions from TileMill which is a free software from MapBox!

I'm using this script and tested it with some google earth positions. It worked perfect on a pixel level. Actually I didnt test this on different or larger maps. I hope it helps you!

Raphael ;)

<?php

$mapWidth = 1500;
$mapHeight = 1577;

$mapLonLeft = 9.8;
$mapLonRight = 10.2;
$mapLonDelta = $mapLonRight - $mapLonLeft;

$mapLatBottom = 53.45;
$mapLatBottomDegree = $mapLatBottom * M_PI / 180;

function convertGeoToPixel($lat, $lon)
{
    global $mapWidth, $mapHeight, $mapLonLeft, $mapLonDelta, $mapLatBottom, $mapLatBottomDegree;

    $x = ($lon - $mapLonLeft) * ($mapWidth / $mapLonDelta);

    $lat = $lat * M_PI / 180;
    $worldMapWidth = (($mapWidth / $mapLonDelta) * 360) / (2 * M_PI);
    $mapOffsetY = ($worldMapWidth / 2 * log((1 + sin($mapLatBottomDegree)) / (1 - sin($mapLatBottomDegree))));
    $y = $mapHeight - (($worldMapWidth / 2 * log((1 + sin($lat)) / (1 - sin($lat)))) - $mapOffsetY);

    return array($x, $y);
}

$position = convertGeoToPixel(53.7, 9.95);
echo "x: ".$position[0]." / ".$position[1];

?>

Here is the image I created with TileMill and which I used in this example: map image

Solution 3

In addition to what Raphael Wichmann has posted (Thanks, by the way!), here is the reverse function, in actionscript :

function convertPixelToGeo(tx:Number, ty:Number):Point
{   
    /* called worldMapWidth in Raphael's Code, but I think that's the radius since it's the map width or circumference divided by 2*PI  */   
    var worldMapRadius:Number = mapWidth / mapLonDelta * 360/(2 * Math.PI);     
    var mapOffsetY:Number = ( worldMapRadius / 2 * Math.log( (1 + Math.sin(mapLatBottomRadian) ) / (1 - Math.sin(mapLatBottomRadian))  ));
    var equatorY:Number = mapHeight + mapOffsetY;   
    var a:Number = (equatorY-ty)/worldMapRadius;

    var lat:Number = 180/Math.PI * (2 * Math.atan(Math.exp(a)) - Math.PI/2);
    var long:Number = mapLonLeft+tx/mapWidth*mapLonDelta;
    return new Point(lat,long);
}

Solution 4

Here's another Javascript implementation. This is a simplification of @Rob Willet's solution above. Instead of requiring computed values as parameters to the function, it only requires essential values and computes everything from them:

function convertGeoToPixel(latitude, longitude,
                  mapWidth, // in pixels
                  mapHeight, // in pixels
                  mapLngLeft, // in degrees. the longitude of the left side of the map (i.e. the longitude of whatever is depicted on the left-most part of the map image)
                  mapLngRight, // in degrees. the longitude of the right side of the map
                  mapLatBottom) // in degrees.  the latitude of the bottom of the map
{
    const mapLatBottomRad = mapLatBottom * Math.PI / 180
    const latitudeRad = latitude * Math.PI / 180
    const mapLngDelta = (mapLngRight - mapLngLeft)

    const worldMapWidth = ((mapWidth / mapLngDelta) * 360) / (2 * Math.PI)
    const mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomRad)) / (1 - Math.sin(mapLatBottomRad))))

    const x = (longitude - mapLngLeft) * (mapWidth / mapLngDelta)
    const y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(latitudeRad)) / (1 - Math.sin(latitudeRad)))) - mapOffsetY)

    return {x, y} // the pixel x,y value of this point on the map image
}

Solution 5

I've converted the PHP code provided by Raphael to JavaScript and can confirm it worked and this code works myself. All credit to Raphael.

/*
var mapWidth = 1500;
var mapHeight = 1577;

var mapLonLeft = 9.8;
var mapLonRight = 10.2;
var mapLonDelta = mapLonRight - mapLonLeft;

var mapLatBottom = 53.45;
var mapLatBottomDegree = mapLatBottom * Math.PI / 180;
*/

function convertGeoToPixel(latitude, longitude ,
                           mapWidth , // in pixels
                           mapHeight , // in pixels
                           mapLonLeft , // in degrees
                           mapLonDelta , // in degrees (mapLonRight - mapLonLeft);
                           mapLatBottom , // in degrees
                           mapLatBottomDegree) // in Radians
{
    var x = (longitude - mapLonLeft) * (mapWidth / mapLonDelta);

    latitude = latitude * Math.PI / 180;
    var worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
    var mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));
    var y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(latitude)) / (1 - Math.sin(latitude)))) - mapOffsetY);

    return { "x": x , "y": y};
}
Share:
29,991
betamax
Author by

betamax

Updated on July 09, 2022

Comments

  • betamax
    betamax almost 2 years

    I have this image. It's a map of the UK (not including Southern Ireland):

    UK Map

    I have successfully managed to get a latitude and longitude and plot it onto this map by taking the leftmost longitude and rightmost longitude of the UK and using them to work out where to put the point on the map.

    This is the code (for use in Processing.js but could be used as js or anything):

    // Size of the map
    int width = 538;
    int height = 811;
    // X and Y boundaries
    float westLong = -8.166667;
    float eastLong = 1.762833;
    float northLat = 58.666667;
    float southLat = 49.95;
    
    void drawPoint(float latitude, float longitude){
    
     fill(#000000);
    
     x = width * ((westLong-longitude)/(westLong-eastLong));
     y = (height * ((northLat-latitude)/(northLat-southLat)));
    
     console.log(x + ", " + y);
     ellipseMode(RADIUS);
     ellipse(x, y, 2, 2);    
    
    }
    

    However, I haven't been able to implement a Mercator projection on these values. The plots are reasonably accurate but they are not good enough and this projection would solve it.

    I can't figure out how to do it. All the examples I find are explaining how to do it for the whole world. This is a good resource of examples explaining how to implement the projection but I haven't been able to get it to work.

    Another resource is the Extreme points of the United Kingdom where I got the latitude and longitude values of the bounding box around the UK. They are also here:

    northLat = 58.666667; 
    northLong = -3.366667; 
    eastLat = 52.481167; 
    eastLong = 1.762833; 
    southLat = 49.95;
    southLong = -5.2; 
    westLat = 54.45;
    westLong = -8.166667;
    

    If anyone could help me with this, I would greatly appreciate it!

    Thanks