# Tile Serving

Yoro Maps can download and serve map tiles offline, stored in MBTiles format inside the `.yoromaps` database.

## How tiles work

Map tiles are small PNG images that cover the world at different zoom levels. At zoom 0, the entire world is one tile. At zoom 12, there are millions of tiles. Yoro Maps downloads only the tiles within a country's bounding box and stores them in the `tiles` table.

The tiles table uses the [MBTiles specification](https://github.com/mapbox/mbtiles-spec) with TMS (flipped Y) coordinate scheme.

## Download tiles during build

### CLI

```bash
# Build with tiles (default zoom 6-12)
yoromaps download ML --output mali.yoromaps --tiles

# Custom zoom range
yoromaps download ML --output mali.yoromaps --tiles --zoom-min 8 --zoom-max 14
```

### Python

```python
import yoromaps

yoromaps.build("ML", "mali.yoromaps", include_tiles=True, zoom_max=14)
```

!!! warning "Tile download size"
    Higher zoom levels = exponentially more tiles. Zoom 14 for a large country like Mali can be 500+ MB and take hours to download. Start with zoom 12 and increase if needed.

## Download tiles separately

You can add tiles to an existing `.yoromaps` database:

```python
from yoromaps.db import open_db
from yoromaps.tiles import download_tiles
from yoromaps.countries import COUNTRIES

conn = open_db("mali.yoromaps")
country = COUNTRIES["ML"]

n = download_tiles(
    conn,
    bbox=country.bbox,
    zoom_min=6,
    zoom_max=12,
    tile_url="https://tile.openstreetmap.org/{z}/{x}/{y}.png",
    progress=lambda msg, cur, tot: print(f"{msg}: {cur}/{tot}"),
)
print(f"Downloaded {n} tiles")
```

## Custom tile providers

The default tile source is OpenStreetMap. You can use any XYZ tile server by changing the URL template:

```python
from yoromaps.tiles import download_tiles

# OpenStreetMap (default)
download_tiles(conn, bbox, tile_url="https://tile.openstreetmap.org/{z}/{x}/{y}.png")

# OpenTopoMap
download_tiles(conn, bbox, tile_url="https://tile.opentopomap.org/{z}/{x}/{y}.png")

# Stamen Terrain (Stadia Maps)
download_tiles(conn, bbox, tile_url="https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png")
```

!!! note
    Respect tile server usage policies. OpenStreetMap requires a valid User-Agent header (yoromaps sets one automatically) and limits bulk downloads. For large-scale use, consider running your own tile server.

## Serve tiles from Django

With the Django integration configured, tiles are served at:

```
GET /maps/tiles/{z}/{x}/{y}.png
```

See the [Django Integration guide](django.md) for setup details.

## Serve tiles from Python

You can retrieve tiles directly from the database:

```python
from yoromaps.db import open_db
from yoromaps.tiles import get_tile

conn = open_db("mali.yoromaps")

# Get a tile (XYZ scheme — the function handles TMS conversion internally)
tile_data = get_tile(conn, z=10, x=512, y=512)

if tile_data:
    with open("tile.png", "wb") as f:
        f.write(tile_data)
```

## Check tile count

```python
from yoromaps.db import open_db
from yoromaps.tiles import tile_count

conn = open_db("mali.yoromaps")
print(f"Tiles in database: {tile_count(conn)}")
```

Or from the CLI:

```bash
yoromaps info mali.yoromaps
```

## Rate limiting

The tile downloader includes built-in rate limiting (50ms between requests) and sets a proper User-Agent header. Tiles that already exist in the database are skipped, so you can safely re-run the download to fill in missing tiles.

## Zoom level guide

| Zoom | Coverage           | Tiles per country | Use case            |
|------|--------------------|-------------------|---------------------|
| 6    | Region overview    | ~50               | Country-level view  |
| 8    | City overview      | ~500              | Regional navigation |
| 10   | Neighborhood       | ~5,000            | City navigation     |
| 12   | Street level       | ~50,000           | Street-level detail |
| 14   | Building level     | ~500,000          | High detail         |

Exact counts depend on the country's geographic area.
