warming up your workspace

How a map works, Mercator, tiles, and your GPS pin

Every online map, the one on your phone right now, performs a small miracle constantly: it takes your latitude and longitude, two numbers on a sphere, and drops a pin on the exact pixel of a flat, square image. Behind that is a 400-year-old projection and a clever tiling scheme. Both fit in a handful of lines.

The one idea

A globe cannot be flattened without distortion, so mapmakers choose which distortion to accept. Web maps almost all use Web Mercator, which preserves angles (a right angle on the ground is a right angle on the map, so shapes look correct locally) at the cost of area (things near the poles get stretched enormously). Then the flattened world is cut into a pyramid of 256-pixel tiles at each zoom level. Projecting your coordinates onto that pyramid is what places your pin.

Project a coordinate to the map

Longitude is easy: it maps linearly across the width. Latitude is where Mercator earns its name, it runs through a logarithm so that angles stay true. The standard tile formula:

import math

def latlon_to_tile(lat, lon, zoom):
    n = 2 ** zoom                       # the map is n x n tiles at this zoom
    x = (lon + 180.0) / 360.0 * n
    lat_rad = math.radians(lat)
    y = (1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n
    return x, y

# London, at zoom level 4
print(latlon_to_tile(51.5074, -0.1278, 4))
(7.994320..., 5.320408...)

London lands at tile column ~8, row ~5 out of the 16x16 grid at zoom 4. The whole-number part (tile 8, 5) tells the map which image to fetch; the fractional part tells it where inside that 256-pixel tile to draw the pin.

From tile to the exact pixel

Multiply the fractional part by 256 and you have the pixel offset within the tile, which is the pin.

def latlon_to_pixel(lat, lon, zoom):
    x, y = latlon_to_tile(lat, lon, zoom)
    tile_x, tile_y = int(x), int(y)
    px = int((x - tile_x) * 256)
    py = int((y - tile_y) * 256)
    return (tile_x, tile_y), (px, py)

print(latlon_to_pixel(51.5074, -0.1278, 4))
((7, 5), (254, 82))

So London's pin is in tile (7, 5), at pixel (254, 82) inside it. That is the entire pipeline a map runs for every marker: project to tile space, split into which-tile and where-in-tile, draw. Zoom in one level and n doubles, so the same coordinate lands in a more precise tile, which is why detail appears as you zoom.

Why Greenland looks as big as Africa

Look again at the latitude formula: asinh(tan(lat)) blows up as latitude approaches the poles. That stretching is the price Mercator pays to keep angles honest. The consequence is the famous lie of the world map: Greenland appears roughly the size of Africa, but Africa is about fourteen times larger. The projection did not make a mistake; it made a choice, correct shapes over correct sizes, and near the poles that choice becomes dramatic.

# how much a small patch is stretched, by latitude
for lat in [0, 45, 60, 75]:
    stretch = 1 / math.cos(math.radians(lat))
    print(f"{lat} deg: {stretch:.1f}x horizontal stretch")
0 deg: 1.0x horizontal stretch
45 deg: 1.4x horizontal stretch
60 deg: 2.0x horizontal stretch
75 deg: 3.9x horizontal stretch

At the equator, no stretch. At 60 degrees, everything is doubled. At 75, nearly quadrupled. That single number, 1/cos(latitude), is the whole distortion, and it is why high-latitude countries loom so large on a web map.

Where it shows up

Every pin, route, and heatmap you have seen on a web map ran this projection. Knowing it means you can place your own markers correctly, understand why area comparisons on a Mercator map are misleading, and pick a different projection when area actually matters. That is the kind of ground-truth understanding the geospatial track is built on: maps are math you can write, not black boxes you can only consume.