Skip to content

Django Integration

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

Install

pip install yoro-maps[django]

Configuration

1. Add to INSTALLED_APPS

# 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:

# 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:

{
    "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:

# 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

# 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:

{
  "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:

{% 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:

// 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

# 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"]
# 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")),
]