How to find the bounding box for a web map tile in Python

Today I learnt …

When you’re working with tiled web maps you’ll often use a z/y/x coordinate system instead of geographic coordinate system like longitude and latitude. Map tiles use a grid system so, for example, you might talk about tile 14/8046/5106 — the 8,046th row and the 5,106rd column at the 14th zoom level. That particular tile corresponds to part of the Southside of Edinburgh.

Sometimes you’ll want to convert a tile’s z/y/x into a bounding box in longitude and latitude. In my particular case I needed to debug which features should appear in a tile, and I wanted the tile’s bounding box so I could pass it to PostGIS’s ST_MakeEnvelope function.

Using the Python script below (save it to a file named bbox.py) you can find the answer:

$ python3 bbox.py 14/8046/5106
ST_MakeEnvelope(-3.2080078125, 55.936894769039434, -3.18603515625, 55.94919982336745, 4326)
import math
import sys
from dataclasses import dataclass


@dataclass
class BoundingBox:
    north: float
    south: float
    east: float
    west: float


def tile_bbox(zoom: int, x: int, y: int) -> BoundingBox:
    return BoundingBox(
        north=tile_lat(y, zoom),
        south=tile_lat(y + 1, zoom),
        west=tile_lon(x, zoom),
        east=tile_lon(x + 1, zoom),
    )


def tile_lon(x: int, z: int) -> float:
    return x / math.pow(2.0, z) * 360.0 - 180


def tile_lat(y: int, z: int) -> float:
    return math.degrees(
        math.atan(math.sinh(math.pi - (2.0 * math.pi * y) / math.pow(2.0, z)))
    )


if __name__ == "__main__":
    bbox = tile_bbox(*map(int, sys.argv[1].split("/")))
    print(
        f"ST_MakeEnvelope({bbox.west}, {bbox.south}, {bbox.east}, {bbox.north}, 4326)"
    )

The script requires Python 3.7 or higher. Consider the code available under the MIT licence.