CHAPTER 05
Web Mercator and tile math
How every web map (Google, OSM, Mapbox) tiles the world, with the formulas to convert lat/lon to tile coordinates.
Almost every web map (Google, OSM, Mapbox, Leaflet) uses Web Mercator (EPSG:3857). It's Mercator with one important simplification: it treats the Earth as a sphere of radius 6,378,137 m even though the underlying data is on the WGS84 ellipsoid. This introduces small errors but makes the math fast.
The reason the entire web mapping ecosystem converged on this standard is practical: tiles precomputed by one provider can be layered with data from another. Google, Apple, Microsoft Bing, and OpenStreetMap all publish tiles at the same zoom levels covering the same geographic cells — your map client can mix them freely because the math is identical.
Tile grid explorer
z=2 · 4×4 = 16 tiles
The slippy map tile system
The world is divided into a quadtree of square tiles, each 256 × 256 pixels:
- At zoom 0, the entire world is one tile.
- At zoom 1, it's a 2×2 grid (4 tiles).
- At zoom z, it's tiles ( total).
A tile is identified by three numbers (z, x, y):
z— zoom levelx— column, increases east (0 at lon = −180°)y— row, increases south (0 at the top, near lat = 85.05°)
The y-axis pointing south (top = 0) follows screen convention: pixel coordinates on a display start at the top-left corner. Latitude 85.05° is the Web Mercator cutoff — at that latitude, the Mercator y-value equals x, producing a square world map.
Tile URL example: https://tile.openstreetmap.org/{z}/{x}/{y}.png
Lat/lon → tile coordinates
(φ in radians here)
The x formula is just linear interpolation of longitude into [0, n). The y formula applies the Mercator projection and then normalises it from [0, 1] top-to-bottom.
In JavaScript:
function lonLatToTile(lon, lat, zoom) {
const n = 2 ** zoom;
const x = Math.floor(n * (lon + 180) / 360);
const latRad = lat * Math.PI / 180;
const y = Math.floor(
n * (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2
);
return { x, y, z: zoom };
}
Tile coordinates → lat/lon (top-left corner)
function tileToLonLat(x, y, z) {
const n = 2 ** z;
const lon = x / n * 360 - 180;
const lat = Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n))) * 180 / Math.PI;
return { lon, lat };
}
Note that this returns the top-left corner of the tile. To get the bounding box of a tile, call it for (x, y) and (x+1, y+1).
Worked example
Q: Which tile contains Lahore (31.5204, 74.3587) at zoom 12?
So tile (12, 2893, 1670). You can check by visiting
https://tile.openstreetmap.org/12/2893/1670.png.
Resolution at a given zoom
How many meters does one pixel represent? At the equator:
At any latitude φ, multiply by .
| Zoom | meters/pixel (equator) |
|---|---|
| 0 | 156,543 |
| 5 | 4,892 |
| 10 | 152.9 |
| 15 | 4.78 |
| 18 | 0.60 |
| 20 | 0.15 |
Resolution drops by half with each zoom increment — each zoom level doubles the number of tiles in each dimension, covering the same area with four times the pixels.
References
3 sources- [1]
Open Geospatial Consortium (2007).
“OpenGIS Web Map Tile Service Implementation Standard.”
The WMTS standard formalising the slippy-map tile grid used by Google, OSM, and Mapbox.
- [2]
Simonds, L. (2006).
“Slippy Map Tilenames.”
The original derivation of the z/x/y tile coordinate formulas from Web Mercator.
- [3]
Esri (2012).
“Well-Known Scale Sets.”
ArcGIS REST API documentation
Documents the standard zoom-level resolution table (metres per pixel at each zoom level).
Related chapters
- Map projections — flattening the globe — the Mercator math behind tile coordinates
- Pixel and world coordinates — finer-grained version of the tile grid
- How maps render — tiles, vectors, and the GPU pipeline — what happens to tiles after fetch
- Formula reference — lat/lon ↔ tile coordinate formulas