There are several sources of world map data available online today, from the likes of Google Maps, the Open Street Maps project and Nokia Maps. These systems usually make their maps available as collections of tiles and use the Mercator projection system to create the tiles.

In order to request the right tile for a specific set of geographic coordinates, it’s necessary to be able to convert a given latitude and longitude into the column and row for the matching map tile. In addition to the coordinates, it’s also necessary to specify a map zoom level.

Before I jump into the implementation details, it’s necessary to describe a bit of the background. This article refers to the Mercator projection. It works with the latitude and longitude in degrees and represented as signed, floating point numbers. For example, latitudes north of the equator are in the range (0.0 – 90.0] and longitudes west of the Prime Meridian are in the range (0.0 – -180.0]. Although the procedure I’m about to describe will work with latitude values in excess of ±85.5, the values are usually meaningless, because the Mercator projection breaks down near the poles of the earth, due to near infinite expansion in the projection.

The tiles in the projection are identified by column and row, with both beginning at zero. There is also always the same number of columns as rows, regardless of the zoom level. Zoom levels typically begin at 1, the least amount of zoom, and proceed up to around 18, the greatest amount of zoom. Zoom level 1 typically displays the entire map in a single tile, while zoom level 18 displays an area similar to a portion of a US city block . The total number of tiles at a particular zoom level is determined by the formula (2^{zoomLevel})^{2}.

With that out of the way, let’s look at how to actually convert a latitude and longitude to a map tile column and row. Since it’s easy to read, I’ll use Python to write my “pseudo-code”.

First, convert the latitude and longitude from degrees to radians:

lonRad = math.radians(lonDeg) latRad = math.radians(latDeg)

and then convert the coordinates into the Mercator projection with

columnIndex = lonRad rowIndex = math.log(math.tan(latRad) + (1.0 / math.cos(latRad)))

At this point, we have a column and row, but for a set of tiles which has its origin in the center of the collection. Usually, the origin of the collection is in the top, left corner. Also, we haven’t accounted for the zoom level yet. Let’s do that next, with

columnNormalized = (1 + (columnIndex / math.pi)) / 2 rowNormalized = (1 - (rowIndex / math.pi)) / 2 tilesPerRow = 2 ** zoomLevel column = round(columnNormalized * (tilesPerRow - 1)) row = round(rowNormalized * (tilesPerRow - 1))

At this point, *column*, *row* and *zoomLevel* can be used to request the appropriate tile from a map tile service, which will contain the starting latitude and longitude and will be at the specified zoom level.

In line:

“rowNormalized = (1 – (row / math.pi)) / 2”

it should be rowIndex, correct?