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

10

21

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

Edvard answered 20/1, 2010 at 18:33 Comment(0)
C
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.

Cribbage answered 20/1, 2010 at 18:41 Comment(2)
I'm fairly certain it is - on this page: 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: 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?Edvard
@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.Cribbage
K
50

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

Klaus answered 1/5, 2012 at 17:36 Comment(8)
@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.Easement
is it just me or does anyone else's value for X always come back the same?Antipole
Hi, this map looks awesome! which style and config do you setted up in TileMill?Buchan
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;Hephzipa
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.Neigh
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 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;Cog
any idea on how to take the map tilt degree into consideration in the above calculations?Fight
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 offFolse
U
13

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);
}
Unoccupied answered 10/11, 2012 at 16:4 Comment(0)
N
12

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
}
Neigh answered 9/5, 2017 at 4:38 Comment(0)
J
11

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};
}
Jehovah answered 5/12, 2014 at 9:51 Comment(0)
C
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.

Cribbage answered 20/1, 2010 at 18:41 Comment(2)
I'm fairly certain it is - on this page: 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: 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?Edvard
@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.Cribbage
A
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.

Aromatic answered 18/11, 2010 at 22:48 Comment(0)
A
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)
Archivolt answered 18/3, 2015 at 20:37 Comment(0)
P
2

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)
    };
}
Paving answered 15/1, 2019 at 22:12 Comment(0)
R
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.

Redpencil answered 10/11, 2012 at 16:36 Comment(0)
S
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);
Snip answered 30/1, 2016 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.