1. Overview
When implementing applications that use maps, we will typically run into the problem of coordinate conversion. Most of the time, we need to convert latitude and longitude to a 2D point to display. Fortunately, to solve this problem, we can utilize the formulas of the Mercator projection.
In this tutorial, we’ll cover the Mercator Projection and will learn how to implement its two variants.
2. Mercator Projection
The Mercator projection is a map projection introduced by the Flemish cartographer Gerardus Mercator in 1569. A map projection converts latitude and longitude coordinates on the Earth to a point on a flat surface. In other words, it translates a point on the surface of the earth to a point on a flat map.
There are two ways of implementing the Mercator projection. The pseudo Mercator projection treats the Earth as a sphere. The true Mercator projection models the Earth as an ellipsoid. We will implement both versions.
Let’s start with a base class for both Mercator projection implementations:
abstract class Mercator { final static double RADIUS_MAJOR = 6378137.0; final static double RADIUS_MINOR = 6356752.3142; abstract double yAxisProjection(double input); abstract double xAxisProjection(double input); }
This class also provides the major and the minor radius of Earth measured in meters. It is well known that Earth is not exactly a sphere. For that reason, we need two radiuses. Firstly, the major radius is the distance from the center of the earth to the equator. Secondly, the minor radius is the distance from the center of the earth to the north and south poles.
2.1. Spherical Mercator Projection
The pseudo-projection model treats the earth as a sphere. In contrast to the elliptical projection where the Earth would be projected on a more accurate shape. This approach allows us a quick estimation to the more precise, but computational heavier elliptical projection. As a result of that, the direct measurements of distances in this projection will be approximate.
Furthermore, the proportions of the shapes on the map will marginally alter. As a result of that latitude and ratios of shapes of objects on the map like countries, lakes, rivers, etc. are not precisely preserved.
This is also called the Web Mercator projection – commonly used in web applications including Google Maps.
Let’s implement this approach:
public class SphericalMercator extends Mercator { @Override double xAxisProjection(double input) { return Math.toRadians(input) * RADIUS_MAJOR; } @Override double yAxisProjection(double input) { return Math.log(Math.tan(Math.PI / 4 + Math.toRadians(input) / 2)) * RADIUS_MAJOR; } }
The first thing to note on this approach is the fact that this approach represents the radius of the earth by one constant and not two as it really is. Secondly, we can see that we have implemented two functions to use for converting to x-axis projection and y-axis projection. In the class above we have used Math library provided by java to help us make our code simpler.
Let’s test a simple conversion:
Assert.assertEquals(2449028.7974520186, sphericalMercator.xAxisProjection(22)); Assert.assertEquals(5465442.183322753, sphericalMercator.yAxisProjection(44));
It is worth noting that this projection will map points into a bounding box (left, bottom, right, top) of (-20037508.34, -23810769.32, 20037508.34, 23810769.32).
2.2. Elliptical Mercator Projection
The true projection models the earth as an ellipsoid. This projection gives accurate ratios for objects anywhere on Earth. Certainly, it respects objects on the map but not 100% accurate. However, this approach is not the most frequently used because it is computationally complex.
Let’s implement this approach:
class EllipticalMercator extends Mercator { @Override double yAxisProjection(double input) { input = Math.min(Math.max(input, -89.5), 89.5); double earthDimensionalRateNormalized = 1.0 - Math.pow(RADIUS_MINOR / RADIUS_MAJOR, 2); double inputOnEarthProj = Math.sqrt(earthDimensionalRateNormalized) * Math.sin( Math.toRadians(input)); inputOnEarthProj = Math.pow(((1.0 - inputOnEarthProj) / (1.0+inputOnEarthProj)), 0.5 * Math.sqrt(earthDimensionalRateNormalized)); double inputOnEarthProjNormalized = Math.tan(0.5 * ((Math.PI * 0.5) - Math.toRadians(input))) / inputOnEarthProj; return (-1) * RADIUS_MAJOR * Math.log(inputOnEarthProjNormalized); } @Override double xAxisProjection(double input) { return RADIUS_MAJOR * Math.toRadians(input); } }
Above we can see how complex this approach is regarding the projection on the y-axis. This is because it should take into consideration the non-round earth shape. Although the true Mercator approach seems complex, is more accurate than the spherical approach as it uses to radius for representing earth one minor and one major.
Let’s test a simple conversion:
Assert.assertEquals(2449028.7974520186, ellipticalMercator.xAxisProjection(22)); Assert.assertEquals(5435749.887511954, ellipticalMercator.yAxisProjection(44));
This projection will map points into a bounding box of (-20037508.34, -34619289.37, 20037508.34, 34619289.37).
3. Conclusion
If we need to convert latitude and longitude coordinates onto a 2D surface, we can use the Mercator projection. Depending on the accuracy we need for our implementation, we can use the spherical or elliptical approach.
As always, we can find the code of this article over on GitHub.