Maps with Django (part 1): GeoDjango, SpatiaLite and Leaflet
Tags: django, tutorial, python, javascript
Published: 2020-12-10 19:35:38

---
title: Maps with Django (part 1): GeoDjango, SpatiaLite and Leaflet
published: true
description: A quickstart guide to create a web map with the **Python** -based web framework **Django** using its module **GeoDjango** , the **SQLite** database with its spatial extension **SpaliaLite** and **Leaflet** , a **JavaScript** library for interactive maps.
date: 2020-12-07 23:00:00 UTC
tags: django,tutorial,python,javascript
canonical_url: https://www.paulox.net/2020/12/08/maps-with-django-part-1-geodjango-spatialite-and-leaflet/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/xkdkx5rujnzoyyptgeyh.jpg
series: PauLoX's Article
---

_A quickstart guide to create a web map with the **Python** based web framework **Django** using its module **GeoDjango** , the **SQLite** database with its spatial extension **SpaliaLite** and **Leaflet** , a **JavaScript** library for interactive maps._

## Introduction

In this guide we will see how to create a minimal web map using Django (the **Python**-based web framework), starting from its default project, writing a few lines of code and with the minimum addition of other software:

* **GeoDjango**, the **Django** geographic module

* **SpatiaLite**, the **SQLite** spatial extension

* **Leaflet**, a **JavaScript** library for interactive maps.

## Requirements

- The only python package required is **Django**.

- We’ll assume you have Django installed already. 

- This guide is tested with Django 3.1 and Python 3.8. 

## Creating a project

You need to create the basic project in your workspace directory with this command:

```bash
$ django-admin startproject mymap
```

That’ll create a directory `mymap`, which is laid out like this:

```console
mymap/
├── manage.py
└── mymap
    ├── __init__.py
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
```

## Creating the Markers app

To create your app, make sure you’re in the same directory as `manage.py` and type this command:

```console
$ python manage.py startapp markers
```

That’ll create a directory `markers`, which is laid out like this:

```console
markers/
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py
```

### Activating the Markers app

Modify the INSTALLED_APPS setting

Append `markers` to the `INSTALLED_APPS` in `mymap/settings.py`

```python
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "markers",
]
```

## Adding an empty web map

We're going to add an empty web to the app:


### Adding a template view

We have to add a `TemplateView` in `views.py`:

```python
"""Markers view."""

from django.views.generic.base import TemplateView


class MarkersMapView(TemplateView):
    """Markers map view."""

    template_name = "map.html"
```

### Adding a map template

We have to add a `templates/` directory in `markers/`:

```console
$ mkdir templates
```

And a `map.html` template in `markers/templates/`:

```html
{% load static %}
<!doctype html>
<html lang="en">
<head>
  <title>Markers Map</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" type="text/css" href="{% static 'map.css' %}">
  <link rel="stylesheet" type="text/css" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
  <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
</head>
<body>
  <div id="map"></div>
  <script src="{% static 'map.js' %}"></script>
</body>
</html>
```

### Adding javascript and css files

We have to add a `static/` directory in `markers/`:

```console
$ mkdir static
```

Add a `map.css` stylesheet in `markers/static/`:

```css
html, body {
  height: 100%;
  margin: 0;
}
#map {
  width: 100%;
  height: 100%;
}
```

Add a `map.js` stylesheet in `markers/static/`:

```javascript
const attribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
const map = L.map('map')
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: attribution }).addTo(map);
map.fitWorld();
```

### Adding a new URL

Add an `urls.py` files in `markers/`:

```python
"""Markers urls."""

from django.urls import path

from .views import MarkersMapView

app_name = "markers"

urlpatterns = [
    path("map/", MarkersMapView.as_view()),
]
```

Modify `urls.py` in `mymap/` :

```python
"""mymap URL Configuration."""

from django.contrib import admin
from django.urls import include, path

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

### Testing the web map

Now you can test the empty web map running this command:

```console
$ python manage.py runserver
```

Now that the server’s running, visit [http://127.0.0.1:8000/markers/map/](http://127.0.0.1:8000/markers/map/) with your Web browser. You’ll see a “Markers map” page, with a full page map. It worked!

![An empty web map page.](https://dev-to-uploads.s3.amazonaws.com/i/lz0yenfhzo4mps77rssy.png)
<figcaption>An empty web map page.</figcaption>

## Adding geographic features

### Installing SpatiaLite

We need to install the `SQLite` spatial extension `SpatiaLite`:

- on Debian-based GNU/Linux distributions (es: Debian, Ubuntu, ...):

```console
$ apt install libsqlite3-mod-spatialite
```

- on macOS using Homebrew:

```console
$ brew install spatialite-tools
```

### Changing the database engine

Modify the `DATABASES` default engine in `mymap/settings.py`

```python
DATABASES = {
    "default": {
        "ENGINE": "django.contrib.gis.db.backends.spatialite",
        "NAME": BASE_DIR / "db.sqlite3",
    }
}
```

### Activating GeoDjango

Modify the INSTALLED_APPS setting

Append `GeoDjango` to the `INSTALLED_APPS` in `mymap/settings.py`

```python
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django.contrib.gis",
    "markers",
]
```

## Adding some markers

Now we can add some markers in the map.

### Adding the Marker model

We're going to add a `Marker` model in `markes/models.py`:

```python
"""Markers models."""

from django.contrib.gis.db.models import PointField
from django.db import models


class Marker(models.Model):
    """A marker with name and location."""

    name = models.CharField(max_length=255)
    location = PointField()
```

Now we have to create migrations for the new model:

```console
$ python manage.py makemigrations
```

And then we'll apply this migration to the `SQLite` database:

```console
$ python manage.py migrate
```

### Activating the Marker admin

To insert new marker we have to add a Marker admin in `markes/admin.py`

```python
"""Markers admin."""

