The Math Behind Google Maps
The math behind Google Maps, explained for developers: how a latitude and longitude turns into a Web Mercator tile and then the exact pixel on your screen, with working JavaScript.
Umar Farooq · compiled MapMath from a decade of map-app work
· 9 min read
Open Google Maps, drop a pin, and drag the map around. It feels like one smooth surface you can slide forever. Underneath, a chain of arithmetic runs on every frame, turning a point on a round planet into a flat square image and then into the exact pixel under your cursor. This post walks that whole chain, the math behind Google Maps from a raw coordinate to a rendered pixel, with working JavaScript at each step.
None of it is secret. The same formulas power OpenStreetMap, Mapbox, Leaflet, and the live tracker in your food-delivery app. Once you can follow a single point through the pipeline, most "why is my marker in the wrong place" bugs stop being mysterious.
The math behind Google Maps, in one pipeline
Every interactive web map does the same four things to put your location on screen:
(lat, lon) -> Web Mercator -> world coordinate -> pixel / tile
a point flatten the a square 0..256 which image,
on Earth round Earth grid for zoom 0 which pixel
We will take each arrow in turn. The point we will follow the whole way is New York City, at roughly 40.7128 north, 74.0060 west.
Step 1: it starts with a coordinate
A latitude and longitude is an angle, not a distance. Longitude is how far east or west you are from the prime meridian, from -180 to +180 degrees. Latitude is how far north or south you are from the equator, from -90 to +90 degrees. Google Maps, like almost everything on the modern web, expresses these on the WGS84 datum, the same reference frame your phone's GPS reports.
The detail that trips people up is that the Earth is not a sphere. It bulges at the equator, so it is closer to a slightly squashed ellipsoid. That difference matters for high-accuracy distance work, and it is why a "radius of the Earth" is really two numbers. For the rendering pipeline, though, Google Maps treats the Earth as a perfect sphere, which keeps the projection math simple and fast. If you want the why behind that choice, the coordinates and shape of the Earth chapters go through it properly.
So we begin with two angles:
const nyc = { lat: 40.7128, lon: -74.0060 };
Step 2: flatten the round Earth with Web Mercator
A screen is flat. The Earth is not. To draw one on the other you need a projection, and the projection Google Maps chose is Web Mercator, formally EPSG:3857. It won because of one property: it is conformal, meaning it preserves angles and the shapes of small features. A square city block looks like a square at every zoom. North is always straight up. For a map you pan and rotate, that local correctness feels right, even though the global picture is distorted.
The forward projection takes longitude and latitude in degrees and returns a position on the flat map. In its raw metres form, against the WGS84 radius:
const R = 6378137; // Earth's radius in metres (WGS84 semi-major axis)
function lonLatToMeters(lon, lat) {
const x = R * (lon * Math.PI) / 180;
const y = R * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI / 180) / 2));
return { x, y };
}
That log(tan(...)) term is the heart of it. It stretches the map vertically more and more as you move away from the equator. The formula written out:
where lambda and phi are longitude and latitude in radians.
The price of keeping shapes correct is that areas get wildly exaggerated near the poles. Greenland looks roughly the size of Africa on a web map. In reality Africa is about fourteen times larger. Drag the slider below to feel how a fixed shape balloons as it moves north.
Tissot's indicatrix: equal circles on the globe, distorted on Mercator
Each circle represents the same area on the globe. Near the poles, Mercator inflates them vertically by a factor of 1/cos(φ). At 60°N that's ×2, Greenland looks twice as tall as it is.
This single tradeoff, correct angles in exchange for distorted areas, is the most important thing to understand about why web maps look the way they do. The full derivation and the alternatives live in the projections chapter.
Step 3: from projection to a world coordinate
Raw metres are awkward to work with on screen, so Google Maps defines its own tidy version of the same projection. It maps the whole world onto a single 256 by 256 square at zoom 0, where (0, 0) is the top-left corner and (256, 256) is the bottom-right. Google calls this a world coordinate.
const TILE_SIZE = 256;
function project(lat, lon) {
const sinY = Math.sin((lat * Math.PI) / 180);
const x = TILE_SIZE * (0.5 + lon / 360);
const y = TILE_SIZE * (0.5 - Math.log((1 + sinY) / (1 - sinY)) / (4 * Math.PI));
return { x, y };
}
project(nyc.lat, nyc.lon);
// → { x ≈ 75.0, y ≈ 96.4 }
It is the same Mercator math as before, just normalised. Longitude maps linearly across the width. Latitude runs through the log term again so the vertical stretch is preserved. Two things are worth noticing. The y axis points down, because screen pixels start at the top-left. And the value is a float, not an integer, because we have not picked a zoom level yet.
Step 4: zoom in, and land on a pixel
Zoom is where the world coordinate becomes something you can actually draw. Each zoom level doubles the map in each direction. At zoom z the world is 256 * 2^z pixels wide, and it is carved into a grid of 2^z by 2^z tiles, each one a 256 by 256 image.
Multiply the world coordinate by 2^z to get the absolute pixel, then divide by the tile size to find which tile that pixel sits in:
function worldToPixel({ x, y }, zoom) {
const scale = 2 ** zoom;
return { px: x * scale, py: y * scale };
}
function pixelToTile({ px, py }) {
return { x: Math.floor(px / TILE_SIZE), y: Math.floor(py / TILE_SIZE) };
}
const world = project(nyc.lat, nyc.lon);
const pixel = worldToPixel(world, 12);
const tile = pixelToTile(pixel);
// tile → { x: 1205, y: 1540 } at zoom 12
That tile identity, zoom 12, column 1205, row 1540, is exactly what the map requests over the network. You can watch it happen: open any slippy map, look at the network panel, and you will see URLs like /12/1205/1540.png stream in as you drag. Each one is a single 256 pixel square the math just asked for.
Tile grid explorer
z=2 · 4×4 = 16 tiles
The whole tile addressing scheme, including the inverse direction and the edge cases that break naive code, is its own topic. I wrote a separate, complete walkthrough of it: Lat/Lon to Tile Coordinates: The Complete Math. The tile math chapter is the reference version.
The leftover fraction, the part Math.floor threw away, is the pixel offset inside the tile. That is how the map knows to draw your marker at pixel (249, 4) of that specific image rather than just somewhere in the square. The pixel coordinates chapter covers that sub-tile step in full.
Zoom does not have to be an integer for the math, but tile servers only store integer-zoom images. At zoom 12.5 a map fetches the zoom 12 tiles and scales them in the GPU. So the projection is continuous, the tiles are discrete, and the renderer bridges the gap.
"How far is it?" is a different question
Projecting points onto a flat map is enough to draw it. It is not enough to measure it. If you take two pixel positions and use plain Pythagoras, the answer is wrong, and it gets more wrong the further the points are from the equator, because Web Mercator stretches distance unevenly.
For the real distance between two coordinates you go back to the angles and measure across the curved surface. The standard tool is the Haversine formula, which gives the great-circle distance, the shortest path over a sphere:
function haversine(lat1, lon1, lat2, lon2) {
const R = 6371000; // mean Earth radius in metres
const toRad = (d) => (d * Math.PI) / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return 2 * R * Math.asin(Math.sqrt(a));
}
haversine(40.7128, -74.0060, 34.0522, -118.2437);
// → 3,936,000 metres, about 3,936 km from New York to Los Angeles
Great-circle distance · drag to rotate
5570 km
Point A
Point B
Distance
5570.5 km
5570452 m
Haversine gives you the straight line over the surface, the way a plane flies. It is not the distance Google Maps shows you for driving directions. That is a different problem entirely.
Haversine assumes a perfect sphere, so it carries up to about 0.5 percent error. When you need sub-metre accuracy over long distances, you switch to Vincenty's formulae on the ellipsoid. The distance chapter has both, and the tradeoff between them.
"How do I get there?" is graph math, not geometry
The blue driving line is not geometry at all. The road network is stored as a graph: intersections are nodes, road segments are edges, and each edge has a cost such as travel time. Finding a route is finding the cheapest path through that graph, which is what Dijkstra's algorithm and its faster cousin A* do.
This is why driving distance is always longer than the Haversine straight line, and why traffic can change your route in real time. The cost on each edge moves, so the cheapest path moves with it. Routing is a deep area on its own, and the routing fundamentals chapter builds it up from a plain road graph to A* with a real heuristic.
The formulas, collected
If you just want the equations in one place, without the prose around them, the formula reference lists every projection, distance, and tile formula in the guide with a one-line explanation and a link to the chapter that derives it. It is free and needs no account.
Putting the whole chain together
Here is the full path, from the two angles we started with to the tile the map fetches:
const nyc = { lat: 40.7128, lon: -74.0060 };
const world = project(nyc.lat, nyc.lon); // flatten with Web Mercator
const pixel = worldToPixel(world, 12); // apply the zoom level
const tile = pixelToTile(pixel); // find the image to request
const url = `https://tile.openstreetmap.org/12/${tile.x}/${tile.y}.png`;
// → https://tile.openstreetmap.org/12/1205/1540.png
Paste that URL into a browser and you get back the 256 pixel square containing New York. That is the math behind Google Maps in miniature: a coordinate becomes an angle pair, the angles get projected onto a flat conformal surface, the surface is scaled by the zoom level, and the result picks out one tile and one pixel. Every pan, every zoom, every marker is that same pipeline running again.
Each step here is a doorway into a deeper one. The projection hides a derivation, the tiles hide a quadtree, the distance hides an ellipsoid, the route hides a graph search. If you want the complete version, with runnable code and an interactive visual for every formula, that is what the MapMath guide is.
