# Django Integration

Yoro Maps includes a full Django integration for serving map tiles and routes from a web application.

## Install

```bash
pip install yoro-maps[django]
```

## Configuration

### 1. Add to INSTALLED_APPS

```python
# settings.py
INSTALLED_APPS = [
    ...
    "yoromaps.django",
]
```

### 2. Configure the maps database

The `.yoromaps` file is a SQLite database that Django can use as a second database:

```python
# settings.py
import yoromaps

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "myapp",
        ...
    },
    "maps": yoromaps.db_config("/data/mali.yoromaps"),
}
```

The `db_config()` helper returns:

```python
{
    "ENGINE": "django.db.backends.sqlite3",
    "NAME": "/data/mali.yoromaps",
}
```

### 3. Add the database router

The router ensures that yoromaps queries go to the `maps` database, not your default:

```python
# settings.py
DATABASE_ROUTERS = ["yoromaps.django.router.YoroMapsRouter"]
```

The router:

- Directs reads and writes for `yoromaps` models to the `maps` database.
- Prevents Django migrations from running on the `maps` database (the schema is managed by yoromaps).
- Leaves all other models unaffected.

### 4. Add URL routes

```python
# urls.py
from django.urls import path, include

urlpatterns = [
    path("maps/", include("yoromaps.django.urls")),
    ...
]
```

This registers two endpoints:

- `GET /maps/tiles/{z}/{x}/{y}.png` — Serve map tiles
- `GET /maps/route/?codes=ML-ABC,ML-XYZ` — Calculate routes

## Tile serving

Once configured, tiles stored in the `.yoromaps` database are served at:

```
GET /maps/tiles/12/1234/567.png
```

Tiles are served with `Cache-Control: public, max-age=86400` (24-hour cache). If a tile is not found, a 404 is returned.

!!! tip
    Make sure your `.yoromaps` file was built with `--tiles` to include tile data. Without tiles, the tile endpoint will always return 404.

## Route API

The route endpoint accepts Yoro codes as a comma-separated query parameter:

```
GET /maps/route/?codes=ML-ABC,ML-XYZ
```

Response:

```json
{
  "total_distance_km": 642.3,
  "total_duration_min": 578.1,
  "legs": [
    {
      "distance_km": 642.3,
      "duration_min": 578.1,
      "found": true,
      "geometry": {
        "type": "LineString",
        "coordinates": [[-8.0, 12.6], [-7.5, 13.0], ...]
      },
      "steps": [
        {"instruction": "Continue on Route Nationale 6", "distance_m": 12340, "name": "Route Nationale 6"}
      ]
    }
  ]
}
```

At least 2 codes are required; pass more for multi-leg routing.

## Leaflet map display

Here is a complete example of displaying tiles and routes on a Leaflet map in a Django template:

```html
{% load static %}
<!DOCTYPE html>
<html>
<head>
    <title>Yoro Maps</title>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9/dist/leaflet.js"></script>
    <style>
        #map { height: 100vh; width: 100%; }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>
        const map = L.map('map').setView([12.6, -8.0], 7);

        // Use tiles from yoro-maps (offline)
        L.tileLayer('/maps/tiles/{z}/{x}/{y}.png', {
            maxZoom: 14,
            attribution: '&copy; OpenStreetMap contributors'
        }).addTo(map);

        // Calculate and display a route
        fetch('/maps/route/?codes=ML-ABC,ML-XYZ')
            .then(r => r.json())
            .then(data => {
                if (data.legs) {
                    data.legs.forEach(leg => {
                        if (leg.found && leg.geometry) {
                            // GeoJSON coordinates are [lon, lat], Leaflet needs [lat, lon]
                            const coords = leg.geometry.coordinates.map(c => [c[1], c[0]]);
                            L.polyline(coords, {color: '#FF5722', weight: 4}).addTo(map);
                        }
                    });
                }
            });
    </script>
</body>
</html>
```

## Fallback to online tiles

If you want to use online tiles as a fallback when offline tiles are missing, use multiple tile layers:

```javascript
// Try offline tiles first, fall back to online
const offlineLayer = L.tileLayer('/maps/tiles/{z}/{x}/{y}.png', {
    maxZoom: 14,
    attribution: '&copy; OpenStreetMap contributors'
});

const onlineLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; OpenStreetMap contributors'
});

// Use online as base, offline overlay where available
onlineLayer.addTo(map);

L.control.layers({
    "Online": onlineLayer,
    "Offline": offlineLayer,
}).addTo(map);
```

## Complete settings example

```python
# settings.py
import yoromaps

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "yoromaps.django",
    "myapp",
]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    },
    "maps": yoromaps.db_config(BASE_DIR / "data" / "mali.yoromaps"),
}

DATABASE_ROUTERS = ["yoromaps.django.router.YoroMapsRouter"]
```

```python
# urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("maps/", include("yoromaps.django.urls")),
]
```