from django.contrib.gis import admin

from .models import Marker


@admin.register(Marker)
class MarkerAdmin(admin.OSMGeoAdmin):
    """Marker admin."""

    list_display = ("name", "location")
```

### Testing the admin

We have to create an admin user to login and test it:

```console
$ python manage.py createsuperuser
```

Now you can test the admin running this command:

```console
$ python manage.py runserver
```

Now that the server’s running, visit [http://127.0.0.1:8000/admin/markers/marker/add/](http://127.0.0.1:8000/admin/markers/marker/add/) with your Web browser. You’ll see a “Markers” admin page, to add a new markers with a map widget. I added a marker to the latest peak I climbed: "Monte Amaro 2793m 🇮🇹"

![Adding a marker in the admin page.](https://dev-to-uploads.s3.amazonaws.com/i/bmu74erfa4lcxk2bei5o.png)
<figcaption>Adding a marker in the admin page.</figcaption>

## Showing all markers in the web map

### Adding all markers in the view

We can add with a `serializer` all markers as a `GeoJSON` in the context of the `MarkersMapView` in `markes/views.py`:

```python
"""Markers view."""

import json

from django.core.serializers import serialize
from django.views.generic.base import TemplateView

from .models import Marker


class MarkersMapView(TemplateView):
    """Markers map view."""

    template_name = "map.html"

    def get_context_data(self, **kwargs):
        """Return the view context data."""
        context = super().get_context_data(**kwargs)
        context["markers"] = json.loads(serialize("geojson", Marker.objects.all()))
        return context
```

The value of the `markes` key in the `context` dictionary we'll something like that:

```JSON
{
    "type": "FeatureCollection",
    "crs": {
        "type": "name",
        "properties": {
            "name": "EPSG:4326"
        }
    },
    "features": [
        {
            "type": "Feature",
            "properties": {
                "name": "Monte Amaro 2793m \ud83c\uddee\ud83c\uddf9",
                "pk": "1"
            },
            "geometry": {
                "type": "Point",
                "coordinates": [
                    14.08591836494682,
                    42.08632592463349
                ]
            }
        }
    ]
}
```

### Inserting the GeoJSON in the template

Using `json_script` built-in filter we can safely outputs the Python dict with all markers as `GeoJSON` in `markers/templates/map.html`:

```html
{% load static %}
<!doctype html>
<html lang="en">
<head>
  <title>Markers Map</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" type="text/css" href="{% static 'map.css' %}">
  <link rel="stylesheet" type="text/css" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css">
  <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
</head>
<body>
  {{ markers|json_script:"markers-data" }}
  <div id="map"></div>
  <script src="{% static 'map.js' %}"></script>
</body>
</html>
```

### Rendering all markers in the map

We can render the `GeoJSON` with all markers in the web map using `Leaflet` in `markers/static/map.js`:

```javascript
const attribution = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
const map = L.map('map')
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: attribution }).addTo(map);
const markers = JSON.parse(document.getElementById('markers-data').textContent);
let feature = L.geoJSON(markers).bindPopup(function (layer) { return layer.feature.properties.name; }).addTo(map);
map.fitBounds(feature.getBounds(), { padding: [100, 100] });
```

## Testing the populated map

I populated the map with other markers of the highest or lowest points I've visited in the world to show them on my map.

Now you can test the populated web map running this command:

```console
$ python manage.py runserver
```

Now that the server’s running, visit [http://127.0.0.1:8000/markers/map/](http://127.0.0.1:8000/markers/map/) with your Web browser. You’ll see the “Markers map” page, with a full page map and all the markers. It worked!

![A map with some markers of the highest or lowest points I've visited in the world with the opened popup of the latest peak I climbed.](https://dev-to-uploads.s3.amazonaws.com/i/cut0d783ipt61sgpmfpv.png)
<figcaption>A map with some markers of the highest or lowest points I've visited in the world with the opened popup of the latest peak I climbed.</figcaption>

<p class="caption center">A map with some markers of the highest or lowest points I've visited in the world with the opened popup of the latest peak I climbed.</p>

## Curiosity

If you want to know more about my latest hike to the Monte Amaro peak you can see it on my Wikiloc account: [Round trip hike from Rifugio Pomilio to Monte Amaro 🔗](https://www.wikiloc.com/hiking-trails/round-trip-hike-from-rifugio-pomilio-to-monte-amaro-57466667).

## Conclusion

We have shown an example of a fully functional map, trying to use the least amount of software, without using external services.

This map is enough to show a few points in a simple project using SQLite and Django templates.

In future articles we will see how to make this map even more advanced using Django Rest Framework, PostGIS, etc ... to render very large numbers of markers in an even more dynamic way.

Stay tuned.

-- Paolo

<hr>

> **Resources**
> 
> - [GeoDjango - Dajngo Documentation 🔗](https://docs.djangoproject.com/en/stable/ref/contrib/gis/)
> - [Leaflet API reference 🔗](https://leafletjs.com/reference.html)
> - [SpatiaLite - SQL functions reference list 🔗](https://www.gaia-gis.it/gaia-sins/spatialite_topics.html)
> - [Safely Including Data for JavaScript in a Django Template 🔗](https://adamj.eu/tech/2020/02/18/safely-including-data-for-javascript-in-a-django-template/)

## License

This article and related presentation is released with Creative Commons Attribution ShareAlike license (CC BY-SA)

## Original

Originally posted on my blog:

[https://www.paulox.net/2020/12/08/maps-with-django-part-1-geodjango-spatialite-and-leaflet/](https://www.paulox.net/2020/12/08/maps-with-django-part-1-geodjango-spatialite-and-leaflet/)