Skip to content

Django + Leaflet Map

This guide shows how to build a complete Django application with an interactive map that displays Yoro codes. Users can click anywhere on the map to get a Yoro code, and see the Hilbert cell highlighted.

What you'll build

A Django page with:

  • An interactive Leaflet map
  • Click-to-encode: click anywhere → see the Yoro code + cell rectangle
  • A search bar to decode a Yoro code → fly to its location
  • The Hilbert grid overlay at the current zoom level

Prerequisites

pip install yoro[django]

Your Django project needs GeoDjango configured (PostGIS or SpatiaLite).

Step 1: Django Setup

settings.py

INSTALLED_APPS = [
    # ...
    "django.contrib.gis",
    "yoro.django",
]

urls.py

from django.urls import include, path
from . import views

urlpatterns = [
    path("", views.map_view, name="map"),
    path("api/yoro/", include("yoro.django.urls")),
]

views.py

from django.shortcuts import render

def map_view(request):
    return render(request, "map.html")

Step 2: The Template

Create templates/map.html:

<!DOCTYPE html>
<html>
<head>
    <title>Yoro Map</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Leaflet CSS -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css" />

    <style>
        body { margin: 0; font-family: system-ui, sans-serif; }
        #map { height: 100vh; width: 100%; }

        .yoro-panel {
            position: absolute;
            top: 10px;
            right: 10px;
            z-index: 1000;
            background: white;
            padding: 16px;
            border-radius: 8px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.2);
            min-width: 280px;
        }

        .yoro-panel h3 { margin: 0 0 12px; }

        .yoro-code {
            font-size: 24px;
            font-weight: bold;
            font-family: monospace;
            color: #e65100;
            margin: 8px 0;
        }

        .yoro-info { font-size: 13px; color: #666; margin: 4px 0; }

        .yoro-search {
            display: flex;
            gap: 8px;
            margin-top: 12px;
        }

        .yoro-search input {
            flex: 1;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-family: monospace;
            font-size: 14px;
        }

        .yoro-search button {
            padding: 8px 16px;
            background: #e65100;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
    </style>
</head>
<body>

<div id="map"></div>

<div class="yoro-panel">
    <h3>Yoro Code</h3>
    <div class="yoro-code" id="code-display">Click on the map</div>
    <div class="yoro-info" id="coords-display"></div>
    <div class="yoro-info" id="resolution-display"></div>
    <div class="yoro-info" id="precision-display"></div>

    <div class="yoro-search">
        <input type="text" id="code-input" placeholder="CI-PV9XD" />
        <button onclick="decodeCode()">Go</button>
    </div>
</div>

<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9/dist/leaflet.js"></script>

<script>
    // Initialize map centered on Cote d'Ivoire
    const map = L.map('map').setView([7.5, -5.5], 7);

    // Add OpenStreetMap tiles
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; OpenStreetMap contributors',
        maxZoom: 19,
    }).addTo(map);

    // Layer for the Yoro cell rectangle
    let cellLayer = null;

    // Click handler: encode the clicked location
    map.on('click', async function(e) {
        const { lat, lng } = e.latlng;
        const domain = 'CI';  // Change for other countries
        const precision = zoomToPrecision(map.getZoom());

        const resp = await fetch(
            `/api/yoro/encode/?lat=${lat}&lon=${lng}&domain=${domain}&precision=${precision}`
        );
        const data = await resp.json();
        showResult(data);
    });

    function showResult(data) {
        document.getElementById('code-display').textContent = data.code;
        document.getElementById('coords-display').textContent =
            `${data.lat.toFixed(6)}, ${data.lon.toFixed(6)}`;
        document.getElementById('resolution-display').textContent =
            `Resolution: ~${data.resolution_m.toFixed(1)} m`;
        document.getElementById('precision-display').textContent =
            `Precision: p=${data.precision} (${data.code.split('-')[1].length} chars)`;

        // Draw the cell rectangle
        if (cellLayer) map.removeLayer(cellLayer);
        const b = data.bounds;
        cellLayer = L.rectangle(
            [[b.lat_min, b.lon_min], [b.lat_max, b.lon_max]],
            { color: '#e65100', weight: 2, fillOpacity: 0.15 }
        ).addTo(map);
    }

    // Decode a code and fly to it
    async function decodeCode() {
        const code = document.getElementById('code-input').value.trim();
        if (!code) return;

        const resp = await fetch(`/api/yoro/decode/?code=${encodeURIComponent(code)}`);
        if (!resp.ok) {
            alert('Invalid code');
            return;
        }
        const data = resp.json().then(data => {
            showResult(data);
            map.flyTo([data.lat, data.lon], 14);
        });
    }

    // Map zoom level to a sensible Yoro precision
    function zoomToPrecision(zoom) {
        if (zoom <= 8)  return 4;
        if (zoom <= 10) return 7;
        if (zoom <= 12) return 9;
        if (zoom <= 14) return 12;
        if (zoom <= 16) return 14;
        if (zoom <= 18) return 17;
        return 19;
    }
</script>
</body>
</html>

Step 3: Run

python manage.py migrate
python manage.py runserver

Open http://localhost:8000/ — click anywhere on the map to see Yoro codes.

How It Works

  1. Click → JavaScript sends GET /api/yoro/encode/?lat=...&lon=... to the Django backend
  2. Backend → Yoro encodes the coordinates, returns the code + cell bounds
  3. Frontend → Displays the code and draws the cell rectangle on the map
  4. Decode → Type a code in the search bar → GET /api/yoro/decode/?code=... → fly to location

The precision adapts to the zoom level: zoomed out = large cells (region-level codes), zoomed in = small cells (meter-level codes).

Domain selection

Change const domain = 'CI' in the JavaScript to match your country. Use 'XX' for worldwide coverage (lower resolution).

Next Steps