19

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

RedBlueThing
  • 42,006
  • 17
  • 96
  • 122
betamax
  • 13,431
  • 9
  • 38
  • 55

10 Answers10

47

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

Raphael
  • 3,846
  • 1
  • 28
  • 28
  • Cool thanks for sharing, it's nice to have some code to reference. This looks like what I ended up with but I ended up doing it in Javascript. – betamax May 08 '12 at 15:46
  • Any chance you'd be willing to post the image you used w/ this bit of code? Thanks! – Lite Byte Jun 05 '12 at 07:30
  • Just added the image. Cheers, Raph ;) – Raphael Jun 05 '12 at 09:50
  • @RaphaelWichmann do you have the reverse of this? convertPixelToGeo()? I tried converting xarinko's answer to Javascript and wasn't getting the same lat/lon. I'm looking to be able to convert back and forth. – wwwuser Oct 23 '13 at 04:26
  • thanks so much for that, this was ridicously useful for me! used in in javascript and it works like a charm – riccardolardi Nov 20 '13 at 16:32
  • is it just me or does anyone else's value for X always come back the same? – AngryDuck Jan 17 '14 at 14:48
  • Thanks so much! this works great, translated to actionscript. – thisispete Jan 23 '14 at 00:15
  • Hi, this map looks awesome! which style and config do you setted up in TileMill? – 501 - not implemented Feb 05 '14 at 19:23
  • Can someone help me on this? I created a world map using TileMill, with bounds -180,-62.2679,180,85.0511 on a map that's about 1200x860. For whatever reason, the Longitude pixel coordinate is fine, but the latitude goes below the width of the map. Driving me crazy, what am I missing? Am using - var mapLonLeft = -180; var mapLonRight = 180.85; var mapLatBottom = 85.0511; – Gerry Jun 24 '14 at 01:20
  • I am having the same problem as @Gerry. Any help is appreciated. – pbathala Aug 21 '14 at 23:50
  • hi @Gerry, were you able to solve this issue of latitude pixel being off on page ? – pbathala Aug 22 '14 at 14:46
  • This was kinda confusing to me, so to whomever may be similarly confused: `mapLonLeft` is 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), `mapLonRight` is the longitude of the right side of the map, and `mapLatBottom` is the latitude of the bottom of the map. – Cully May 09 '17 at 04:32
  • This worked for me. It took a minute, but here goes with a map of New Jersey cities. Data comes from zip_code database. Thank you Rafael http://signrelease.net/nj2/nj_dom.php with these parameters $mapWidth = 544; $mapHeight = 855; $mapLonLeft = -75.615507; $mapLonRight = -73.537431; $mapLonDelta = $mapLonRight - $mapLonLeft; $mapLatBottom = 38.942021; – Nicolas Giszpenc Feb 15 '18 at 07:03
  • any idea on how to take the map tilt degree into consideration in the above calculations? – Mostafa Khattab Oct 01 '19 at 18:13
  • I guess this is based on the assumption that the latitude 0 is in the middle of the map? I have a map showing from -58 to around 85 latitude and -170 to 190 longitude... Longitude works fine but latitude is really off – danikaze Apr 01 '22 at 17:29
12

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);
}
Xarinko
  • 121
  • 1
  • 2
10

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};
}
Rob Willett
  • 121
  • 1
  • 4
  • thank you for this conversion, it worked awesome for what i needed it for, i have tried to up vote both you and Raphael. – JohnV Jul 27 '18 at 01:37
10

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
}
Cully
  • 6,427
  • 4
  • 36
  • 58
