You drag a map, it loads more map. You drop a pin, it lands exactly where you tapped. This feels like one seamless thing, but underneath, every "slippy map", Google Maps, OpenStreetMap, the map in a thousand apps, is built from two pieces of math: a way to flatten a round Earth onto a flat screen, and a way to chop that flat image into loadable squares. Both fit in a few lines of Python.
Learn them and your blue dot stops being magic: you will be able to take a latitude and longitude and compute exactly which image tile it falls in and where inside that tile the pin goes.
The one idea: the Earth is round and your screen is not
A screen is a flat grid of pixels. The Earth is a sphere. To draw one on the other you need a projection: a rule that turns (longitude, latitude) into (x, y). There are many, each lying about the Earth in a different way (you cannot flatten a sphere without distortion). Web maps almost all use one, Web Mercator, because it has a property that makes interactive maps possible: it is conformal, it preserves angles and local shape, so north is always up and a small square of ground stays square. The price is area: Greenland looks the size of Africa. For navigation, keeping shapes and directions right is the trade everyone makes.
Step one: flatten the globe
Longitude is the easy axis, it maps straight to x. Latitude is the interesting one: Mercator stretches it more and more toward the poles (that is the Greenland effect), using a logarithm.
import math
def mercator(lon, lat):
x = math.radians(lon)
y = math.log(math.tan(math.pi / 4 + math.radians(lat) / 2))
return x, y
print(mercator(0, 0)) # (0.0, 0.0) equator / prime meridian
print(mercator(0, 60)) # (0.0, 1.317...) 60N is pushed far from the equator
That log(tan(...)) is the whole Mercator projection. The reason a degree of latitude near the pole takes more vertical space than a degree at the equator is exactly that logarithm growing. Everything else is rescaling this into pixels.
Step two: chop it into tiles
A world map at full zoom is billions of pixels, you cannot download it. So the projected world is cut into a quadtree of 256×256-pixel tiles. At zoom 0 the whole world is one tile. At zoom 1 it is a 2×2 grid (four tiles). At zoom z it is a 2^z × 2^z grid. Each tile has an address (x, y, z), and that is literally the URL the map requests: .../z/x/y.png.
Here is the standard formula that turns a coordinate into the tile that contains it:
def lonlat_to_tile(lon, lat, z):
n = 2 ** z
x = int((lon + 180.0) / 360.0 * n)
lat_rad = math.radians(lat)
y = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n)
return x, y
print(lonlat_to_tile(-0.1276, 51.5072, 12)) # London -> (2047, 1362)
Two details that matter:
-
asinh(tan(lat))is the Mercator y in disguise.asinh(tan(θ))equalslog(tan(π/4 + θ/2))from step one, the same projection, just written so it normalizes neatly into the 0..1 range that the tile grid needs. -
int(...)picks the tile; the fractional part picks the spot inside it. Keep the fraction instead of truncating and you get where in the tile the point lands, the pixel your pin sits on. Drag the map and the viewer just computes which(x, y, z)tiles overlap the screen and fetches them. That is the entire "slippy" mechanism.
Step three: where the blue dot comes from
Your device's GPS gives a raw (latitude, longitude). The map runs exactly the math above: project to Web Mercator, figure out which tiles are on screen and where they sit, then place the marker at the projected position. The blue dot is mercator(lon, lat) rescaled to screen pixels, nothing more. (How the GPS got that lat/lon in the first place, trilateration from satellite timing, is its own beautiful story for another post.)
Why this is worth knowing
Maps feel like a solved, sealed product, but they are just a projection plus a tiling scheme, and both are short. Once you have written them you can do things that otherwise look like wizardry: pre-compute which tiles a region needs, place markers without a mapping library, reason about why two map providers' tiles line up (they agree on Web Mercator and the z/x/y scheme), or debug why a point is "slightly off" (usually someone mixed up a projection).
This is the recurring payoff of building the primitive yourself: the product stops being opaque. A degree of latitude, a logarithm, a quadtree of squares, that is a world map. If you want to go further, distances on a sphere, projections and their trade-offs, spatial indexing, GPS trilateration, that is the path through the geospatial track, where you build the map from the coordinates up.
Top comments (0)