8

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.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 3
    I'm fairly certain it is - on this page: http://en.wikipedia.org/wiki/Mercator_projection#Uses it mentions Google Maps uses the projection. I overlayed the map included in my post (also found here: http://en.wikipedia.org/wiki/File:Uk-map.svg) onto a Google map version and they fitted exactly so that made me think that it was. Is it still possible to use the Mercator projections? If not only to see if that solves the problem.. Or should I rethink my maps? – betamax Jan 20 '10 at 19:01
  • @betamax: Well, it looks like that map may indeed be Mercator. It's certainly possible to project lat/lon onto an existing map, and in fact your linear interpolation method should work just fine for the longitude. For the latitude, however, you will need to use the projection equations presented on that wikipedia page, and work out the relationship between the actual latitude and the coordinates on your map. It should be a linear relationship (meaning only a multiply and an add) between the Mercator `y` and your map coordinate `y'`. That is, y' = Ay + B for some A and B. – Greg Hewgill Jan 20 '10 at 20:44
4

@Xarinko Actionscript snippet in Javascript (with some testing values)

var mapWidth = 1500;
var mapHeight = 1577;

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

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



function convertPixelToGeo(tx, ty)
{   
    /* 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 = mapWidth / mapLonDelta * 360/(2 * Math.PI);     
    var mapOffsetY = ( worldMapRadius / 2 * Math.log( (1 + Math.sin(mapLatBottomRadian) ) / (1 - Math.sin(mapLatBottomRadian))  ));
    var equatorY = mapHeight + mapOffsetY;   
    var a = (equatorY-ty)/worldMapRadius;

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

convertPixelToGeo(241,444)
nauti
  • 1,396
  • 3
  • 16
  • 37
4

I know the question was asked a while ago, but the Proj4JS library is ideal for transforming between different map projections in JavaScript.

UK maps tend to use the OSGB's National Grid which is based on a Transverse Mercator projection. Ie. like a conventional Mercator but turned 90 degrees, so that the "equator" becomes a meridian.

winwaed
  • 7,645
  • 6
  • 36
  • 81
1

C# implementation:

private Point ConvertGeoToPixel(
    double latitude, double longitude, // The coordinate to translate
    int imageWidth, int imageHeight, // The dimensions of the target space (in pixels)
    double mapLonLeft, double mapLonRight, double mapLatBottom // The bounds of the target space (in geo coordinates)
) {
    double mapLatBottomRad = mapLatBottom * Math.PI / 180;
    double latitudeRad = latitude * Math.PI / 180;

    double mapLonDelta = mapLonRight - mapLonLeft;
    double worldMapWidth = (imageWidth / mapLonDelta * 360) / (2 * Math.PI);
    double mapOffsetY = worldMapWidth / 2 * Math.Log((1 + Math.Sin(mapLatBottomRad)) / (1 - Math.Sin(mapLatBottomRad)));

    double x = (longitude - mapLonLeft) * (imageWidth / mapLonDelta);
    double y = imageHeight - ((worldMapWidth / 2 * Math.Log((1 + Math.Sin(latitudeRad)) / (1 - Math.Sin(latitudeRad)))) - mapOffsetY);

    return new Point()
    {
        X = Convert.ToInt32(x),
        Y = Convert.ToInt32(y)
    };
}
Alex McMillan
  • 17,096
  • 12
  • 55
  • 88
0

If you want to avoid some of the messier aspects of lat/lng projections intrinsic to Proj4JS, you can use D3, which offers many baked-in projections and renders beautifully. Here's an interactive example of several flavors of Azimuthal projections. I prefer Albers for USA maps.

If D3 is not an end-user option -- say, you need to support IE 7/8 -- you can render in D3 and then snag the xy coordinates from the resultant SVG file that D3 generates. You can then render those xy coordinates in Raphael.

Chris Wilson
  • 6,599
  • 8
  • 35
  • 71
0

This function works great for me because I want to define the mapHeight based on the map I want to plot. I'm generating PDF maps. All I need to do is pass in the map's max Lat , min Lon and it returns the pixels size for the map as [height,width].

convertGeoToPixel(maxlatitude, maxlongitude)

One note in the final step where $y is set, do not subtract the calculation from the mapHeight if your coordinate system 'xy' starts at the bottom/left , like with PDFs, this will invert the map.

$y =  (($worldMapWidth / 2 * log((1 + sin($lat)) / (1 - sin($lat)))) - $mapOffsetY);
alQemist
  • 362
  • 4
  • 13